原文:Using the State Hook

Hooks是React 16.8的新特性。这些特性让你不必专门写class组件,来使用state和其他React特性。

介绍章节中使用了这个例子来熟悉Hooks:

  1. import React, { useState } from 'react';
  2. function Example() {
  3. // Declare a new state variable, which we'll call "count"
  4. const [count, setCount] = useState(0);
  5. return (
  6. <div>
  7. <p>You clicked {count} times</p>
  8. <button onClick={() => setCount(count + 1)}>
  9. Click me
  10. </button>
  11. </div>
  12. );
  13. }

我们将通过对比一个等价的class组件例子来开始学习Hooks。

相等的class例子

  1. class Example extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. count: 0
  6. };
  7. }
  8. render() {
  9. return (
  10. <div>
  11. <p>You clicked {this.state.count} times</p>
  12. <button onClick={() => this.setState({ count: this.state.count + 1 })}>
  13. Click me
  14. </button>
  15. </div>
  16. );
  17. }
  18. }

状态开始于{ count : 0 },并且我们通过点击按钮回调this.setState()来设置state.count。我们在这一讲中将持续使用这个代码片段。
注意:

你可能会想知道为啥要用计数器而不是一个实际点的例子。因为这样能帮助我们还停留在最初接触Hooks这个阶段的时候,更加关注API本身。

Hooks和函数组件

我们印象中,函数组件是这样:

  1. const Example = (props) => {
  2. // You can use Hooks here!
  3. return <div />;
  4. }

或者:

  1. function Example(props) {
  2. // You can use Hooks here!
  3. return <div />;
  4. }

你先前可能知道这些是所谓“无状态组件”。我们现在就是在介绍把React的状态在这些组件里面用起来的能力。所以我们更愿意称它们是“函数组件”。
Hooks不能在class组件中工作。不过你可以用它们来代替编写一个class组件。

什么是Hook?

我们的新例子开始于从React中import一个useState的Hook:

  1. import React, { useState } from 'react';
  2. function Example() {
  3. // ...
  4. }

什么是Hook?Hook是一个特殊的函数让你“钩进来”React的特性。举例说,useState是一个Hook,它使你为函数组件添加了React状态。很快我们将会学习其他Hook。
我们什么时候使用Hook?如果你写了一个函数组件并意识到你需要为它增加一些状态,之前你需要把它变成class,现在你在组件里面使用Hook。我们马上就那么做。

注意: 还有些特殊的关于你在函数组件里可以或者不可以的规则,在Hooks的规则一讲中会讲到。

声明一个状态变量

在class组件中,我们初始化count为0是通过在constructor函数中设置this.state的值:{ count : 0 }:

  1. class Example extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. count: 0
  6. };
  7. }

