简介
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.state
,setName
相当于this.setState
。
useState()
钩子中状态的初始值来自一个参数。换句话说,useState()
参数是状态的初始值。在你的案例中,你把它设置为‘’无名氏‘’。这意味着状态中的姓名的初始状态为‘’John Doe‘。
这段代码是如何使用Hooks将基于类的React组件转换为函数组件的示例。
让我们探索其他场景,包括具有多个状态属性的类。
第三步-向具有多个状态属性的类添加钩子
您已经了解了如何使用useState
转换一个状态属性,但是当您有多个状态属性时,相同的方法不会完全起作用。例如,如果您有两个或更多的userName
、firstName
和lastName
输入字段,那么您将拥有一个具有三个状态属性的基于类的组件:
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
的类添加钩子`
让我们来考虑一个带有state
和ComponentDidMountain
的类。为了进行演示,您将查看一个场景,其中您为三个输入字段设置了初始状态,并在五秒钟后将它们全部更新为一组不同的值。
为了实现这一点,您将为输入字段声明一个初始状态值,并实现一个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 useState
和useEffect
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。
就功能而言,该组件与上一个示例执行完全相同的操作。唯一的区别是,您使用了useState
和useEffect
钩子,而不是像在类组件中那样使用传统的state
对象和componentDidMount()
生命周期方法。
第五步-向带有State、ComponentDidmount
、ComponentDidUpdate
的类添加钩子`
接下来,我们来看一个带有状态和两个生命周期方法的Reaction类:ComponentDidMontt
和ComponentDidUpdate
。到目前为止,大多数解决方案都使用了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;
在这里,您有state
、componentDidMount()
和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()
的调用中以增强性能。使用PureComponent
和React.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;
当你运行应用程序并检查日志时,你会注意到它每两秒呈现一次组件,状态或道具没有任何变化。这是一种可以通过PureComponent
和React.Memo()
改善的情况。
的控制台日志输出
大多数情况下,您只想在状态或道具发生更改时重新渲染组件。使用上面的示例,您可以使用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{}
,而不是类应用扩展组件{}
。
的控制台日志输出
这解决了在不考虑当前状态的情况下重新呈现组件的问题。但是,如果在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 Hooks和Build a React To-Do App with React Hooks。