如何使用 React 挂钩将 React 类组件转换为功能组件

简介

Reaction的最新Alpha版本引入了一个名为_Hooks_的新概念。引入了钩子来响应解决通用problems.然而,它们主要用作类的替代。使用Hooks,您可以创建使用状态和生命周期方法的功能组件。

钩子目前在React v16.7.0-Alpha中可用。目前还没有取消课程的计划。钩子提供了编写Reaction的另一种方式。

鉴于Hooks仍然是新的,许多开发人员希望在现有的React应用程序或新应用程序中应用这个概念。在这篇文章中,你将探索使用React Hooks将React类组件转换为函数组件的五种方法。

前提条件

要完成本教程,您需要:

  • 熟悉JavaScript。您可以阅读如何在JavaScript中编写代码》系列,以了解更多信息并开始使用。
  • 熟悉反应。您可以查看我们的如何在React.js中编写代码》系列,以获取帮助您入门的指南。

不需要本地开发,但提供了CodeSandbox示例以供进一步实验。

第一步-了解没有状态或生命周期方法的类

让我们从一个既没有状态也没有生命周期组件的Reaction类开始:

 1[label ExampleClassComponent.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  alertName = () => {
 6    alert('John Doe');
 7  };
 8
 9  render() {
10    return (
11      <div>
12        <h3>This is a Class Component</h3>
13        <button onClick={this.alertName}>
14          Alert
15        </button>
16      </div>
17    );
18  }
19};
20
21export default App;

这里有一个典型的Reaction类,它没有状态或生命周期方法。当一个按钮被点击时,它会提醒一个名字。

此类的函数等效项如下所示:

 1[label ExampleFunctionalComponent.js]
 2import React from 'react';
 3
 4function App() {
 5  const alertName = () => {
 6    alert('John Doe');
 7  };
 8
 9  return (
10    <div>
11      <h3>This is a Functional Component</h3>
12      <button onClick={alertName}>
13        Alert
14      </button>
15    </div>
16  );
17};
18
19export default App;

与第一个示例一样,这个功能类的行为方式很典型。

然而,这个例子还没有使用Hooks或任何新的东西。在这些示例中,您不需要状态或生命周期。

让我们来看看带状态的基于类的组件,并学习如何使用Hooks将它们转换为功能组件。

Step 2 -为类添加带状态的钩子

让我们考虑这样一种情况:您有一个全局名称变量,您可以在应用程序中从文本输入字段更新该变量。

在Reaction中,您可以通过在state对象中定义name变量并在有新值要用来更新name变量时调用setState()来处理这样的情况:

 1[label ExampleClassComponentWithState.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  state = {
 6    name: ''
 7  }
 8
 9  alertName = () => {
10    alert(this.state.name);
11  };
12
13  handleNameInput = e => {
14    this.setState({ name: e.target.value });
15  };
16
17  render() {
18    return (
19      <div>
20        <h3>This is a Class Component</h3>
21        <input
22          type="text"
23          onChange={this.handleNameInput}
24          value={this.state.name}
25          placeholder="Your Name"
26        />
27        <button onClick={this.alertName}>
28          Alert
29        </button>
30      </div>
31    );
32  }
33}
34
35export default App;

当用户在输入字段中输入姓名并单击警报 按钮时,它会弹出一个警报,该警报的名称在状态中定义。

您可以使用Hooks将整个类转换为功能反应组件:

 1[label ExampleFunctionalComponentWithState.js]
 2import React, { useState } from 'react';
 3
 4function App() {
 5  const [name, setName] = useState('John Doe');
 6
 7  const alertName = () => {
 8    alert(name);
 9  };
10
11  const handleNameInput = e => {
12    setName(e.target.value);
13  };
14
15  return (
16    <div>
17      <h3>This is a Functional Component</h3>
18      <input
19        type="text"
20        onChange={handleNameInput}
21        value={name}
22        placeholder="Your Name"
23      />
24      <button onClick={alertName}>
25        Alert
26      </button>
27    </div>
28  );
29};
30
31export default App;

在这里,您介绍了useState钩子。它允许您在Reaction功能组件中使用状态。通过useState()钩子,可以在该功能组件中使用状态。它使用类似的语法对数组进行解构赋值。

请考虑下面这行:

