在React中用useEffectHook替换组件生命周期
介绍
React Hooks 正在彻底改变我们在 React 中的开发方式,并解决了我们最关心的一些问题。 useEffect
Hook 允许我们替换重复的组件生命周期代码。
本质上,Hook 是一个特殊的函数,它允许你“钩入”React 特性。 如果您之前编写了一个函数式组件并意识到您需要向它添加状态,那么 Hooks 是一个很好的解决方案。
如果您是 Hooks 的新手并想了解概述,请查看 对 React Hooks 的介绍。
本文假设您熟悉 useState
Hook。 如果你不是,永远不要害怕! 如果您花一点时间在 使用状态挂钩 将基于 React 类的组件转换为函数式组件,那么您将走上正轨!
关于 useEffect
useEffect
是“使用副作用”的缩写。 效果是当我们的应用程序对外部世界做出反应时,比如使用 API。 它允许我们根据是否发生变化来运行一个函数。 useEffect
还允许我们组合 componentDidMount
和 componentDidUpdate
。
关于我们的应用程序
我们将采用一些预先编写的基于类的代码并将其转换为功能组件。 我们将使用 reactstrap 来简化我们的格式,并使用 axios 来调用外部虚拟 API。
具体来说,我们使用 jsonplaceholder 在我们的初始组件挂载中引入虚拟用户数据。
然后我们触发组件根据用户点击重新渲染并提取有关用户的其他数据。
入门
只需使用起始代码克隆 repo:
$ git clone https://github.com/alligatorio/use-effect-hook $ npm i $ npm start
花点时间熟悉一下代码,特别是 ClassBasedComponent.js
文件。
你会注意到我们在这个文件中有两个生命周期方法,componentDidMount
和 componentDidUpdate
。
async componentDidMount() { const response = await axios .get(`https://jsonplaceholder.typicode.com/users`); this.setState({ users: response.data }); }; async componentDidUpdate(prevProps) { if (prevProps.resource !== this.props.resource) { const response = await axios .get(`https://jsonplaceholder.typicode.com/users`); this.setState({ users: response.data }); } };
这些都是 async
生命周期方法,它们调用 jsonplaceholder API 来引入用户列表。
在 componentDidMount
中,我们说 在第一次渲染时,获取用户数据 。 接下来,在 componentDidUpdate
上,我们查看 props
中是否有任何变化。 这可以由用户发起的事件触发,例如在我们的示例中,按下按钮。 一旦检测到变化,我们说,出去再次获取数据。
我们希望将生命周期方法压缩到 useEffect
Hook 中,并创建一个基于函数的组件。
创建一个组件
不要使用相同的 ClassBasedComponent.js
文件,而是创建一个名为 FunctionBasedComponent.js
的新文件。 我们正在创建一个新文件,以便我们可以对比和比较两者。
在您的终端中,您可以运行以下命令从根目录创建新文件:
$ touch FunctionBasedComponent.js
为了帮助您开始,请将以下代码复制并粘贴到您的新文件中:
import React, { useState, useEffect } from 'react'; import { Container, Button, Row } from 'reactstrap'; import axios from 'axios'; const FunctionBasedComponent = () => { return ( <Container className="user-list"> <h1>My Contacts:</h1> </Container> ) }; export default FunctionBasedComponent;
现在跳到您的 App.js
文件,导入您的 FunctionBasedComponent.js
文件并将 ClassBasedComponent
替换为 FunctionBasedComponent
。
您的应用现在应该类似于下面的屏幕截图。
让我们从使用 useState
初始化状态开始。
const [ users, setUsers ] = useState([]); const [ showDetails, setShowDetails ] = useState(false);
为了快速回顾 useState
,使用 useState
钩子初始化 state
,我们在数组中声明我们的变量和对应于该变量的函数,然后我们传递 [ X188X] 我们想用来初始化变量的参数。
users
状态变量用一个空数组初始化,并赋予setUsers
的功能。showDetails
状态变量被初始化为false
的值,并分配了setShowDetails
的功能。
添加 API 调用
让我们继续添加我们的 API 调用作为 fetchUsers
函数。
const fetchUsers = async () => { const response = await axios.get(`https://jsonplaceholder.typicode.com/users`); setUsers(response.data); };
我们本质上是从以前的 componentDidMount
和 componentDidUpdate
函数中提取这个 async
调用。
请记住,我们不能直接在 useEffect
内部使用 async
函数。 如果我们想要调用异步函数,我们需要在 useEffect
之外定义函数,然后在 useEffect
中调用它。
useEffect 参数
让我们先谈谈 useEffect
钩子。 很像 componentDidMount
,useEffect
会立即调用我们的函数。
useEffect( () => {}, [ 'value' ]);
默认情况下,useEffect
会查看数组值是否不同,如果不同,则会自动调用箭头函数。
useEffect( () => {}, [ 'different value' ]);
让我们回到我们的代码编辑器并在我们将调用 fetchUsers
的最新函数下方添加 useEffect
钩子。
在下面的代码中,我们正在查看用户对象以查看是否有更改。
useEffect( () => { fetchUsers(users) }, [ users ] );
常见问题
- 如果您不将数组传递给 useEffect Hook,您的组件将不断重复加载。
useEffect( () => { fetchUsers(users) } );
- 如果你传递一个空数组,我们不会观察任何变量,因此它只会在第一次渲染时更新状态,就像
componentDidMount
。
useEffect( () => { fetchUsers(users) }, [] );
- 每次我们在 JavaScript 中创建一个对象时,它在内存中都是一个不同的对象。 虽然 下面的代码看起来 相同,但页面将被重新渲染,因为每个对象都存储在不同的内存地址中。 相同的逻辑适用于数组。
useEffect( () => { fetchUsers(users) }, [{ user: 'Alli Alligator' }] );
不等于!
useEffect( () => { fetchUsers(users) }, [{ user: 'Alli Alligator' }] );
useEffect
函数 必须 返回一个清理函数,否则什么都不返回。
要演示触发另一个重新渲染,请将以下代码复制并粘贴到您的 FunctionBasedComponent.js
文件中:
import React, { useState, useEffect } from 'react'; import { Container, Button, Row } from 'reactstrap'; import axios from 'axios'; const FunctionBasedComponent = () => { const [ users, setUsers ] = useState([]); const [ showDetails, setShowDetails ] = useState(false); const fetchUsers = async () => { const response = await axios.get(`https://jsonplaceholder.typicode.com/users`); setUsers(response.data); }; useEffect( () => { fetchUsers(users) }, [ users ] ); const handleClick = event => { setShowDetails(!showDetails) }; return ( <Container> { users.map((user) => ( <ul key={ user.id }> <li> <strong>{ user.name }</strong> <div> <Button onClick={ handleClick } > { showDetails ? "Close Additional Info" : "More Info" } </Button> { showDetails && <Container className="additional-info"> <Row> { `Email: ${ user.email }` } </Row> <Row> { `Phone: ${ user.phone }` } </Row> <Row> { `Website: ${ user.website }` } </Row> </Container> } </div> </li> </ul> )) } </Container> ) } export default FunctionBasedComponent;
现在我们在按钮中有一个 onClick
事件。 单击按钮时,showDetails
的状态发生更改,触发重新渲染,该重新渲染将再次调用 API 并引入我们需要的其他详细信息。
瞧!
async componentDidMount() { const response = await axios.get(`https://jsonplaceholder.typicode.com/users`) this.setState({ users: response.data }) }; async componentDidUpdate(prevProps) { if (prevProps.resource !== this.props.resource) { const response = await axios.get(`https://jsonplaceholder.typicode.com/users`) this.setState({ users: response.data }) } };
变成:
const fetchUsers = async () => { const response = await axios.get(`https://jsonplaceholder.typicode.com/users`); setUsers(response.data); }; useEffect( () => { fetchUsers(users) }, [ users ] );