在函数组件,我们没有this,所以我们不能访问this.state。取而代之的是,可以把useState的Hook直接到我们的组件中使用。

  1. import React, { useState } from 'react';
  2. function Example() {
  3. // Declare a new state variable, which we'll call "count"
  4. const [count, setCount] = useState(0);

调用useState后做了啥?它声明了“状态变量”。我们的变量叫count,但是我们也能把它叫做其他名字,像banana。这是一种在函数调用间保留一些值的方式——useState是用来使用与class组件中this.state能力一模一样的新方式。正常情况下,当函数退出后,变量会消失。但是状态变量能被React保存。
我们传递给useState的参数是什么?useState这个Hook有着唯一的参数,就是初始状态值。不像在函数组件中那样,这里的值不必是一个对象。我们根据需求,能选择储存一个数字,或者字符串等。在我们的例子中,我们仅仅想要一个数字来表示用户点击次数,所以,把0作为初始状态值(假如我们想存储两个不同的值,那么就调用useState()两次)。
useState返回了啥?它返回了一组值:当前的状态和一个用来改变此值的函数。这就是为什么我们写道:const [count, setCount] = useState(0);这就像是class组件中的this.state.count和this.setState。要是你不熟悉我们的语法,我们会在本讲后面回顾它。
现在我们知道了useState的工作,那么我们的例子应该更加有意义:

  1. import React, { useState } from 'react';
  2. function Example() {
  3. // Declare a new state variable, which we'll call "count"
  4. const [count, setCount] = useState(0);

我们声明了状态变量count,初始化它为0.React将会在每次渲染间记住它当前值,并把最新的值返回给我们的函数。如果我们想更新当前的值count,我们调用setCount。

注意: 你也许想知道:为什么useState不以creatState来命名? “Create”不是更精确是因为状态是仅在第一次渲染被创建。到了下一次渲染,useState会给我们当前的状态值。否则那就不是状态了。这也是为什么Hook总是以use开头的一个原因。我们会在“Hooks的规则”一讲中学习它们。

读取状态

当我们在class组件中展示当前计数器值,我们读取this.state.count:

  1. <p>You clicked {this.state.count} times</p>

在函数中,我们直接用count:

  1. <p>You clicked {count} times</p>

更新状态

class组件中,我们调用this.setState()来更新状态:

  1. <button onClick={() => this.setState({ count: this.state.count + 1 })}>
  2. Click me
  3. </button>

在函数中,我们已经有了setCount和count变量了,所以我们不需要this:

  1. <button onClick={() => setCount(count + 1)}>
  2. Click me
  3. </button>

总结

让我们逐行来总结所学并检测我们的理解:

  1. 1: import React, { useState } from 'react';
  2. 2:
  3. 3: function Example() {
  4. 4: const [count, setCount] = useState(0);
  5. 5:
  6. 6: return (
  7. 7: <div>
  8. 8: <p>You clicked {count} times</p>
  9. 9: <button onClick={() => setCount(count + 1)}>
  10. 10: Click me
  11. 11: </button>
  12. 12: </div>
  13. 13: );
  14. 14: }
  • Line1:我们引入了useState这个Hook,它让我们在函数组件中保持本地状态;

  • Line4:在组件里面我们通过调用useState来声明了新的状态变量。它返回了一组值,我们把这组值命名。我们把变量叫做count是因为它的含意是按钮的点击次数。我们通过把0作为useState的参数来初始化变量为0。第二个返回项自身就是一个函数,它使得我们可以更新count,所以我们命名它为setCount。

  • Line9:当用户点击,我们就调用setCount。React接着就会重新渲染组件,把新的count值传给它。

在最初,看上去有很多要点。别急!要是上面的解释把你整迷糊了,就再一次看看上面的代码,并尝试从头读到尾。我们保证一旦你“忘记”状态在class组件中的工作方式,并用全新的眼光看待这些代码,他就会有点意思。

提示:方括号是啥意思?

你可能注意到我们声明变量时用到的方括号:

  1. const [count, setCount] = useState(0);

左边的名字可不是React的API。你可以命名自己的状态变量:

  1. const [fruit, setFruit] = useState('banana');

这种JavaScript语法是“数组结构(Array Destructuring)”。我们有两个新变量fruit很setFruit,fruit作为useState返回数组的第一个值,setFruit是第二个。这些完全与下面代码等价:

  1. var fruitStateVariable = useState('banana'); // Returns a pair
  2. var fruit = fruitStateVariable[0]; // First item in a pair
  3. var setFruit = fruitStateVariable[1]; // Second item in a pair

当我们用useState声明状态变量的时候,它返回一对——一个两个元素的数组。第一个元素是当前状态值,第二个原始是更新状态的函数。使用下标[0]和[1]来访问它们有点迷惑,因为它们是有确切意义的。这就是为啥子要用数组结构来代替了。

注意: 你也许好奇React怎么知道那个组件的useState是对应的,因为我们也没有传“this”这类东西给React,我们将会回答这个问题以及其他问题在后续讲章“FAQ”

提示:使用多个状态变量

将状态变量声明为一对[something,setsomething]也很方便,因为如果你想使用不只一个变量,它使得不同的变量有不同名称:

  1. function ExampleWithManyStates() {
  2. // Declare multiple state variables!
  3. const [age, setAge] = useState(42);
  4. const [fruit, setFruit] = useState('banana');
  5. const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

上述组件中我们有age, fruit和todos本地变量,我们可以独立的更新它们:

  1. function handleOrangeClick() {
  2. // Similar to this.setState({ fruit: 'orange' })
  3. setFruit('orange');
  4. }

你不必使用多个状态变量。状态变量也能是对象或者数组,都OK的,所以你依旧能把相关的数据成组组织结构。但是,不像class里面的this.setState,这里更新状态是直接替换值而不是合并值。
在“FAQ”章节中,我们提供了更多建议关于分割独立状态变量。

下一步

这一讲我们学习了React提供的一个Hook叫useState。我们也有时称之为State Hook。它使得我们为函数组件添加状态——这可是我们有史以来第一次这么做。
我们也学了些什么是Hook的皮毛。Hook是一个特殊的函数让你“钩进来”React的特性。它们的名字是以use开头,且我们还有更多还没见过呢。
现在我们继续学习吧,下一个Hook:useEffect。它能让你的组件处理副作用,而且像class组件的生命周期方法。