1const [name, setName] = useState('John Doe')

其中,name相当于普通类组件中的this.statesetName相当于this.setState

useState()钩子中状态的初始值来自一个参数。换句话说,useState()参数是状态的初始值。在你的案例中,你把它设置为‘’无名氏‘’。这意味着状态中的姓名的初始状态为‘’John Doe‘。

这段代码是如何使用Hooks将基于类的React组件转换为函数组件的示例。

让我们探索其他场景,包括具有多个状态属性的类。

第三步-向具有多个状态属性的类添加钩子

您已经了解了如何使用useState转换一个状态属性,但是当您有多个状态属性时,相同的方法不会完全起作用。例如,如果您有两个或更多的userNamefirstNamelastName输入字段,那么您将拥有一个具有三个状态属性的基于类的组件:

 1[label ExampleClassComponentWithMultipleStateProperties.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  state = {
 6    userName: '',
 7    firstName: '',
 8    lastName: ''
 9  };
10
11  logName = () => {
12    console.log(this.state.userName);
13    console.log(this.state.firstName);
14    console.log(this.state.lastName);
15  };
16
17  handleUserNameInput = e => {
18    this.setState({ userName: e.target.value });
19  };
20  handleFirstNameInput = e => {
21    this.setState({ firstName: e.target.value });
22  };
23  handleLastNameInput = e => {
24    this.setState({ lastName: e.target.value });
25  };
26
27  render() {
28    return (
29      <div>
30        <h3>This is a Class Component</h3>
31        <input
32          type="text"
33          onChange={this.handleUserNameInput}
34          value={this.state.userName}
35          placeholder="Your Username"
36        />
37        <input
38          type="text"
39          onChange={this.handleFirstNameInput}
40          value={this.state.firstName}
41          placeholder="Your First Name"
42        />
43        <input
44          type="text"
45          onChange={this.handleLastNameInput}
46          value={this.state.lastName}
47          placeholder="Your Last Name"
48        />
49        <button
50          className="btn btn-large right"
51          onClick={this.logName}
52        >
53          Log Names
54        </button>
55      </div>
56    );
57  }
58}
59
60export default App;

要使用Hooks将这个类转换为功能组件,您必须采取一种有点非常规的方式。使用useState()钩子,上面的例子可以写成:

 1[label ExampleFunctionalComponentWithMultipleStateProperties.js]
 2import React, { useState } from 'react';
 3
 4function App() {
 5  const [userName, setUsername] = useState('');
 6  const [firstName, setFirstname] = useState('');
 7  const [lastName, setLastname] = useState('');
 8
 9  const logName = () => {
10    console.log(userName);
11    console.log(firstName);
12    console.log(lastName);
13  };
14
15  const handleUserNameInput = e => {
16    setUsername(e.target.value);
17  };
18  const handleFirstNameInput = e => {
19    setFirstname(e.target.value);
20  };
21  const handleLastNameInput = e => {
22    setLastname(e.target.value);
23  };
24
25  return (
26    <div>
27      <h3>This is a Functional Component</h3>
28      <input
29        type="text"
30        onChange={handleUserNameInput}
31        value={userName}
32        placeholder="Your Username"
33      />
34      <input
35        type="text"
36        onChange={handleFirstNameInput}
37        value={firstName}
38        placeholder="Your First Name"
39      />
40      <input
41        type="text"
42        onChange={handleLastNameInput}
43        value={lastName}
44        placeholder="Your Last Name"
45      />
46      <button
47        className="btn btn-large right"
48        onClick={logName}
49      >
50        Log Names
51      </button>
52    </div>
53  );
54};
55
56export default App;

下面是本例的CodeSandbox

这演示了如何使用useState()钩子将具有多个状态属性的基于类的组件转换为功能组件。

