就像是个有限状态机,在不同状态里变化。每个状态就像是个 Snapshot。当收到外来的输入时,比如人或计算机,就会进入下一个状态,通过比较两个状态下数据的 Snapshot 进行 re-render。尝试用这种思维构建应用,并将每个 State 看作是你页面的一种状态,便是声明式 UI 的构建方式。
使用 State 的原则是保持 read-only,也就是 immutable,任何时候传给 set 的都不该是原有对象,通常是修改后的副本。
无论何时都别忘了 State 是和 re-render 绑定的,只有在 re-render 时才会更新,不要将其用作可以迭代的对象。
保持 State 尽可能的简单,不要出现重复的部分,尽可能保证其是 flat 的。也尽量不要用 State 去修改 State,如果出现这种情况,最好想想能不能用一个 State + 对象存储。
关于树的 Snapshot。注意 Component 位置,避免重新渲染造成 state 丢失。React 只能“看到”树,代码逻辑比如返回的 JSX 不能改变 Component 本身。注意到“树”是否发生改变,取决于下一次 re-render。如果在 re-render 时,有个相同的 Component 替换了原先的,其 State 并不会发生改变。但如果在 re-render 时去除,并在下一个 re-render 时加回来,则原有的 Component 的 State 会在第一次去除时消失。你需要对比的是 re-render 前后树的差异。我们看到现在具有两个 Snapshot 分别是 State 和 树,注意区分二者。如果想要改变相同位置的 Component 具备不同的 State,可以使用 key prop。
注意到下面是有区别的:
{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}
{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}
不要尝试将 Component 嵌套。会导致 React 误认为是不同的 Component 导致 State 丢失。
useReducer
就像数组的 reducer,可以执行一组操作,进入下一个 State。你需要为每一组操作命名,使每个 State 的转变设计变得更加明确。避免在里面使用函数计算的结果来初始化值,可以使用第三个参数传入函数。
useState
如果初值由函数计算,应该传入函数本身,而不是结果。此外还有个怪东西,少见但可以看。
useContext
像依赖注入一样好用,你可以在任何层级注入其上层的 props,配合 reducer 使用可以将每个 Componet 变得更加简单。由于 Provider 的 value 变化时,其使用它(useContent)的组件会重新渲染,因此如果想要保证 value 在每次 Provider 重新渲染时不变,可以用 useMemo 包裹对象,用 useCallback 包裹函数。
useRef
用来创建一个不受 re-render 影响的包包。不要在每次 re-render 的时候修改它。使用 ref
props 可以用 DOM 操作,当你需要嵌套时使用 forwardRef
,ref
里支持写函数,其参数为 node。
useEffect
执行于 render 后。ref 拿到 DOM 是在 render 之后,所以一般的 DOM 操作或者其他操作都在其后的用户行为上,比如 Event,那如果不想通过 Event 获得,又想要在 render,也就是 commit 之后拿到数据,就得用 useEffect
,其在每次 commit 之后执行。建议是给一个 condition,只在需要时执行。被 useEffect
包裹的部分会产生 side-effect。或者说,当我们需要存放一些 impure code 的时候,useEffect
是一个不错的选择。
<aside>
💡 我觉得这里主要在于执行的顺序,虽然代码按顺序写在 Component 里面,但是其「拿到值」「更新值」的顺序是值得注意的。而这些过程应该就是所谓 React 的生命周期。主要在于从当前 render 到下一个 render 的周期。很明显 useEffect
在这个中间。所以他们被称为 Hooks。
</aside>
useEffect
是在 commit 之后,尽量不要将可以预先处理的数据放在里面 set,不然会 re-render。useEffectEvent
是一个新的官方 hook,我觉得就是用来解决有些情况不得不抑制依赖项检查,来规避某些不应该被响应的响应式变量(会发生变化的变量,如 State 或者 props)而增加的 hook,这些变量仍然可以在每个 render 周期获取到最新值,但不再触发新的动作(也就是不再参与响应)。这个 hook 只能在 useEffect
里调用,并且需要声明在他旁边,避免传递。注意从外部取得的参数和从 useEffect
中传入的参数的区别,前者拿的总是最新的,后者可能会拿到之前响应的参数(例如使用延迟函数(最后一个挑战))。关键点:如果你想读取最新值而不“反应”它,请从 Effect 中提取出一个 EffectEvent。同时这个「值」一般是别的状态去“反应”的,通常是 state,如果不是 state 那应该直接写成静态的 ref,此时则无需 EffectEvent。useEffect
只干一件事,防止其由于别的响应式变量触发其他不应该触发的事,即保持独立检测。useMemo
很像 useEffect
但是不可用其 side-effect,所以叫 Memo,因为其在每次 rendering 中执行,主要是避免繁杂冗余且参数不常变化的任务执行多次。
useCallback
可以和 useMemo
一起用,当 dependency 是一个 function 的时候,如果把 function 写在 component 里面,那么每次 Render 的时候都会重新渲染,这种重新渲染会导致 function 本身变成响应的(实际上参数没变),此时可以用一个 useCallback
包裹(将 func 脱离响应,缩小响应范围到其参数上),并把它那些 primitive 的 parameter 作为 dependency。我觉得这个情况在于无法将这个 function 包裹在 memo 里,而是作为参数传进 memo 里才会出现的情况,同理应该也适用于同样需要依赖的 useEffect
。但是对于 useEffect
,优先将函数声明直接包裹在里面是更好的原则。
useDeferredValue
配合 suspense lazy 用的,如果 suspense 包裹的内容慢了,会延迟提交新的 commit,react 会在后台使用新的数据 render,等 render 好了再一起呈现,旧的数据则用于保持上一次的状态,这个时候 fallback 就不会显示了(除了 init render),有点像可以解决 debounce 的感觉。与防抖或节流不同,useDeferredValue
不需要选择任何固定延迟时间。如果用户的设备很快(比如性能强劲的笔记本电脑),延迟的重渲染几乎会立即发生并且不会被察觉。如果用户的设备较慢,那么列表会相应地“滞后”于输入,滞后的程度与设备的速度有关。在处理重渲染上的“防抖节流”时会很有用。而如果面临网络请求,应该采用传统方式。一般用于 props,降低渲染子树优先级。
useId
用来生成 html attribute id 的,用它而不用随机数或者 incrementing counter 的主要原因是为了保证 server 和 client 一致,我在构建新的 Blog 时使用了随机的 theme number,它会在 console 报不一致问题。
useImperativeHandle
为 ref 添加一个 Proxy,包括自定义和有限暴露。
useLayoutEffect
在 reprint 前动态计算 element 属性的。useLayoutEffect
内部的代码和所有计划的状态更新阻塞了浏览器重新绘制屏幕。如果过度使用,这会使你的应用程序变慢。React 保证了 useLayoutEffect
中的代码以及其中任何计划的状态更新都会在浏览器重新绘制屏幕之前得到处理。
useSyncExternalStore
订阅同步,需要注册两个参数,包括 subscribe 和 getSnapshot func,当外部应用需要更新时,外部来源 store 可调用 subscribe 的唯一参数 callback 回调来更新,也就是这个 callback 调用会触发「响应更新」。
useTransition
updateState 且不会 block ui,用 start 包裹相应的 setState 即可。相比 suspense,用户可以继续操作组件,当然它不会等被 suspense 包裹的部分。另外注意不要将需要同步显示的 event value 放进去,那种情况应用前面的 useDeferredValue
。不要将异步的函数放在里面。此外,它只会将有关 State 的函数异步延后执行。
还可以用来处理 Error Boundary。同样降低渲染优先级,用于 State。