欢迎光临散文网 会员登陆 & 注册

React手册 Hooks 之 useState

2023-05-06 18:01 作者:海里我最大  | 我要投稿

描述

    React 官网对 useState 的描述原文

useState is a React Hook that lets you add a state variable to your component.

useState 是一个 React Hook,可让您向组件添加状态变量。

    这是一个非常常用且基础的 Hook, 组件的渲染, 操作, 功能, 样式, 大多数时候都与之有关.

    

    接口定义:


场景

    当 React 渲染一个函数组件的时候, 会执行这个函数, 假如直接在函数中定义局部变量, 那么就会产生两个问题: 

  1. 每次渲染组件的时候, 这个变量都会被重新声明;

  2. 如果修改了局部变量, 组件感知不到变化从而无法触发重新渲染;

   因此为了让组件能有状态, 同时保持组件是纯函数,所以需要 useState 创建和操作状态.


参数

  • initialState: (() => any) | any

    任意类型的初始化状态值或一个返回任意类型的初始化状态值的无参纯函数, initialState 能够接受任意类型的参数, 不过如果是函数, 会有特殊效果

返回

    返回一个特定格式的数组 [any, Dispatch], 其中第一项是当前状态, 它的值与你传入的初始值相等, 第二项是修改这个状态并触发重新渲染 set 函数.

    set 函数, 也就是 Dispatch, 也有两种传参方式, 可以传入要设置的下一个状态, 或基于当前状态计算下一个状态的函数, set 函数没有返回值.

    set 函数传入的更新方法应该是纯函数, 它将会被 React 维护进该状态的更新队列, 在下一次更新时, 会依次执行更新队列中的函数, 前一次更新函数的返回值, 将会当作参数传给后一个更新函数.


用法1

    给函数组件增加状态, 惯例是按照像[ something, setSomething ] 这样使用数组解构的方式命名状态.

    使用 setName('Robin'); 方式更新状态, 但是 set 方法只会影响下一次 useState 渲染调用时返回的结果, 并不会更改已经执行代码的当前状态.


用法2

    根据之前的状态更新状态, 假如 age 是 1, 然后我们连续调用三次 setAge(age + 1)

    因为 set 函数不会改变已经运行中的状态, 要解决这个问题, 可以给 set 函数传入一个更新函数: 

    当 set 函数收到一个更新函数时, React 会把更新函数存放进一个更新队列, 在下一次渲染时, React 会以相同的顺序调用这些更新函数, 并把最终值存储为当前状态.

    按照惯例, 更新函数中的参数(也叫挂起状态 pending state), 我们认为是抽象的, 命名的时候需要和真实的状态做出区分, 所以一般使用状态变量的首个字母来作为参数的命名, 比如使用 a 来作为 age 挂起状态参数, 或者其他诸如 prevAge 等, 你觉得合适准确的命名方式.


用法3

    更新状态中的对象和数组, 在 useState 中, 可以传入对象和数组, 但是需要注意的是, 作为状态的对象应该是只读的, 你应该替换它, 而不是直接修改, 假如有一个 form 对象作为状态:

    . . . 的解构赋值是一种操作, 只会复制第一层, 所以速度非常快, 同时也意味着你如果是多层嵌套的对象, 就不得不多次使用这种操作.

    这样的操作很繁琐, 但是有效, 同时 React 给出了两种处理多级对象的方案:

一种是避免对象的嵌套, 使用对象引用赋值以上面的对象为例

    artworkperson 可以被分成两个独立对象分开维护, 他们之间的关系可以使用引用赋值的方式建立.


第二种是使用 Immer 库

    尝试 Immer:

    使用 useImmer 替换 useState


用法4

    避免重新创建初始状态, 如果在 useState 中的初始值是通过函数计算得出的, 那么就需要把函数结果传给 useState, 像这样:

    尽管 createState() 函数的返回值只用做初始化组件时使用, 但是在实际渲染中, 每次渲染 List 组件时, createState() 都会被执行, 这会造成浪费.

    要避免这种浪费, 需要像下面这个样改写

     这里没有吧 createState 的执行结果传给 useState, 而是把 createState 函数本身当作初始化函数传给 useState, 这样 React 只会在初始化阶段调用 createState 函数, 在实际执行过程中, 两种写法效果一样, 但是传递函数本身的方式性能更好.


用法5

    重制组件状态, 使用 useState 来控制 key, 当你在渲染列表时, 你经常会用到 key, 不过它还有另外一个功能, 那就是当你主动修改 key 时, React 会重新创建组件, 组件内的状态也会被重置.

    当 version 发生变化时, Form 组件就会被重新创建, 从而重置状态.


用法6

    状态修改的历史, 也就是获取本次状态的前一次状态, 这是一个少数场景, 当你正常使用 set 函数的时候, 你很轻易的可以拿到当前状态和下一次状态, 但是如果状态的变更来自外部, 通过 props 传入进来的, 那就无法获取状态变更前的值.

    解决的方式就是在接收 props 的组件内部, 使用 useState 对状态的变化趋势进行存储:

    在判断 prevCount !== count 中执行 setPrevCount(count), 这一步是必须的, 通常在组件渲染过成中直接调用 set 函数都是不允许的, 会收到一个 Too many re-renders. 的错误, 所以需要使用判断控制.

    观察打印信息之后, 会发现一个现象, 组件 CountLabel 内的日志打印了两次, 而其子组件 Children 内的日志只打印了一次, 为什么父组件渲染两次, 而子组件只渲染一次? 因为父组件的两次渲染, 有一次是在渲染过成中调用 set 函数引起的, React 处理这种渲染的时候, 会在函数执行完毕之后并在开始渲染之前, 再次重新渲染组件, 这样就会避免子组件渲染两次, 头一次的计算结果将会被丢弃, 你也可以在头一次函数执行的过程中提前 return, 尽快开始重新渲染.

    

用法7

    设置函数作为一个状态, 如果你想把一个函数作为状态进行存储:

    你会发现 someFunction 会被执行, 这和预期不符, 因为 useState 会认为你传入的是一个初始化函数, 将其执行, 并将返回值进行存储, set 函数同样认为 someOtherFunction 是一个计算下一状态的函数, 将其执行, 并把返回值作为状态更新, 正确的将函数作为状态的写法如下:

    将这个函数本身作为返回值, 放在一个函数中, 传给 useStateset 函数.


总结

  • useState 返回一个数组, [count, setCount];

  • useState 可以传入初始化函数, 只在初始化调用;

  • set 函数可以传入一个函数, 参数是当前的挂起状态, 并将返回值作为新状态更新;

  • 当状态是对象时, 需要使用 . . . 解构赋值, 对状态进行直接替换, 或使用 Immer 库;

  • 使用 useState 控制组件的 key 时, 可以让组件重新创建, 从而重置状态;

  • 在组件渲染过程中调用 set 函数是不允许的, 需要使用判断控制, 此时组件函数会执行两次,  第一次的结果会被丢弃, 并且都是在真实渲染之前, 防止子组件的多余渲染;

  • 把函数作为状态时, 需要使用另一个函数将其返回: useState(() => someFunction), 否则会被当作初始化函数使用.


React手册 Hooks 之 useState的评论 (共 条)

分享到微博请遵守国家法律