第四步-向带有State和ComponentDidmount的类添加钩子`

让我们来考虑一个带有stateComponentDidMountain的类。为了进行演示,您将查看一个场景,其中您为三个输入字段设置了初始状态,并在五秒钟后将它们全部更新为一组不同的值。

为了实现这一点,您将为输入字段声明一个初始状态值,并实现一个componentDidMount()生命周期方法,该方法将在初始渲染后运行以更新状态值:

 1[label ExampleClassComponentWithStateAndComponentDidMount.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  state = {
 6    // initial state
 7    userName: 'johndoe',
 8    firstName: 'John',
 9    lastName: 'Doe'
10  }
11
12  componentDidMount() {
13    setInterval(() => {
14      this.setState({
15        // update state
16        userName: 'janedoe',
17        firstName: 'Jane',
18        lastName: 'Doe'
19      });
20    }, 5000);
21  }
22
23  logName = () => {
24    console.log(this.state.userName);
25    console.log(this.state.firstName);
26    console.log(this.state.lastName);
27  };
28
29  handleUserNameInput = e => {
30    this.setState({ userName: e.target.value });
31  };
32  handleFirstNameInput = e => {
33    this.setState({ firstName: e.target.value });
34  };
35  handleLastNameInput = e => {
36    this.setState({ lastName: e.target.value });
37  };
38
39  render() {
40    return (
41      <div>
42        <h3>This is a Class Component</h3>
43        <input
44          type="text"
45          onChange={this.handleUserNameInput}
46          value={this.state.userName}
47          placeholder="Your Username"
48        />
49        <input
50          type="text"
51          onChange={this.handleFirstNameInput}
52          value={this.state.firstName}
53          placeholder="Your First Name"
54        />
55        <input
56          type="text"
57          onChange={this.handleLastNameInput}
58          value={this.state.lastName}
59          placeholder="Your Last Name"
60        />
61        <button
62          className="btn btn-large right"
63          onClick={this.logName}
64        >
65          Log Names
66        </button>
67      </div>
68    );
69  }
70}
71
72export default App;

当应用程序运行时,输入字段将具有您在状态对象中定义的初始值。五秒钟后,这些值将更新为您在ComponentDidmount()方法中定义的值。

接下来,您将使用React useStateuseEffect Hooks将此类转换为函数组件:

 1[label ExampleFunctionalComponentWithStateAndComponentDidMount.js]
 2import React, { useState, useEffect } from 'react';
 3
 4function App() {
 5  const [userName, setUsername] = useState('johndoe');
 6  const [firstName, setFirstname] = useState('John');
 7  const [lastName, setLastname] = useState('Doe');
 8
 9  useEffect(() => {
10    setInterval(() => {
11      setUsername('janedoe');
12      setFirstname('Jane');
13      setLastname('Doe');
14    }, 5000);
15  });
16
17  const logName = () => {
18    console.log(userName);
19    console.log(firstName);
20    console.log(lastName);
21  };
22
23  const handleUserNameInput = e => {
24    setUsername({ userName: e.target.value });
25  };
26  const handleFirstNameInput = e => {
27    setFirstname({ firstName: e.target.value });
28  };
29  const handleLastNameInput = e => {
30    setLastname({ lastName: e.target.value });
31  };
32
33  return (
34    <div>
35      <h3>This is a Functional Component</h3>
36      <input
37        type="text"
38        onChange={handleUserNameInput}
39        value={userName}
40        placeholder="Your Username"
41      />
42      <input
43        type="text"
44        onChange={handleFirstNameInput}
45        value={firstName}
46        placeholder="Your First Name"
47      />
48      <input
49        type="text"
50        onChange={handleLastNameInput}
51        value={lastName}
52        placeholder="Your Last Name"
53      />
54      <button
55        className="btn btn-large right"
56        onClick={logName}
57      >
58        Log Names
59      </button>
60    </div>
61  );
62};
63
64export default App;

下面是本例的CodeSandbox

就功能而言,该组件与上一个示例执行完全相同的操作。唯一的区别是,您使用了useStateuseEffect钩子,而不是像在类组件中那样使用传统的state对象和componentDidMount()生命周期方法。

第五步-向带有State、ComponentDidmountComponentDidUpdate的类添加钩子`

接下来,我们来看一个带有状态和两个生命周期方法的Reaction类:ComponentDidMonttComponentDidUpdate。到目前为止,大多数解决方案都使用了useState挂钩。在本例中,您将重点介绍useEffect钩子。

为了更好地演示它的工作原理,让我们修改您的代码以动态更新页面上的<h3>头部。

