React hooks(一):useState 的几个基础用法和进阶技巧

学习react Hooks的笔记

自从 React v16.8 版本以来,React Hooks 为我们提供了全新的编写和思考 React 组件的方式。不仅管理状态和生命周期变得更简洁、更强大,Hooks 还引入了并发渲染和其他高级功能。本系列文章将详细探讨每一个 Hook,从基础到高级。首篇,我们将探讨最常用的 hook——useState。


useState 的基本用法

useState主要用于给组件添加状态变量。注意,我们只能在组件的顶层或自定义的 Hooks 中调用useState。

初始化状态

  1. 基础定义
1
const [age, setAge] = useState(42);
  1. 懒初始化

对于需要计算得到的初始状态,可以使用函数传递给useState。这样,函数只在初次渲染时执行,而非每次渲染。

1
const [todos, setTodos] = useState(createInitialTodos); // 注意:传递函数本身,非执行结果

更新状态

直接更新vs函数式更新
大部分情况,直接更新状态即可:

1
setAge(newState);

但当新状态依赖于前一个状态时,推荐使用函数式更新。这确保了更新准确性,特别是在并发模式下。

1
setState(prevState => prevState + 1);
  1. 以下两个例子展示函数式更新的重要性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 示例1: 使用函数式更新
import { useState } from 'react';

export default function Counter() {
const [age, setAge] = useState(42);

function increment() {
setAge(a => a + 1); // 函数式更新
}

return (
<>
<h1>Your age: {age}</h1>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
</>
);
}
// 结果:点击 +3 时,age 更新为 45。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 示例2: 使用直接更新
import { useState } from 'react';

export default function Counter() {
const [age, setAge] = useState(42);
function increment() {
setAge(age + 1); // 直接更新
}
return (
<>
<h1>Your age: {age}</h1>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
</>
);
}
// 结果:点击 +3 时,可能只更新为 43。
在 React 中,当你直接更新状态,每次调用 setAge(age + 1) 时,如果这些调用在同一个渲染周期中执行,它们可能都会使用相同的初始状态(即在函数组件体的开始时捕获的状态值)。这是因为 React 的状态更新可能是异步的,特别是在批量处理或并发模式下,状态的更新可能还没有被应用,就已经触发了下一个更新。

在你的第二个示例中,每次调用 increment() 都直接使用了组件当前渲染周期中的 age 值,而这个值在连续调用 increment 时不会改变(始终为 42)。因此,尽管 increment() 被调用了三次,每次都是基于 42 的状态值来计算的,只有一次有效更新将被执行,使得 age 最终只增加 1,变为 43。
相反,使用函数式更新(如 setAge(a => a + 1)),每次更新都会接收到最新的状态作为参数,即使这些更新是在同一个渲染周期内触发的。这种方式确保了每次调用都基于前一次更新后的最新状态进行,从而正确地将 age 增加三次,达到预期的 45。

在并发模式下,因为状态更新可能是异步的,直接使用状态值进行更新时可能会遇到竞态条件,这时函数式更新显得尤为重要。函数式更新通过使用最新的状态值来保证状态更新的连贯性和正确性,尤其是在多次状态更新需要被顺序应用时。

对象与数组的更新

对象和数组的更新需要创建新的引用,而不是直接修改原状态。

1
2
3
4
5
6
7
setForm({
...form,
name: e.target.value // 更新这个属性
});

// 错误示例:
// form.name = e.target.value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setTodos([
...todos,
{
id: nextId++,
title: title,
done: false
}
]);

// 错误示例
// todos.push({
// id: nextId++,
// title: title,
// done: false
// });
// setTodos(todos);

函数的更新

把函数存储到state里是很少见的做法,但某些情况下,我们有可能需要这么做。

先看一个错误的示例:

1
2
3
4
5
const [fn, setFn] = useState(someFunction);

function handleClick() {
setFn(someOtherFunction);
}

根据上文,我们知道这样的用法是把函数的返回值存储或更新到状态中,并不是把函数存储到状态中。

如果你想在状态中存储一个函数,你需要使用一个箭头函数来“包裹”它。这是正确的做法:

1
2
3
4
5
const [fn, setFn] = useState(() => someFunction);

function handleClick() {
setFn(() => someOtherFunction);
}

那么我们什么时候会需要这样使用?这里介绍一些可能的场景:

  1. 可配置的行为: 你可能有一个组件,它的行为可以由父组件进行配置。在这种情况下,你可以将函数作为状态存储,以便在组件的生命周期中更改或更新它。
  2. 动态创建的函数: 在某些情况下,你可能需要基于组件的某些属性或状态动态创建函数。将这些函数存储为状态可以确保你只在必要时重新创建它们。
  3. 回调和外部交互: 如果你的组件与外部系统交互,并且需要提供回调函数,你可能希望在状态中存储这些回调函数,以便在适当的时候更改或更新它们。
  4. 延迟执行的函数: 在某些情况下,你可能想要在将来的某个时间点执行函数(例如,使用setTimeout)。将函数存储为状态可以确保即使组件的其他部分发生变化,你也可以访问到最初的函数引用。
  5. 与第三方库的集成: 有些第三方库可能要求你提供并在后续更改函数。在这种情况下,将函数作为状态存储可能会更加方便。
在 React 开发中,通常我们不会把函数直接存储在状态(state)中,因为函数通常作为固定的逻辑存在而不需要存储为变化的状态。然而,有一些特定场景下,存储函数到状态中可能是有益的或必要的。这些场景通常涉及到动态行为的配置、动态函数的创建、对外部交互的处理、计划执行任务,或者是与某些需要动态改变函数的第三方库集成。下面我会详细解释每一个场景:
  1. 可配置的行为:

    • 在某些情况下,组件的行为可能需要根据父组件的配置来动态改变。例如,如果你有一个通用的按钮组件,其点击行为(如打开模态框、提交表单等)可以通过父组件传入的函数来定义。这时,你可以在状态中存储这个行为函数,并在需要时更新它,以改变按钮的功能。
  2. 动态创建的函数:

    • 如果函数的行为需要基于组件的某些属性或状态动态生成,例如一个根据用户角色动态生成的权限验证函数,那么将这些函数存储为状态可以避免在每次渲染时都重新创建它们,优化性能。
  3. 回调和外部交互:

    • 当组件需要与外部系统(如 Web API、浏览器API等)交互时,这些外部系统可能需要回调函数来处理异步事件或数据。将这些回调函数存储在状态中可以在它们需要更新时,方便地进行替换。
  4. 延迟执行的函数:

    • 在使用 setTimeoutsetInterval 等时,你可能想在未来某个时间点执行某些特定的逻辑。将这个逻辑封装在一个函数中并将其保存在状态里,可以确保即使在组件更新后,你依然可以访问到最初的函数引用,保证执行的一致性。
  5. 与第三方库的集成:

    • 有些第三方库可能要求提供函数并在之后的操作中可能需要更改这些函数。例如,一个事件监听库可能允许你在初始化时提供一个回调函数,并在需要时通过某种方法更新它。在这种情况下,将函数作为状态存储,可以使这一过程更加灵活和可控。

总之,虽然通常不推荐将函数存储在状态中,但在需要高度动态和可配置的行为时,这种方法可以提供额外的灵活性和控制能力。

总结

在这篇文章中,我们深入探讨了 React 的useStateHook,从它的基础用法到一些进阶技巧。掌握好useState是走向 React 高手之路的关键一步。在未来的文章中,我们还将继续探讨其他的 Hooks。

参考文档

掘金
精读React hooks