如何使用ReactHooks将React类组件转换为函数式组件
介绍
React 的最新 alpha 版本引入了一个名为 Hooks 的新概念。 Hooks 被引入 React 以解决 常见问题 。 但是,它们主要用作类的替代品。 使用 Hooks,您可以创建使用状态和生命周期方法的功能组件。
Hooks 目前在 React v16.7.0-alpha 中可用。 没有取消课程的计划。 Hooks 提供了另一种编写 React 的方法。
鉴于 Hooks 仍然是新事物,许多开发人员正在寻求将这一概念应用到他们现有的 React 应用程序或新应用程序中。 在这篇文章中,您将探索使用 React Hooks 将 React 类组件转换为函数式组件的五种方法。
先决条件
要完成本教程,您需要:
- 熟悉JavaScript。 您可以查看 如何在 JavaScript 系列中编码以了解更多信息并开始使用。
- 熟悉 React。 您可以查看我们的 How To Code in React.js 系列以获取帮助您入门的指南。
无需本地开发,但提供了 CodeSandbox 示例以供进一步实验。
第 1 步 — 了解没有状态或生命周期方法的类
让我们从一个既没有状态也没有生命周期组件的 React 类开始:
ExampleClassComponent.js
import React, { Component } from 'react'; class App extends Component { alertName = () => { alert('John Doe'); }; render() { return ( <div> <h3>This is a Class Component</h3> <button onClick={this.alertName}> Alert </button> </div> ); } }; export default App;
这里有一个典型的 React 类,它缺少状态或生命周期方法。 单击按钮时,它会提醒名称。
此类的等效功能如下所示:
ExampleFunctionalComponent.js
import React from 'react'; function App() { const alertName = () => { alert('John Doe'); }; return ( <div> <h3>This is a Functional Component</h3> <button onClick={alertName}> Alert </button> </div> ); }; export default App;
与第一个示例一样,此功能类以典型方式运行。
然而,这个例子还没有使用 Hooks 或任何新的东西。 在这些示例中,您不需要状态或生命周期。
让我们看一下具有状态的基于类的组件,并学习如何使用 Hooks 将它们转换为函数式组件。
第 2 步 — 将钩子添加到具有状态的类
让我们考虑一种情况,您有一个全局名称变量,您可以在应用程序中从文本输入字段更新该变量。
在 React 中,您可以通过在 state
对象中定义 name 变量并在我们有一个新值来更新 name
变量时调用 setState()
来处理这样的情况:
ExampleClassComponentWithState.js
import React, { Component } from 'react'; class App extends Component { state = { name: '' } alertName = () => { alert(this.state.name); }; handleNameInput = e => { this.setState({ name: e.target.value }); }; render() { return ( <div> <h3>This is a Class Component</h3> <input type="text" onChange={this.handleNameInput} value={this.state.name} placeholder="Your Name" /> <button onClick={this.alertName}> Alert </button> </div> ); } } export default App;
当用户在输入字段中键入名称并单击 Alert 按钮时,它会弹出一个带有在 state 中定义的名称的警报。
您可以使用 Hooks 将整个类转换为功能性 React 组件:
ExampleFunctionalComponentWithState.js
import React, { useState } from 'react'; function App() { const [name, setName] = useState('John Doe'); const alertName = () => { alert(name); }; const handleNameInput = e => { setName(e.target.value); }; return ( <div> <h3>This is a Functional Component</h3> <input type="text" onChange={handleNameInput} value={name} placeholder="Your Name" /> <button onClick={alertName}> Alert </button> </div> ); }; export default App;
在这里,您介绍了 useState
Hook。 它允许您在 React 功能组件中使用状态。 通过 useState()
Hook,您可以在此功能组件中使用状态。 它使用与数组解构赋值类似的语法。
考虑这一行:
const [name, setName] = useState('John Doe')
这里,name
相当于普通类组件中的this.state
,setName
相当于this.setState
。
useState()
Hook 中状态的初始值来自一个参数。 换句话说,useState()
参数是状态的初始值。 在您的情况下,您将其设置为 'John Doe'
。 这意味着状态中名称的初始状态是'John Doe'
。
此代码是一个示例,说明如何使用 Hooks 将具有状态的基于类的 React 组件转换为功能组件。
让我们探索其他场景,包括具有多个状态属性的类。
第 3 步 — 向具有多个状态属性的类添加 Hook
您已经了解了如何使用 useState
转换一个状态属性,但是当您有多个状态属性时,相同的方法就不太适用了。 例如,如果您有两个或多个用于 userName
、firstName
和 lastName
的输入字段,那么您将拥有一个具有三个状态属性的基于类的组件:
ExampleClassComponentWithMultipleStateProperties.js
import React, { Component } from 'react'; class App extends Component { state = { userName: '', firstName: '', lastName: '' }; logName = () => { console.log(this.state.userName); console.log(this.state.firstName); console.log(this.state.lastName); }; handleUserNameInput = e => { this.setState({ userName: e.target.value }); }; handleFirstNameInput = e => { this.setState({ firstName: e.target.value }); }; handleLastNameInput = e => { this.setState({ lastName: e.target.value }); }; render() { return ( <div> <h3>This is a Class Component</h3> <input type="text" onChange={this.handleUserNameInput} value={this.state.userName} placeholder="Your Username" /> <input type="text" onChange={this.handleFirstNameInput} value={this.state.firstName} placeholder="Your First Name" /> <input type="text" onChange={this.handleLastNameInput} value={this.state.lastName} placeholder="Your Last Name" /> <button className="btn btn-large right" onClick={this.logName} > Log Names </button> </div> ); } } export default App;
要使用 Hooks 将此类转换为功能组件,您将不得不采取一些非常规的路线。 使用 useState()
Hook,上面的例子可以写成:
ExampleFunctionalComponentWithMultipleStateProperties.js
import React, { useState } from 'react'; function App() { const [userName, setUsername] = useState(''); const [firstName, setFirstname] = useState(''); const [lastName, setLastname] = useState(''); const logName = () => { console.log(userName); console.log(firstName); console.log(lastName); }; const handleUserNameInput = e => { setUsername(e.target.value); }; const handleFirstNameInput = e => { setFirstname(e.target.value); }; const handleLastNameInput = e => { setLastname(e.target.value); }; return ( <div> <h3>This is a Functional Component</h3> <input type="text" onChange={handleUserNameInput} value={userName} placeholder="Your Username" /> <input type="text" onChange={handleFirstNameInput} value={firstName} placeholder="Your First Name" /> <input type="text" onChange={handleLastNameInput} value={lastName} placeholder="Your Last Name" /> <button className="btn btn-large right" onClick={logName} > Log Names </button> </div> ); }; export default App;
这是此示例的 CodeSandbox。
这演示了如何使用 useState()
Hook 将具有多个状态属性的基于类的组件转换为功能组件。
第 4 步 — 将钩子添加到具有状态和 componentDidMount
的类
让我们考虑一个具有 state
和 componentDidMount
的类。 为了演示,您将看到一个场景,您为三个输入字段设置初始状态,并在五秒后将它们全部更新为一组不同的值。
为此,您将为输入字段声明初始状态值并实现 componentDidMount()
生命周期方法,该方法将在初始渲染后运行以更新状态值:
ExampleClassComponentWithStateAndComponentDidMount.js
import React, { Component } from 'react'; class App extends Component { state = { // initial state userName: 'johndoe', firstName: 'John', lastName: 'Doe' } componentDidMount() { setInterval(() => { this.setState({ // update state userName: 'janedoe', firstName: 'Jane', lastName: 'Doe' }); }, 5000); } logName = () => { console.log(this.state.userName); console.log(this.state.firstName); console.log(this.state.lastName); }; handleUserNameInput = e => { this.setState({ userName: e.target.value }); }; handleFirstNameInput = e => { this.setState({ firstName: e.target.value }); }; handleLastNameInput = e => { this.setState({ lastName: e.target.value }); }; render() { return ( <div> <h3>This is a Class Component</h3> <input type="text" onChange={this.handleUserNameInput} value={this.state.userName} placeholder="Your Username" /> <input type="text" onChange={this.handleFirstNameInput} value={this.state.firstName} placeholder="Your First Name" /> <input type="text" onChange={this.handleLastNameInput} value={this.state.lastName} placeholder="Your Last Name" /> <button className="btn btn-large right" onClick={this.logName} > Log Names </button> </div> ); } } export default App;
当应用程序运行时,输入字段将具有您在状态对象中定义的初始值。 这些值将在五秒后更新为您在 componentDidMount()
方法中定义的值。
接下来,您将使用 React useState
和 useEffect
Hooks 将此类转换为功能组件:
ExampleFunctionalComponentWithStateAndComponentDidMount.js
import React, { useState, useEffect } from 'react'; function App() { const [userName, setUsername] = useState('johndoe'); const [firstName, setFirstname] = useState('John'); const [lastName, setLastname] = useState('Doe'); useEffect(() => { setInterval(() => { setUsername('janedoe'); setFirstname('Jane'); setLastname('Doe'); }, 5000); }); const logName = () => { console.log(userName); console.log(firstName); console.log(lastName); }; const handleUserNameInput = e => { setUsername({ userName: e.target.value }); }; const handleFirstNameInput = e => { setFirstname({ firstName: e.target.value }); }; const handleLastNameInput = e => { setLastname({ lastName: e.target.value }); }; return ( <div> <h3>This is a Functional Component</h3> <input type="text" onChange={handleUserNameInput} value={userName} placeholder="Your Username" /> <input type="text" onChange={handleFirstNameInput} value={firstName} placeholder="Your First Name" /> <input type="text" onChange={handleLastNameInput} value={lastName} placeholder="Your Last Name" /> <button className="btn btn-large right" onClick={logName} > Log Names </button> </div> ); }; export default App;
这是此示例的 CodeSandbox。
在功能方面,该组件的功能与前面的示例完全相同。 唯一的区别是,您没有像在类组件中那样使用传统的 state
对象和 componentDidMount()
生命周期方法,而是使用了 useState
和 useEffect
挂钩。
第 5 步 — 将钩子添加到具有状态、componentDidMount
和 componentDidUpdate
的类
接下来,让我们看一个具有状态和两个生命周期方法的 React 类:componentDidMount
和 componentDidUpdate
。 到目前为止,大多数解决方案都使用了 useState
Hook。 在本例中,您将关注 useEffect
钩子。
为了最好地演示这是如何工作的,让我们修改您的代码以动态更新页面上的 <h3>
标头。
目前,标题显示 This is a Class Component
。 现在,您将定义一个 componentDidMount()
方法以在三秒后更新标题为 Welcome to React Hooks
:
ExampleClassComponentWithStateAndTwoLifecycleMethods.js
import React, { Component } from 'react'; class App extends Component { state = { header: 'Welcome to React Hooks' } componentDidMount() { const header = document.querySelectorAll('#header')[0]; setTimeout(() => { header.innerHTML = this.state.header; }, 3000); } render() { return ( <div> <h3 id="header">This is a Class Component</h3> </div> ); } } export default App;
当应用程序运行时,它将以初始标题 This is a Class Component
开始,并在三秒后将其更改为 Welcome to React Hooks
。 这是经典的 componentDidMount()
行为,因为它在 render
函数成功执行后运行。
让我们添加从另一个输入字段动态更新标题的功能,以便在您键入时使用新文本更新标题。
为此,您需要实现 componentDidUpdate()
生命周期方法:
ExampleClassComponent.js
import React, { Component } from 'react'; class App extends Component { state = { header: 'Welcome to React Hooks' } componentDidMount() { const header = document.querySelectorAll('#header')[0]; setTimeout(() => { header.innerHTML = this.state.header; }, 3000); } componentDidUpdate() { const node = document.querySelectorAll('#header')[0]; node.innerHTML = this.state.header; } handleHeaderInput = e => { this.setState({ header: e.target.value }); }; render() { return ( <div> <h3 id="header">This is a Class Component</h3> <input type="text" onChange={this.handleHeaderInput} value={this.state.header} /> </div> ); } } export default App;
在这里,您有 state
、componentDidMount()
和 componentDidUpdate()
。 当您运行应用程序时,componentDidMount()
函数将在三秒后将标题更新为 Welcome to React Hooks
。 当您开始在标题文本输入字段中输入时,<h3>
文本将使用 componentDidUpdate()
方法中定义的输入文本进行更新。
接下来,您将使用 useEffect()
Hook 将此类转换为功能组件:
ExampleFunctionalComponentWithStateAndTwoLifecycleMethods.js
import React, { useState, useEffect } from 'react'; function App() { const [header, setHeader] = useState('Welcome to React Hooks'); useEffect(() => { const newheader = document.querySelectorAll('#header')[0]; setTimeout(() => { newheader.innerHTML = header; }, 3000); }); const handleHeaderInput = e => { setHeader(e.target.value); }; return ( <div> <h3 id="header">This is a Functional Component</h3> <input type="text" onChange={handleHeaderInput} value={header} /> </div> ); }; export default App;
在 CodeSandbox 上查看此示例。
您使用此组件实现了与之前使用 useEffect()
Hook 相同的功能。 您还优化了代码,因为您不必为 componentDidMount()
和 componentDidUpdate()
函数编写单独的代码。 使用 useEffect()
Hook,您可以获得两者的功能。 这是因为 useEffect()
默认在初始渲染后和每次后续更新后都会运行。
第 6 步 — 将 PureComponent
转换为 React memo
React PureComponent 的工作方式与 Component 类似。 它们之间的主要区别在于 React.Component
没有实现 shouldComponentUpdate()
生命周期方法,而 React.PureComponent
实现了。
如果您有一个应用程序,其中 render()
函数在给定相同的道具和状态的情况下呈现相同的结果,您可以在某些情况下使用 React.PureComponent
来提高性能。
React.memo()
以类似的方式工作。 当你的函数组件在给定相同的 props 的情况下呈现相同的结果时,你可以将它包装在对 React.memo()
的调用中以提高性能。 使用 PureComponent
和 React.memo()
可以显着提高 React 应用程序的性能,因为它减少了应用程序中的渲染操作数量。
要了解它们的作用,您将首先查看组件每两秒渲染一次的代码,无论值或状态是否发生变化:
ExampleClassComponent.js
import React, { Component } from 'react'; function Unstable(props) { // monitor how many times this component is rendered console.log('Rendered Unstable component'); return ( <div> <p>{props.value}</p> </div> ); }; class App extends Component { state = { value: 1 }; componentDidMount() { setInterval(() => { this.setState(() => { return { value: 1 }; }); }, 2000); } render() { return ( <div> <Unstable value={this.state.value} /> </div> ); } } export default App;
当您运行应用程序并检查日志时,您会注意到它每两秒渲染一次组件,状态或道具没有任何变化。 这种情况可以通过 PureComponent
和 React.memo()
来改善。
大多数时候,您只想在状态或道具发生变化时重新渲染组件。 使用上面的示例,您可以使用 PureComponent
对其进行改进,以便组件仅在状态或道具发生变化时重新渲染。
您可以通过导入 PureComponent
并扩展它来完成此操作:
ExamplePureComponent.js
import React, { PureComponent } from 'react'; function Unstable(props) { console.log('Rendered Unstable component'); return ( <div> <p>{props.value}</p> </div> ); }; class App extends PureComponent { state = { value: 1 }; componentDidMount() { setInterval(() => { this.setState(() => { return { value: 1 }; }); }, 2000); } render() { return ( <div> <Unstable value={this.state.value} /> </div> ); } } export default App;
现在,如果您再次运行该应用程序,您只会获得初始渲染。 之后没有其他事情发生。 这是因为你有 class App extends PureComponent {}
而不是 class App extends Component {}
。
这解决了组件被重新渲染而不考虑当前状态的问题。 但是,如果您在 setState
方法中实现状态更改,则会遇到另一个问题。
例如,考虑对 setState()
进行以下更改:
目前,value
设置为 1
:
componentDidMount() { setInterval(() => { this.setState(() => { return { value: 1 }; }); }, 2000); }
让我们考虑将 value
设置为 Math.random()
的情况:
componentDidMount() { setInterval(() => { this.setState(() => { return { value: Math.round(Math.random()) }; }); }, 2000); }
在这种情况下,第一个示例组件将在每次值更新为下一个随机数时重新渲染。 但是,PureComponent
可以仅在状态或道具发生变化时重新渲染组件。
现在您可以探索如何使用 React.memo()
来实现相同的修复。 为此,请使用 React.memo()
包装组件:
ExampleReactMemo.js
import React, { Component } from 'react'; const Unstable = React.memo(function Unstable (props) { console.log('Rendered Unstable component'); return ( <div> <p>{props.value}</p> </div> ); }); class App extends Component { state = { val: 1 }; componentDidMount() { setInterval(() => { this.setState(() => { return { value: 1 }; }); }, 2000); } render() { return ( <div> <Unstable val={this.state.val} /> </div> ); } } export default App;
这是此示例的 CodeSandbox。
这实现了与使用 PureComponent
相同的结果。 该组件仅在初始渲染后渲染,并且在状态或道具发生变化之前不会再次重新渲染。
结论
在本教程中,您探索了几种使用 React Hooks 将现有的基于类的组件转换为功能组件的方法。
您还查看了将 React PureComponent
类转换为 React.memo()
的特殊情况。
要在您的应用程序中使用 Hooks,请务必将您的 React 版本更新到支持的版本:
"react": "^16.7.0-alpha", "react-dom": "^16.7.0-alpha",
你现在有了进一步试验 React Hooks 的基础。