当前,标题显示)方法,在三秒后更新头部,说Welcome to React Hooks`:

 1[label ExampleClassComponentWithStateAndTwoLifecycleMethods.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  state = {
 6    header: 'Welcome to React Hooks'
 7  }
 8
 9  componentDidMount() {
10    const header = document.querySelectorAll('#header')[0];
11    setTimeout(() => {
12      header.innerHTML = this.state.header;
13    }, 3000);
14  }
15
16  render() {
17    return (
18      <div>
19        <h3 id="header">This is a Class Component</h3>
20      </div>
21    );
22  }
23}
24
25export default App;

当应用程序运行时,它会以初始头部This is a Class Component开头,三秒后改为Welcome to Reaction Hooks。这是典型的ComponentDidmount()行为,因为它在render函数成功执行后运行。

让我们添加从另一个输入字段动态更新标题的功能,以便在您键入时使用新文本更新标题。

为此,您需要实现ComponentDidUpdate()生命周期方法:

 1[label ExampleClassComponent.js]
 2import React, { Component } from 'react';
 3
 4class App extends Component {
 5  state = {
 6    header: 'Welcome to React Hooks'
 7  }
 8
 9  componentDidMount() {
10    const header = document.querySelectorAll('#header')[0];
11    setTimeout(() => {
12      header.innerHTML = this.state.header;
13    }, 3000);
14  }
15
16  componentDidUpdate() {
17    const node = document.querySelectorAll('#header')[0];
18    node.innerHTML = this.state.header;
19  }
20
21  handleHeaderInput = e => {
22    this.setState({ header: e.target.value });
23  };
24
25  render() {
26    return (
27      <div>
28        <h3 id="header">This is a Class Component</h3>
29        <input
30          type="text"
31          onChange={this.handleHeaderInput}
32          value={this.state.header}
33        />
34      </div>
35    );
36  }
37}
38
39export default App;

在这里,您有statecomponentDidMount()componentDidUpdate()。当你运行应用程序时,componentDidMount()函数会在三秒后将头更新为Welcome to React Hooks。当您开始在标题文本输入字段中键入时,<h3>文本将按照componentDidUpdate()方法中定义的输入文本进行更新。

接下来,您将使用useEffect()钩子将此类转换为功能组件:

 1[label ExampleFunctionalComponentWithStateAndTwoLifecycleMethods.js]
 2import React, { useState, useEffect } from 'react';
 3
 4function App() {
 5  const [header, setHeader] = useState('Welcome to React Hooks');
 6
 7  useEffect(() => {
 8    const newheader = document.querySelectorAll('#header')[0];
 9    setTimeout(() => {
10      newheader.innerHTML = header;
11    }, 3000);
12  });
13
14  const handleHeaderInput = e => {
15    setHeader(e.target.value);
16  };
17
18  return (
19    <div>
20      <h3 id="header">This is a Functional Component</h3>
21      <input
22        type="text"
23        onChange={handleHeaderInput}
24        value={header}
25      />
26    </div>
27  );
28};
29
30export default App;

CodeSandbox.上查看此示例

您使用该组件通过使用useEffect()钩子实现了与之前相同的功能。您还对代码进行了优化,因为您不必为ComponentDidmount()ComponentDidUpdate()函数编写单独的代码。使用useEffect()钩子,您可以获得两者的功能。这是因为默认情况下,useEffect()在初始渲染之后和每次后续更新之后都会运行。

第六步-将PureComponent转换为React Memo

Reaction PureComponent的工作方式与[Component](https://reactjs.org/docs/react-api.html reactcomponent).类似它们之间的主要区别是React.Component没有实现shouldComponentUpdate()生命周期方法,而React.PureComponent实现了。

如果您有一个应用程序,其中render()函数在给定相同的道具和状态的情况下呈现相同的结果,您可以在某些情况下使用React.PureComponent来提高性能。

React.memo()的工作方式与此类似。当您的函数组件在给定相同道具的情况下呈现相同的结果时,您可以将其包装在对React.memo()的调用中以增强性能。使用PureComponentReact.Memo()可以显著提高React应用程序的性能,因为它减少了应用程序中的渲染操作数量。

为了理解它们都做了什么,您将首先查看组件每两秒呈现一次的代码,无论值或状态是否发生更改:

 1[label ExampleClassComponent.js]
 2import React, { Component } from 'react';
 3
 4function Unstable(props) {
 5  // monitor how many times this component is rendered
 6  console.log('Rendered Unstable component');
 7  return (
 8    <div>
 9      <p>{props.value}</p>
10    </div>
11  );
12};
13
14class App extends Component {
15  state = {
16    value: 1
17  };
18
19  componentDidMount() {
20    setInterval(() => {
21      this.setState(() => {
22        return { value: 1 };
23      });
24    }, 2000);
25  }
26
27  render() {
28    return (
29      <div>
30        <Unstable value={this.state.value} />
31      </div>
32    );
33  }
34}
35export default App;

当你运行应用程序并检查日志时,你会注意到它每两秒呈现一次组件,状态或道具没有任何变化。这是一种可以通过PureComponentReact.Memo()改善的情况。

多个渲染operations的控制台日志输出

大多数情况下,您只想在状态或道具发生更改时重新渲染组件。使用上面的示例,您可以使用PureComponent对其进行改进,以便组件仅在状态或道具发生变化时才重新呈现。

您可以通过导入并扩展PureComponent来实现:

 1[label ExamplePureComponent.js]
 2import React, { PureComponent } from 'react';
 3
 4function Unstable(props) {
 5  console.log('Rendered Unstable component');
 6  return (
 7    <div>
 8      <p>{props.value}</p>
 9    </div>
10  );
11};
12
13class App extends PureComponent {
14  state = {
15    value: 1
16  };
17
18  componentDidMount() {
19    setInterval(() => {
20      this.setState(() => {
21        return { value: 1 };
22      });
23    }, 2000);
24  }
25
26  render() {
27    return (
28      <div>
29        <Unstable value={this.state.value} />
30      </div>
31    );
32  }
33}
34
35export default App;

现在,如果你再次运行该应用程序,你只会得到初始渲染。在那之后,没有其他事情发生。因为您使用的是类应用扩展PureComponent{},而不是类应用扩展组件{}

单个渲染operation的控制台日志输出

这解决了在不考虑当前状态的情况下重新呈现组件的问题。但是,如果在setState方法中实现状态更改,则会遇到另一个问题。

例如,考虑对setState()进行以下更改:

目前value设置为1

1componentDidMount() {
2  setInterval(() => {
3    this.setState(() => {
4      return { value: 1 };
5    });
6  }, 2000);
7}

让我们来考虑一下value设置为Math.Random()的情况:

1componentDidMount() {
2  setInterval(() => {
3    this.setState(() => {
4      return { value: Math.round(Math.random()) };
5    });
6  }, 2000);
7}

在此场景中,第一个示例组件将在每次值更新为下一个随机数时重新呈现。但是,只有在状态或道具发生变化时,PureComponent才能重新呈现组件。

现在您可以探索如何使用React.Memo()来实现相同的修复。为此,使用React.memo()包装组件:

 1[label ExampleReactMemo.js]
 2import React, { Component } from 'react';
 3
 4const Unstable = React.memo(function Unstable (props) {
 5  console.log('Rendered Unstable component');
 6  return (
 7    <div>
 8      <p>{props.value}</p>
 9    </div>
10  );
11});
12
13class App extends Component {
14  state = {
15    val: 1
16  };
17
18  componentDidMount() {
19    setInterval(() => {
20      this.setState(() => {
21        return { value: 1 };
22      });
23    }, 2000);
24  }
25
26  render() {
27    return (
28      <div>
29        <Unstable val={this.state.val} />
30      </div>
31    );
32  }
33}
34
35export default App;

下面是本例的CodeSandbox

达到了与使用PureComponent相同的效果。该组件仅在初始渲染后进行渲染,并且在状态或道具发生更改之前不会再次重新渲染。

结论

在本教程中,您探索了几种使用Reaction Hooks将现有的基于类的组件转换为功能组件的方法。

您还了解了将ReactionPureComponent类转换为React.Memo()的特殊情况。

要在应用程序中使用Hooks,请确保将您的React版本更新为支持的版本:

1"react": "^16.7.0-alpha",
2"react-dom": "^16.7.0-alpha",

现在,您有了进一步尝试使用Reaction Hooks的基础。

了解更多关于Getting Started with React HooksBuild a React To-Do App with React Hooks

Published At
Categories with 技术
Tagged with
comments powered by Disqus