useState
使用
initalState
useState() 接受一个初始值,React 会在第一次渲染组件时保存它,而在之后的重渲中会直接忽略。
使用一个函数来获得初始值
可以使用一个函数来获取初始值,如 getInitValue()
。
上面的做法看上去十分直觉,但是会引发问题:虽然在 re-render 时,initalState 会被忽略,但是 getInitValue()
这个函数会被再次调用。(尽管上面的例子中的 getInitValue()
并不会带来多大开销)
正确做法是:不在 useState() 中直接调用用这个函数,而是直接把 初始化函数
传递给 useState()。
useState() 可以接受一个函数作为参数,如果你传入了一个初始化函数,React 只会在首次渲染中调用它。
在严格模式下,React 会将这些函数调用两遍,来检测他们是否是纯函数。如果是的话,调用两次也不会也不应该产生问题。
根据当前 state 更新 newState
一个简单的方法:
但是,如果调用上面的方法多次,会得到以外的结果:
通过 updater 函数来更新
setState()
可以接受一个 updater 函数:
updater 函数必须是 pure 的。
只能接受一个参数,即当前的 state。
需要返回新的 state。
🙋 是不是所有根据当前 state 更新 newState 的操作都需要 updater ?
其实不是必要的,但是如果你为了保证一致性或者正确性,也可以使用它。
或者,当你在同一事件内要多次更新某一 state,考虑使用它。
将上面的函数调用三次,可以得到想要的效果。
上面的三次 setCount 会被推入到一个队列中,会在下次 re-render 时,依次调用。
三个 updater 推入到队列中不代表组件要被重新渲染三次!而是,在下次渲染的时候,链式的调用,从而得到最后的正确值。
下面的代码调用了三次 updater:
setState() 的坑
setState()
方法只会更新你下一次 re-render 时候的 state。而在 setState()
之后继续读取 state,仍然是当前的 state。
这也就是为什么,调用三次 setState()
并不能达到你想要的效果。因为尽管调用了三次,但是每次读到的 state 并不是更新后的!
setState() 后会使用 Object.is 来进行比较,如果比较结果相同,则会跳过不执行 re-render。
React 会将一系列 states 更新打包,他会等到所有事件回调完成后,并且所有 setState() 方法都运行后,才会执行 re-render。
setState() 参数
setState()
接受一个值或者一个 updater 函数。
如果接受一个值,这个值是可以任意的 type。但是仔细体会这个 nextState
,这个值在传入进去的时候,就已经确定好了!这也就是为什么调用三遍 setCount(count + 1)
并没有效果,因为你每次实际上调用的都是 setCount(1)
。
setState() 返回
无返回值
更新对象或数组
更新对象
利用扩展运算符来更新新的对象
⚠️
...
运算符是浅拷贝,需要保证所有属性都是值类型
当你的对象层级大于一层时,需要谨慎处理。假设需要更新 person.artwork.city
属性:
使用 Immer 来更新对象
使用 useImmer() 来替代 useState(),他们在使用起来没太大区别;唯一在更新对象时,可以使用如下的语法:
修改对象时,就好像它是 mutable 的。
https://github.com/immerjs/use-immer
更新数组
和更新对象类似,你也应该把数组当做 immutable 的。因此,诸如 arr[0] = 0
,push()
,pop()
操作都是不允许的。
相反,一些可以返回新数组的 API 是可以使用的,如:map()
,filter()
。
参考下面的表格,可以将左边的操作转换为右边的:
adding
push
, unshift
concat
, [...arr]
removing
pop
, shift
, splice
filter
, slice
replacing
splice
, arr[i] = ...
map
sorting
reverse
, sort
先复制一个副本在进行原地操作
⚠️ 数组的
...
操作也存在浅拷贝问题,使用之前确保所有元素都是值类型
此外,还可以通过 Immer 来完成更新数组,Immer 不仅可以直接执行 arr[0] = 0
这样的操作,还可以使用 push()
,pop()
等直接在数组原位置修改的 API。
其他应用
通过 key 来重置 state
你可以用一个 state 来管理某一元素的 key,然后当你修改这个 state 的时候,key 也发生了变化,会触发渲染新的组件。
保存上一次更新的信息
你可以需要在组件发生渲染时,更新 state 信息。但在多数情况下,你都不需要这么做。
比如,你需要在 count prop 发生改变时,来判断相较于上一个 count,新的 count 是增加了,还是减少。
这时,就可以利用 state 来保存上一次信息。
可以把 state 理解成一个 Ref,他保存在组件之外,因此即便组件重渲了,这个数据还存在。
使用 useState() 来保存一个函数
如果你直接把函数传入 useState(),或者 setState() 中,React 会把它们分别当做初始化函数和 updater 。
所以正确做法是,使用闭包来返回一个高阶函数:
Immutable
state 是 immutable 的,注意他是用 const 来解构 useState() 的返回值的。
所以对于值类型来说,你肯定是不能直接改变的,因为 const 不允许你修改他。
而对于引用类型,虽然在语法上你是可以修改的,但是你也应该把它当成 immutable 的。
无论如何,都应该使用 setState() 来更新,而不是直接操作它们!
为什么 state 要设计成 immutable 的?
方便 Debug:每一次 render 的 state 都是不变的,你可以通过如
console.log()
来快速的 Debug。方便优化:React 根据 state 是否发生变化来决定是否执行 re-render。对于 object 类型的 state,利用 immutable 的话就可以确保:如果 object 的引用没有发生变化,那么 object 的内容也一定不会变化。这样 React 只需要比较引用值,而不需要递归地比较对象内容。
Last updated