发现一个Solid中的坑

353 阅读4分钟

补充说明

我之前看solid项目模板里面没eslint还挺高兴的 后来才发现其实是有的
github.com/solidjs-com…\

// 报错
const Text:Component<{value:string}> = props=>props.value

image.png 高兴是误以为solid里没那么多规矩 不过有eslint能帮助纠错更好 而且solid确实没那么多村规

正文

没错 solid比react对新人更加友好 不过我这种半新不新的 也更容易进入一些比较隐蔽的坑
上篇文章提到了我自制的文件路由系统 正是在写的过程中 我意识到solid没有想象中那么美好
虽然没有闭包的问题 但它有getter的问题
简单来说就是————
你不能觉得类型没问题就真的没问题了
想要响应式 那就得传getter
以JSX对象为例 不应该const a = \<div xx/> 然后传到底下
而是要const a = ()=>\<div xx/> 然后把a传下去 给别人调用
其他类型的值其实还好 主要是JSX
它的语法比较暧昧 新手(我)很容易弄不清具体的生效时间

复盘一下

文件路由插件的原理

//index.tsx
import routes from '~solid-pages'

//xxx
render(() => <Router>{routes}</Router>, root!)

vite.env.d.ts'~solid-pages'这个模块的默认导出是<Router>可以接受的静态配置项 所以这里没有类型错误
而实际上 vite-plugin-pages会生成一个临时的js文件 它的默认导出就是这个routes数组
我希望使用类似next.js app router的路由结构 必须重写resolver 自己实现从文件结构到js文件的全过程
最终目的是把文件结构中的layout error loading page 404转化为如下的配置项

{
  path: '/',
  component: (props) => (
    <Layout>
      <ErrorBoundary
        fallback={(err, reset) => <Error err={err} reset={reset}></Error>}
      >
        <Suspense fallback={<Loading></Loading>}>{props.children}</Suspense>
      </ErrorBoundary>
    </Layout>
  ),
  children: [
    {
      path: '',
      component: Page,
    },
    //..其他的下级路由
    {
      path:'*rest',
      component: NotFound
    }
  ],
}

尝试

需要注意的是 js ts文件是不能使用jsx语法的 vite会报错
至少我反复拷打ai 没有得到解决办法
所以往'~solid-pages'里输出的其实是这种东西

component:(props) =>
  createComponent(Layout, {
    children: createComponent(ErrorBoundary, {
      fallback: (err, reset) => createComponent(Error, { err, reset }),
      children: createComponent(Suspense, {
        fallback: createComponent(Loading, {}),
        children: props.children,
      }),
    }),
  })

但是发现不顶用 ErrorBoundary和Suspense都不生效 只有Layout正常
一开始我以为是这两个组件的问题 比如需要运行时什么的
想起之前看过一篇远程组件的文章 我把输出内容改成了这样

export default function getRoutes(ErrorBoundary,Suspense){
    //...
}

不过还是没用
后来还试了Dynamic组件 依旧没用

解决

我又尝试改了一下写法 把组装的工作放在外面做

//~solid-pages
const routesMap = {
    path:'/',
    component:{Layout,Error,Loading}
        //xxx
}
export default routesMap

// index.tsx
import routesMap from '~solid-pages'

const routes = [{
  path:'/',
  component:(props:ParentProps)=>{
    const { Layout,Error,Loading } = routesMap.xxx.xxx.xx
    let res = null
    if (Loading) {
      res = <Suspense fallback={<Loading />}>{props.children}</Suspense>
    }
    if (Error) {
      res = (
        <ErrorBoundary
          fallback={(err, reset) => <Error err={err} reset={reset} />}
        >
          {res ?? props.children}
        </ErrorBoundary>
      )
    }
    if (Layout) {
      res = <Layout>{res ?? props.children}</Layout>
    }
    return res
  }
}]

这次行了一半 能loading 不能error
把loading的部分注释掉 就可以error了
此刻我突然意识到问题的关键 那就是solid实现响应式的原理
要在使用一个值的时候调用getter函数 而不能提前把这个值取出来
所以要写成

    const { Layout,Error,Loading } = routesMap.xxx.xxx.xx
    let res = null
    if (Loading) {
      res = ()=><Suspense fallback={<Loading />}>{props.children}</Suspense>
    }
    if (Error) {
      const _res = res
      res = ()=>(
        <ErrorBoundary
          fallback={(err, reset) => <Error err={err} reset={reset} />}
        >
          {_res?_res(): props.children}
        </ErrorBoundary>
      )
    }
    if (Layout) {
      const _res = res
      res = ()=> <Layout>{_res ?_res(): props.children}</Layout>
    }
    return res()

其实刚才的写法 Layout的children也不会自动更新 但一开始我以为是热更新的问题 并没有发现
那么为什么一开始的createComponent也不行呢?

      createComponent(Suspense, {
        fallback: createComponent(Loading, {}),
        children: props.children,
      })

原因是这里已经把props.children的值取了
正确的写法是

      createComponent(Suspense, {
        fallback: createComponent(Loading, {}),
        get children(){ 
          return props.children 
        },
      })

此事(get children())在文档里亦有记载 不过当时我不知道为什么要这么写 现在又找不到具体位置了
所以这种写法是错误的

const comp = <div xx/>

return <div>{comp}</div>

要写成

const comp = ()=><div xx/>
或者
const comp = children(()=><div xxx/>)

return <div>{comp}</div>

其实换成数字或者随便一个其他类型 都不会出错 主要是JSX语法和react太像 太具有迷惑性了

更新

还是存在问题 但是我已经不知道原因了 热更新就是不行 ErrorBoundary 就是拦截不到异常
what can i say 摆了
还有一个问题

屏幕截图 2025-04-06 113405.png
'/test1'切换回'/' 居然不能触发loading?
这下有正当理由切回next了

又更新

又是铸币时刻 熬夜真的很害人啊 上述问题基本解决了
切换路由不能触发loading 只需要把loading和page放在同一层就可以解决

{
    path:'/'
    component:props=>(
        <Layout>
            <ErrorBoundary fallback={xx}>{props.children}</ErrorBoundary>
        </Layout>
        )
    children:[{
        path:'',
        component:props=>(
            <Suspense fallback={xx}>
                <Page>{props.children}</Page>
            </Suspense>
        )
    }]
}

没有热更新其实solid ErrorBoundary的机制 不会主动脱离错误状态