从React项目 迁移到 Solid项目的踩坑记录

从React项目 迁移到 Solid项目的踩坑记录

solid 中父子组件执行次序和 react 不一样

注意,这里的 子组件是指 children形式的子组件。

对于非children形式的子组件,都是先执行父组件在执行子组件。

tsx 复制代码
const ComponentA = ({ children }) => {
    console.log('ComponentA')
    return (
        <div style={{ background: 'skyblue' }}>
          {/* children形式的子组件 */}
            {children}
        </div>
    );
};

对比一般形式的子组件写法:

tsx 复制代码
/* 父组件 */
const ComponentA = ({  }) => {
  console.log('ComponentA')
  return (
    <div style={{ background: 'skyblue' }}>
      /* 子组件 */
      <ChildComp/>
    </div>
  );
};

在 React 中,先执行父组件,再执行children子组件

tsx 复制代码
import { Test } from './components/test/index';

function App() {
  console.log('App')

  return (
    <div>
      <Test></Test>
    </div>
  );
}

export default App
tsx 复制代码
import React from "react";

const ComponentA = ({ children }) => {
  console.log('ComponentA')
  return (
    <div style={{ background: 'skyblue' }}>
      {children}
    </div>
  );
};


const ComponentB = () => {
  console.log('ComponentB')
  return (
    <div style={{ background: 'pink' }}>
      ComponentB
    </div>
  );
};

const ComponentC = () => {
  console.log('ComponentC')
  return (
    <div style={{ background: 'red' }}>
      ComponentC
    </div>
  );
};


export const Test = () => {
  console.log('test')
  return (
    <div>
      <ComponentA>
        <ComponentC></ComponentC>
      </ComponentA>

      <ComponentB></ComponentB>
    </div>
  )
}

是先执行 父组件函数后才执行子组件函数,即使是 children形式的子组件。

在 Solid 中,先执行children子组件,再执行父组件

tsx 复制代码
import { Component, ParentComponent } from 'solid-js';

const ComponentA: ParentComponent = ({ children }) => {
  console.log('ComponentA')
  return (
    <div style={{ background: 'skyblue' }}>
      {children}
    </div>
  );
};



const ComponentB = () => {
  console.log('ComponentB')
  return (
    <div style={{ background: 'pink' }}>
    </div>
  );
};

const ComponentC = () => {
  console.log('ComponentC')
  return (
    <div style={{ background: 'red' }}>
      ComponentC
    </div>
  );
};


export const Test = () => {
  console.log('test')
  return (
    <div>
      <ComponentA>
        <ComponentC></ComponentC>
      </ComponentA>

      <ComponentB></ComponentB>
    </div>
  )
}

这导致的迁移问题

tsx 复制代码
import DynamicRender from 'xxx'
import { createCtx } from 'xxx'

const [useGlobalCtx, GlobalProvider] = createCtx(useCreateGlobalCtx)

const App = () => {
  console.log('APP')
  return (
    <div id="app">
      <GlobalProvider>
        <DynamicRender />
      </GlobalProvider>
    </div>
  );
};
tsx 复制代码
import { createContext, ParentComponent, useContext } from "solid-js"

export const createCtx = <T extends Record<string, any> | null>(
  hook: () => T
) => {

  const Ctx = createContext<T | undefined>(undefined)

  function useCtx() {
    // debugger
    const c = useContext(Ctx)
    if (c === undefined)
      throw new Error('获取 context 必须包裹在Provider里 并提供value')
    return c
  }

  const Provider:ParentComponent = ({ children }) => {
    // 2. 父组件函数后执行
    console.log('Ctx.Provider')
    const value = hook()

    // 1. children 子组件先执行
    return <Ctx.Provider value={value}>{children}</Ctx.Provider>
  }

  return [useCtx, Provider] as const
}

注意上面这种写法的生命周期,console.log('Ctx.Provider')最后打印的,也就是说 Ctx.Provider value={value}是最后才有 value 值的。

问题:先调用子组件children函数,才调用父组件函数,当 子组件children函数被调用时,里面使用了useContext去获取上下文的 value值,由于 value 值是在父组件函数中提供的,但是父组件还没有执行,这就导致子组件获取不到 value值,导致报错。

解决

不使用封装的父组件去包裹上下文的Provider( Ctx.Provider),避免 children形式的子组件写法:

tsx 复制代码
import DynamicRender from '@/components/dynamic-render'
import { GlobalProvider } from './hooks/useGlobalCtx'
import "./app.less"

const Ctx = createContext<T | undefined>(undefined)

const App = () => {
  console.log('APP')

  const value = {}

  return (
    <div id="app">
      <Ctx.Provider value={value}>
        <DynamicRender />
      </Ctx.Provider>Ctx.Provider>
    </div>
  );
};

solid 组件更新机制 和 react 不同

对于 React

在 React 中,父组件中的状态变化时,父组件更新,子组件默认更新,因为 react 不知道父组件中的状态是否有被子组件使用到。

