使用 Effect Hook
Posted 前端e站
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 Effect Hook相关的知识,希望对你有一定的参考价值。
Effect Hook可以让你在function组件里搞出点副作用出来。
import React, useState, useEffect from 'react';
function Example()
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() =>
// Update the document title using the browser API
document.title = `You clicked $count times`;
);
return (
<div>
<p>You clicked count times</p>
<button onClick=() => setCount(count + 1)>
Click me
</button>
</div>
);
这段代码是基于之前的例子的,现在我们可以给它加一点功能——点击后可以在文档标题上显示自定义消息。
从服务器获取数据,设置一个订阅,在React组件里手动修改DOM都属于effect的范畴。无论你是否把它们称为effect,你其实以前都在组件里用到过它们了。
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
React组件里有两种常见的effect,一种需要清理,另一种不需要。
不需要清理的effect
有时我们需要在React更新DOM后执行额外的代码。网络请求,手工DOM修改,日志等都是常见的不需要清理的effect。之所以如此说是因为我们运行完之后可以立刻把它们忘了。让我们比较一下class和Hooks是怎么实现这种effect的。
使用class的例子
在React的class组件里,render方法自身不会引起effect。在这个里面还早了些——我们特别希望是React更新DOM了之后再执行我们的effect代码。
这就是为什么我们把effect代码放在了class的componentDidMount和componentDidUpdate里。回到例子里,这里有一个class组件,当React更新完DOM之后会去修改文档标题。
class Example extends React.Component
constructor(props)
super(props);
this.state =
count: 0
;
componentDidMount()
document.title = `You clicked $this.state.count times`;
componentDidUpdate()
document.title = `You clicked $this.state.count times`;
render()
return (
<div>
<p>You clicked this.state.count times</p>
<button onClick=() => this.setState( count: this.state.count + 1 )>
Click me
</button>
</div>
);
注意到我们不得不在class里的两个生命周期方法里重复代码。
这是因为在许多场景下,不管是第一次组件加载还是组件更新,我们需要执行相同的effect代码。从概念上来说,我们希望是发生在每次渲染之后——但React类组件没有像这样的回调方法。尽管可以把代码抽到一个独立的方法里,但我们仍然要在class里调用两次。
使用Hooks的例子
import React, useState, useEffect from 'react';
function Example()
const [count, setCount] = useState(0);
useEffect(() =>
document.title = `You clicked $count times`;
);
return (
<div>
<p>You clicked count times</p>
<button onClick=() => setCount(count + 1)>
Click me
</button>
</div>
);
useEffect用来干什么?使用这个Hook,相当于你告诉React你的组件需要在渲染之后执行一些操作。React将会把你传递给它的方法记住,并在DOM更新之后执行。在这个示例中,我们设置了文档标题,但其实也可以拉取数据或者调用其他必要的API。
为什么在组件内部调用useEffect?把useEffect放在一个组件内部让我们可以使用count这个state变量(或者属性)。我们不需要一个特定的API来读取它——它已经在方法作用域里了。Hooks拥抱了javascript的闭包,避免引入新的React的API。
是不是在每次渲染后都执行useEffect?是的!默认情况下,在第一次渲染和每一次更新后都会被运行到。不需要思考当前是加载还是更新,你只要把它看作在每一次渲染之后都会执行。React保证执行前DOM操作都已经更新完成了。
详细说明
现在我们已经对 effect 有了大致了解,下面这些代码应该不难看懂了:
function Example()
const [count, setCount] = useState(0);
useEffect(() =>
document.title = `You clicked $count times`;
);
我们声明了count这个state变量,然后告诉React我们需要使用一个effect方法。我们把一个方法传递给useEffect这个Hook。这个方法就是我们的effect。在effect内部,我们用document.title浏览器API修改了文档标题。我们可以在effect里读取count变量。当React渲染组件时,它会记住我们的effect,并在更新完DOM之后运行它。这意味着effect在每次渲染后都会被调用,包括第一次。
有经验的JavaScript开发者会发现我们传给useEffect的方法在每次渲染后不是同一个实例。这其实是有意为之的。实际上,这样做可以让我们在effect里读取count值且不用担心数据老化。每次重新渲染,都会有一个不同的effect来替代前一个。这样子使得effect更像是渲染结果的一部分——每一个effect都属于一次特定的渲染。
提示
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的
useLayoutEffect
Hook 供你使用,其 API 与 useEffect 相同。
需要清理的effect
之前我们看了不需要清理的effect。但是还是有一些场景是需要的。比如我们可能想建立一个数据源的订阅。这时候清理就十分必要了,不然会引起内存泄漏。比较一下class组件和Hooks的写法。
使用class的例子
在React的class里,我们一般在componentDidMount里建立订阅,在componentWillUnmount里清除。比如假设我们有一个ChatAPI模块可以让我们订阅某个朋友的在线状态。
class FriendStatus extends React.Component
constructor(props)
super(props);
this.state = isOnline: null ;
this.handleStatusChange = this.handleStatusChange.bind(this);
componentDidMount()
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
componentWillUnmount()
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
handleStatusChange(status)
this.setState(
isOnline: status.isOnline
);
render()
if (this.state.isOnline === null)
return 'Loading...';
return this.state.isOnline ? 'Online' : 'Offline';
注意到componentDidMount和componentWillUnmount里干的是相反的。生命周期回调迫使我们必须把相同意义逻辑代码分离在不同的地方。
使用Hooks的例子
我们看看用Hooks如何写这个例子。
你可能觉得我们需要把effect分开才能执行清理动作。但其实用useEffect可以把添加订阅和清理订阅绑在一起。如果你的effect返回一个方法,React会在清理的时候来执行它。
import React, useState, useEffect from 'react';
function FriendStatus(props)
const [isOnline, setIsOnline] = useState(null);
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup()
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
);
if (isOnline === null)
return 'Loading...';
return isOnline ? 'Online' : 'Offline';
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect?React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。我们稍后将讨论为什么这将助于避免 bug以及如何在遇到性能问题时跳过此行为。
注意
并不是必须为 effect 中返回的函数命名。这里我们将其命名为 cleanup 是为了表明此函数的目的,但其实也可以返回一个箭头函数或者给起一个别的名字。
回顾
我们已经知道了组件渲染后可以用useEffect来执行effect。一些effect可以返回用以清理的方法。
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () =>
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
);
其他的 effect 可能不必清除,所以不需要返回。
useEffect(() =>
document.title = `You clicked $count times`;
);
effect Hook 使用同一个 API 来满足这两种情况。
如果你对 Effect Hook 的机制已经有很好的把握,或者暂时难以消化更多内容,你现在稍后学习 Hook 的规则。
以上是关于使用 Effect Hook的主要内容,如果未能解决你的问题,请参考以下文章
[React] Use the React Effect Hook in Function Components