SolidJS 1.0在今年6月正式发布后,我就一直在关注。它的性能很强,API设计也很优雅,号称"拥有React的开发体验和Svelte的运行时性能"。上个月我用SolidJS做了一个中小型项目,整体体验不错,但也踩了不少坑。本文记录实战中遇到的问题和解决方案,给想用SolidJS的朋友一个参考。
一、为什么选SolidJS
在选技术栈的时候,我对比了React、Vue 3、Svelte和SolidJS。最终选SolidJS的原因有几个:
- 性能好。 SolidJS采用细粒度的响应式更新,不需要虚拟DOM,运行时性能接近原生JS,在JS框架性能 benchmark中名列前茅。
- API熟悉。 SolidJS的API和React很像,有JSX、Hooks、组件化,有React经验的人上手很快。
- 包体积小。 Hello World只有不到5KB,比React小很多,对首屏加载友好。
- TypeScript支持好。 SolidJS对TypeScript的支持很完善,类型推导很智能。
项目做下来,这些优点都感受到了。但也遇到了不少坑,下面一个个说。
二、响应式的坑
SolidJS的响应式是核心,也是最容易踩坑的地方。
1. 解构props会丢失响应式。
这是最常见的坑。在React中,我们习惯解构props:
function MyComponent({ name, age }) {
return <div>{name} - {age}</div>
}但在SolidJS中,这样写会丢失响应式。因为props是一个响应式对象,解构之后,变量就变成了普通的值,不会再随props更新。
正确的写法是不解构,直接用props.name、props.age。或者用splitProps来拆分props,保持响应式:
function MyComponent(props) {
const [local, others] = splitProps(props, ['name', 'age'])
return <div>{local.name} - {local.age}</div>
}这个坑我踩了好几次,刚开始总是忘记,导致组件不更新。
2. 在createEffect中访问信号要注意依赖。
SolidJS的createEffect会自动追踪依赖,在依赖变化时重新执行。但如果在effect中访问了条件分支里的信号,依赖追踪可能不符合预期。
比如:
createEffect(() => {
if (show()) {
console.log(name())
}
})当show()为false时,name()不会被访问,所以不会被追踪为依赖。当show()变成true时,effect会重新执行,这时候name()才会被追踪。这个行为其实是合理的,但刚开始用的时候容易困惑。
3. 不要在渲染期间创建信号。
在组件渲染过程中创建信号(createSignal)是可以的,但不要在条件分支或循环中创建,否则会导致不一致。信号应该在组件顶层创建。
4. store的更新是不可变的。
SolidJS的store(createStore)看起来可以直接修改,但实际上是不可变更新。比如:
const [state, setState] = createStore({ count: 0 })
// 正确
setState('count', c => c + 1)
// 错误,不会触发更新
state.count++刚开始我习惯性地直接修改state,发现界面不更新,查了文档才知道要用setState。
三、组件生命周期的坑
1. 没有componentDidMount,用onMount。
SolidJS没有React的componentDidMount,对应的是onMount。onMount在组件挂载到DOM之后执行,可以用来获取DOM元素、发起请求等。
function MyComponent() {
let ref
onMount(() => {
console.log(ref) // 可以获取到DOM元素
})
return <div ref={ref}>Hello</div>
}2. onCleanup的执行时机。
onCleanup在组件卸载时执行,也会在effect重新执行之前执行。刚开始我以为只有卸载时才执行,后来发现在effect的清理函数中也会用到。
3. 组件函数只执行一次。
这是SolidJS和React最大的区别之一。React的组件函数会在每次渲染时重新执行,而SolidJS的组件函数只执行一次。这意味着你不能在组件函数中写副作用,也不能用普通变量来存储会变化的状态。
刚开始我很不适应,因为React的思维模式已经根深蒂固了。比如我想在组件中计算一个值,习惯性地写:
function MyComponent() {
const [count, setCount] = createSignal(0)
const doubled = count() * 2 // 这是错的,doubled不会更新
return <div>{doubled}</div>
}正确的写法是用createMemo:
const doubled = createMemo(() => count() * 2)或者直接在JSX中计算:
return <div>{count() * 2}</div>这个坑踩了好几次之后,才慢慢适应了SolidJS的思维模式。
四、状态管理的坑
1. 全局状态管理方案不成熟。
React有Redux、Zustand、Jotai等成熟的状态管理库,SolidJS的生态还比较新,全局状态管理的选择不多。
官方推荐用createContext+createStore来做全局状态,但用起来没有Redux那么方便。我在项目中自己封装了一个简单的store,基本够用,但不如成熟的库完善。
2. 跨组件共享状态要注意。
在SolidJS中,信号可以直接在组件外创建,然后在多个组件中共享。这比React的Context简单,但也要注意不要滥用,否则状态流向会不清晰。
我的建议是:组件内部状态用createSignal,跨组件状态用Context,全局状态用单独的store模块。
五、JSX的坑
1. class和className。
SolidJS的JSX用class而不是className,这和React不一样。刚开始总是写错,写成className,发现样式不生效,查了半天才发现。
2. 样式绑定。
SolidJS的样式绑定和React不一样,style属性接受一个对象,而且是响应式的:
<div style={{ color: color(), 'font-size': '14px' }}>注意属性名要用kebab-case,不能用camelCase。
3. ref的用法。
SolidJS的ref和React类似,但有一个区别:SolidJS的ref可以是一个函数,在元素创建时执行:
<div ref={el => console.log(el)}>这个特性有时候很方便,但也要注意,函数ref会在每次渲染时执行(虽然SolidJS组件只渲染一次)。
4. 列表渲染用For组件。
SolidJS的列表渲染用<For>组件,而不是map:
<For each={items()}>{item => <li>{item}</li>}</For>For组件会做diff,只更新变化的项,性能很好。但要注意,each必须是一个响应式的值,如果是普通数组,就不会更新。
六、生态和工具链的坑
1. 生态还不够丰富。
SolidJS还很新,生态不如React丰富。很多常用的库没有SolidJS版本,比如一些UI组件库、图表库、表单库等。
我在项目中需要一个表格组件,找了一圈没有特别满意的,最后自己封装了一个简单的。如果你需要很多第三方库,可能会遇到找不到的情况。
2. 调试工具不完善。
React有React DevTools,可以查看组件树、状态、props等。SolidJS的DevTools还比较初级,功能有限。调试的时候主要靠console.log,效率不如React。
3. 构建工具配置。
SolidJS推荐用Vite,配置很简单。但如果要和其他工具集成(比如ESLint、Prettier、TypeScript),需要自己配置,没有React那么多现成的脚手架。
七、性能方面的坑
1. 不要过早优化。
SolidJS的性能已经很好了,不需要过早优化。我刚开始用的时候,总是担心性能,到处用createMemo、splitProps,后来发现很多都是多余的,反而增加了代码复杂度。
我的建议是:先写正常的代码,遇到性能问题再优化。SolidJS的默认性能已经足够好了。
2. 注意信号的粒度。
SolidJS的细粒度更新是优势,但如果信号太粗,也会导致不必要的更新。比如一个大对象用一个信号存储,修改任何属性都会触发所有依赖更新。这时候应该用store(createStore),它支持细粒度的属性更新。
3. 避免在JSX中创建函数。
在JSX中内联创建函数,每次渲染都会创建新的函数引用。虽然SolidJS组件只渲染一次,但在For循环中,每个列表项都会创建函数,可能会影响性能。可以把函数提取到组件外面。
八、项目总结
用SolidJS做完这个项目,整体感受是:
优点:
- 性能确实好,页面很流畅
- 包体积小,首屏加载快
- API设计优雅,写起来很舒服
- TypeScript支持好
缺点:
- 生态不够丰富,很多库要自己写
- 调试工具不完善
- 学习曲线比预期陡,主要是思维模式的转变
- 社区资料少,遇到问题不容易搜到答案
如果让我再选一次,对于中小型项目,我还是会考虑SolidJS,尤其是对性能要求高的项目。但对于大型项目,可能还是会选React,因为生态和工具链更成熟。
九、给想用SolidJS的人的建议
- 先理解响应式原理。 SolidJS的核心是细粒度响应式,先搞懂它的原理,再写代码,会少踩很多坑。
- 忘掉React的思维模式。 虽然API像React,但运行机制完全不同。组件只执行一次,没有虚拟DOM,这些都要适应。
- 多读官方文档。 SolidJS的官方文档写得很好,而且是最权威的资料。遇到问题先查文档。
- 从小项目开始。 不要一上来就用SolidJS做大型项目,先做个小项目练练手,熟悉了再考虑大项目。
- 关注生态。 选技术栈之前,先看看你需要的库有没有SolidJS版本,避免做到一半发现没有合适的库。
十、写在最后
SolidJS是一个很有潜力的前端框架,它的性能和开发体验都很好。虽然生态还不够成熟,但随着1.0的发布,社区正在快速成长。
踩坑是学习新技术的必经之路,这些坑让我更深入地理解了SolidJS的设计理念。如果你也在考虑用SolidJS,希望我的经验能帮你少走一些弯路。
最后想说,框架只是工具,没有最好的框架,只有最适合的框架。根据项目需求和团队情况来选择,才是最理性的做法。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录