对于 solid

在 solid 中,当信号量变化时,只有该信号量的订阅者会响应数据的变化。也就是说,如果父组件中定义了一个信号量,这个 信号量如果在通过 props 传递给子组件时不是信号类型,那么子组件中就没办法使用该信号量,这样的情况下,当父组件中的信号量更新时,子组件不会更新。

可能会存在的问题

我们封装一个组件,可能需要根据外部传入的 props 来 更新组件的状态(受控组件),对于 solid 来说,由于其细粒度响应式的更新机制, 我们要实现上述的效果,那么父组件传递给子组件的 prop 就必须得是Accessor类型。

如果一个组件的 props 中大部分属性都需要变为Accessor,那我们在定义 ts 类型时就需要多次用Accessor<>包裹原数据类型,很麻烦,如:

tsx 复制代码
interface Props {
  expired: Accessor<boolean>
  privilegeNumber: Accessor<number>
  assetNumber: Accessor<number>
  showTooltip?: Accessor<boolean>
  mid?: Accessor<number>
  callback1: () => {}
  callback2: () => {}
}

解决

我们可以实现一个 ts 类型工具:

tsx 复制代码
import { Accessor } from "solid-js";

/** 泛型工具类型,将 Props 中的每个属性值包装进 Accessor 类型 */
// export type AccessorizeProps<Props> = {
// 	[K in keyof Props]: Accessor<Props[K]>;
// };


/** 泛型工具类型,将 Props 中的每个属性值包装进 Accessor 类型,除了那些在 ExcludeKeys 中指定的键 */
export type AccessorizeProps<Props, ExcludeKeys extends keyof Props = never> = {
  [K in keyof Props as K extends ExcludeKeys ? never : K]: Accessor<Props[K]>;
} & {
  [K in keyof Props as K extends ExcludeKeys ? K : never]: Props[K];
}

关键部分解释:K in keyof Props as K extends ExcludeKeys ? never : K

  • 作用 : 遍历 Props 的所有键,并根据 ExcludeKeys 过滤掉指定的键。
  • 过程:
    1. Props 的每个键 K 进行遍历。
    2. 检查 K 是否存在于 ExcludeKeys 中:
      • 如果存在,则将其映射为 never,从而从结果类型中移除。
      • 如果不存在,则保留该键 K

使用:

tsx 复制代码
interface Props {
  expired: boolean
  privilegeNumber: number
  assetNumber: number
  showTooltip?: boolean
  mid?: number
  callback1: () => {}
  callback2: () => {}
}

type NewProps = AccessorizeProps<
  Props,
  "callback1" | "callback2"
  >

图片 src 处 require() 不能使用模版字符串

项目相关包版本如下:

json 复制代码
"vite": "^5.2.0",
  "vite-plugin-html": "^3.2.2",
  "vite-plugin-require-transform": "^1.0.21",
  "vite-plugin-solid": "^2.11.0"
"solid-js": "^1.9.3",

src 处 不能使用模版字符串

tsx 复制代码
<img src={require(`@assets/images/ic_experience.png`)} alt="" />

只能使用普通字符串

tsx 复制代码
<img src={require('@assets/images/ic_experience.png')} alt="" />

否则报错如下:

javascript 复制代码
Failed to resolve import "" from "xxxx/aaaaa.tsx". Does the file exist?
  import * as _vite_plugin_require_transform__0 from "";

jotai 状态管理库迁移

Jotai 是一个原始且灵活的 React 状态管理库。

由于 solid 的信号可以在组件外创建,所以我们可以直接使用 全局信号来当做全局状态。

使用 jotai:

tsx 复制代码
import { atom } from 'jotai'

export const secondFloorShowAtom = atom<boolean>(false)

直接使用信号量:

tsx 复制代码
import { createSignal } from 'solid-js'

const [secondFloorShow, setSecondFloorShow] = createSignal<boolean>(false)

export {
  secondFloorShow,
  setSecondFloorShow
}

完~

欢迎补充,欢迎指正~

相关推荐
windliang2 分钟前
Cursor 写一个网页标题重命名的浏览器插件
前端·浏览器
JavaDog程序狗5 分钟前
【前端】前端 CSS 原子化,代码乐高随便搭
前端·css
只可远观6 分钟前
Mac搭建Flutter IOS环境详细指南
前端·flutter·macos·ios
程序员马晓博7 分钟前
还是聊聊吧:"大龄"程序员失业的一年
前端·程序员
忧郁的蛋~23 分钟前
开发vue项目所需要安装的依赖包
前端·javascript·vue.js
服部26 分钟前
如何查看指定作者在所有分支的提交记录
前端·git·github
前端付豪35 分钟前
1、为什么浏览器要有渲染流程? ——带你一口气吃透 Critical Rendering Path
前端·后端·浏览器
前端付豪37 分钟前
3、Node.js异步编程彻底吃透
前端·后端·node.js