SolidJS中For与Index组件的渲染差异及更新机制疑问
SolidJS For与Index组件的行为疑问
一、Index组件渲染结果与预期不符的问题
测试中发现,用Index组件渲染数组并更新后,实际渲染结果和预期不一致。
测试代码
import { render } from 'solid-js/web'; import { createSignal, For, Index } from 'solid-js'; function ForCats() { const [cats, setCats] = createSignal([ 'Keyboard Cat', 'Maru', 'Henri The Existential Cat' ]); setTimeout(() => setCats(['Keyboard Cat', 'Keyboard Cat', 'Keyboard Cat', 'New Cat']), 2000) return ( <ul> <For each={cats()}>{(name, i) => { console.log(`For: rendered ${i()}:${name} whole cat`); return <li><a target="_blank" href="">1: {name}</a></li> }}</For> </ul> ); } function IndexCats() { const [cats, setCats] = createSignal([ 'Keyboard Cat', 'Maru', 'Henri The Existential Cat' ]); setTimeout(() => setCats(['Keyboard Cat', 'Keyboard Cat', 'Keyboard Cat', 'New Cat']), 2000) return ( <ul> <Index each={cats()}>{(name, i) => { console.log(`Index: rendered ${i}:${name()} whole cat`); return <li><a target="_blank" href="">1: {name()}</a></li> }}</Index> </ul> ); } render(() => <> <ForCats /> <IndexCats/></>, document.getElementById('app'))
运行日志
For: rendered 0:Keyboard Cat whole cat For: rendered 1:Maru whole cat For: rendered 2:Henri The Existential Cat whole cat Index: rendered 0:Keyboard Cat whole cat Index: rendered 1:Maru whole cat Index: rendered 2:Henri The Existential Cat whole cat For: rendered 1:Keyboard Cat whole cat For: rendered 2:Keyboard Cat whole cat For: rendered 3:New Cat whole cat Index: rendered 3:New Cat whole cat
预期与实际结果
- 预期结果:Index组件保留原有的前三个元素内容(Keyboard Cat、Maru、Henri),新增第四个元素New Cat
- 实际结果:Index组件前三个元素全部变为Keyboard Cat,和For组件渲染结果一致
原因解析
SolidJS中For和Index的核心差异在于元素追踪逻辑:
- For组件:基于元素的身份追踪,通过引用或值的唯一性判断是否需要重新创建元素。当数组前三个元素替换为相同的"Keyboard Cat"时,For会认为索引1、2的元素身份发生变化,因此重新执行回调生成新元素。
- Index组件:基于数组的索引位置追踪,只要索引存在,就复用该位置已创建的元素,仅更新元素内部依赖的响应式值。更新后的数组中,索引0、1、2依然存在,只是对应的值变成了"Keyboard Cat",因此Index会直接更新这三个位置元素的内容,不会保留原来的Maru和Henri,最终渲染结果和For一致。
二、IndexComp点击更新界面正常但无console.log输出的问题
测试中点击IndexComp的列表项时,界面能正确更新状态,但回调函数中的console.log没有二次输出。
测试代码
import { For, createSignal, Index } from "solid-js"; const initialTodos = [ { content: "TODO A", complete: true }, { content: "TODO B", complete: true }, { content: "TODO C", complete: true }, ]; function ForComp() { const [todos, setTodos] = createSignal(initialTodos); return ( <For each={todos()}> {(todo, todoIdx) => { console.log("todo", todo, "todoIdx", todoIdx); return ( <div> <h5 style={{ cursor: "pointer" }} onClick={() => { const newTodos = [...todos()]; newTodos[todoIdx()] = { ...newTodos[todoIdx()], complete: !newTodos[todoIdx()].complete, }; setTodos(newTodos); }} > {todo.content}:{todo.complete.toString()} </h5> </div> ); }} </For> ); } function IndexComp() { const [todos, setTodos] = createSignal(initialTodos); return ( <div> <Index each={todos()}> {(todo, todoIdx) => { console.log("todo", todo, "todoIdx", todoIdx); return ( <div> <h5 style={{ cursor: "pointer" }} onClick={() => { const newTodos = [...todos()]; newTodos[todoIdx] = { ...newTodos[todoIdx], complete: !newTodos[todoIdx].complete, }; setTodos(newTodos); }} > {todo().content}:{todo().complete.toString()} </h5> </div> ); }} </Index> </div> ); } function App() { return ( <> <h1>For</h1> <ForComp /> <hr /> <h1>Index</h1> <IndexComp /> </> ); } export default App;
原因解析
SolidJS采用细粒度响应式更新,Index组件的回调函数仅在初始化时为每个索引位置执行一次,用于创建对应的DOM元素。当点击列表项更新数组中某个索引的值时:
- Index组件检测到对应索引位置的响应式值(
todo())发生变化 - 直接更新该DOM元素内部绑定的响应式内容(
todo().content、todo().complete) - 不会重新执行回调函数,因此
console.log不会再次触发
这种机制保证了更新的高效性,避免不必要的组件重建,仅更新变化的部分。
内容的提问来源于stack exchange,提问作者seongkuk han
相关产品推荐
相关产品推荐

