本节摘要:React 生态沉淀的模式清单——高阶组件、render props、容器与展示分离、受控组件、reducer 封装——各有既定的适用场景,而那些场景大多由"重渲染模型"制造。本节逐条盘点这些模式在 Solid 里的去向:保留的、变形的、直接退役的,并给出 children 函数与受控组件的对照代码。
「容器组件」在 React 语境里专指"把状态和请求收上来,通过 props 喂给纯展示组件"的那一层。它的存在理由是隔离:重渲染是常态,就把变化圈在容器层,让展示层尽量纯。Solid 里这条理由塌了——展示组件的绑定各自订阅,容器层不再承担"挡渲染"的职责,于是模式清单要重新审计。审计结果分三栏:保留、变形、退役。
| React 模式 | 解决的问题 | Solid 去向 | 说明 |
|---|---|---|---|
| 高阶组件 HOC | 复用逻辑、拦截渲染 | 退役 | 用组合原语替代;拦截渲染无从谈起 |
| render props | 把渲染权交给调用方 | 变形 | children 函数,且天然响应式 |
| 容器与展示分离 | 圈住重渲染范围 | 弱化 | 分层仍有组织价值,挡渲染动机消失 |
| Provider 包裹 | 跨层传值 | 保留 | 语义变为注入引用,见 5.1 |
| 受控组件 | 表单值单向可控 | 变形 | 信号直绑,双向但仍单向数据流 |
| reducer 加 dispatch | 集中化状态迁移 | 变形 | store 加 produce 平移,样板骤减 |
| 条件渲染三元链 | 分支结构 | 变形 | Show 与 Switch,见 4.3 |
表里"变形"栏占了一半,说明迁移不是推倒重来,而是同一意图换一套更短的实现。下面把两个最常用的变形展开。
React 里把渲染权下放的惯用写法是 render props——传一个函数当属性,组件在合适时机调用它。Solid 里直接用 children 函数,且由于组件只跑一次,这个函数内部读取的信号会自动成为响应式绑定:
// Solid:children 是函数,内部读取的资源变化会直达文本节点 function DataGate(props) { return ( <Suspense fallback={<Spin />}> {props.children} </Suspense> ); } // 使用方 <DataGate> <p>共 {count()} 条</p> </DataGate>
微妙之处在于:React 的 render props 函数随宿主渲染反复调用,Solid 的 children 函数求值一次、内部绑定终身有效。写出"每次都重新执行"的预期代码反而是错的。同理,Show 的 when 分支支持回调形式拿到收窄值——render props 的常见用途在这里被内建成了控制流的一部分。
React 的受控输入三板斧:value、onChange、setState。Solid 版本:
// React const [text, setText] = useState(""); <input value={text} onChange={e => setText(e.target.value)} /> // Solid const [text, setText] = createSignal(""); <input value={text()} onInput={e => setText(e.currentTarget.value)} />
形状依旧相近,机制走了一条更短的路:输入事件改信号,信号变化直写 value 属性绑定,全程没有"组件重渲染后再读新值"的回路。表单大时优势明显——React 每敲一个字符重跑一次表单组件,Solid 每敲一个字符只更新一个属性节点。双向绑定语法糖(Vue 的 v-model)Solid 没有提供,社区方案是一行组合原语包装,成本可控。
Redux 时代的"动作加 reducer"范式,核心诉求是状态迁移集中可测。迁移到 Solid:
// 原来:dispatch({type: "remove", id}) 进 reducer 返回新树 // 现在:store 加 produce,意图函数依然集中、依然可测 const remove = id => setState(produce(s => { const i = s.items.findIndex(it => it.id === id); if (i >= 0) s.items.splice(i, 1); }));
可测性没有损失——意图函数输入旧状态输出动作效果,测试照写。损失的是"时间旅行调试"这类依赖纯函数树的配套设施,Solid 生态里没有完全对位的工具;换来的是中间件链条、样板类型声明全部消失。团队要不要做这笔交换,取决于对调试工具的依赖深度,第 9 章的调试工具篇会给另一半信息。
💡 迁移代码库时别按"模式对模式"硬翻,按"问题对问题"重审:每个模式先问它在 React 里防的是什么,那个问题在 Solid 里是否还存在。存在则保留形态,不存在则用最短写法重表达。多数模式会瘦成一两行原语组合。
给一个二问判据。问一:同一个页面会不会出现多份实例?会(多标签组件、向导式分步表单、测试隔离),选 Context;不会(当前用户、购物车、主题),全局单例即可。问二:创建时机要不要晚于应用启动?要(依赖路由参数、依赖另一模块就绪),选 Context 让 Provider 控制时机。两问都答否,全局单例;任何一问答是,Context。这个判据比"看团队风格"可靠得多,写进 8.2 的评审清单完全够格。
动手迁移前,建议先做半小时"模式清点":随机抽旧代码库的十个组件文件,数一数五种模式各出现几次——HOC 包装、render props、Provider 嵌套层数、容器组件、reducer 分发。清点结果直接预测迁移工作量:HOC 多的库(老牌表单与高阶连接器风格)改写面最大;render props 居多的中等;Provider 与容器为主的最轻——后两者在 Solid 里几乎按原语义保留。清点还能提前暴露"为绕开重渲染而生"的怪异代码(层层 memo、手动 shouldComponentUpdate 的类组件遗迹),这些代码迁移时不是翻译而是删除,别让它们污染新代码库的评审基线。
状态与模式的迁移盘完,下一章进入结构化部分:路由与全栈框架,对照 React Router 与 Next.js。