We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
作者:Dave Ceddia 译者:前端小智 来源:daveceddia.
作者:Dave Ceddia
译者:前端小智
来源:daveceddia.
为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!
为了回馈读者,《大迁世界》不定期举行(每个月一到三次),现金抽奖活动,保底200,外加用户赞赏,希望你能成为大迁世界的小锦鲤,快来试试吧
想象一下:你有一个非常好用的函数组件,然后有一天,咱们需要向它添加一个生命周期方法。
呃...
刚开始咱们可能会想怎么能解决这个问题,然后最后变成,通常的做法是将它转换成一个类。但有时候咱们就是要用函数方式,怎么破? useEffect hook 出现就是为了解决这种情况。
useEffect
使用useEffect,可以直接在函数组件内处理生命周期事件。 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。来看看例子:
componentDidMount
componentDidUpdate
componentWillUnmount
import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; function LifecycleDemo() { useEffect(() => { // 默认情况下,每次渲染后都会调用该函数 console.log('render!'); // 如果要实现 componentWillUnmount, // 在末尾处返回一个函数 // React 在该函数组件卸载前调用该方法 // 其命名为 cleanup 是为了表明此函数的目的, // 但其实也可以返回一个箭头函数或者给起一个别的名字。 return function cleanup () { console.log('unmounting...'); } }) return "I'm a lifecycle demo"; } function App() { // 建立一个状态,为了方便 // 触发重新渲染的方法。 const [random, setRandom] = useState(Math.random()); // 建立一个状态来切换 LifecycleDemo 的显示和隐藏 const [mounted, setMounted] = useState(true); // 这个函数改变 random,并触发重新渲染 // 在控制台会看到 render 被打印 const reRender = () => setRandom(Math.random()); // 该函数将卸载并重新挂载 LifecycleDemo // 在控制台可以看到 unmounting 被打印 const toggle = () => setMounted(!mounted); return ( <> <button onClick={reRender}>Re-render</button> <button onClick={toggle}>Show/Hide LifecycleDemo</button> {mounted && <LifecycleDemo/>} </> ); } ReactDOM.render(<App/>, document.querySelector('#root'));
在CodeSandbox中尝试一下。
单击“Show/Hide”按钮,看看控制台,它在消失之前打印“unmounting...”,并在它再次出现时打印 “render!”。
Show/Hide
unmounting...
render!
现在,点击Re-render按钮。每次点击,它都会打render!,还会打印umounting,这似乎是奇怪的。
Re-render
render!
umounting
为啥每次渲染都会打印 'unmounting'。
unmounting
咱们可以有选择性地从useEffect返回的cleanup函数只在组件卸载时调用。React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。这实际上比componentWillUnmount生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。
cleanup
effect
useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。
与其将useEffect看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。
如果希望 effect 较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect的依赖关系。 如果其中一个依赖项自上次更改后,effect将再次运行。
const [value, setValue] = useState('initial'); useEffect(() => { // 仅在 value 更改时更新 console.log(value); }, [value])
上面这个示例中,咱们传入 [value] 作为第二个参数。这个参数是什么作用呢?如果value的值是 5,而且咱们的组件重渲染的时候 value 还是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。
[value]
value
5
[5]
(5 === 5)
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
[]
props
state
useEffect(() => { console.log('mounted'); return () => console.log('unmounting...'); }, [])
这样只会在组件初次渲染的时候打印 mounted,在组件卸载后打印: unmounting。
mounted
不过,这隐藏了一个问题:传递空数组容易出现bug。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。
bug
在这个例子中,一起来看下如何使用useEffect和useRef hook 将input控件聚焦在第一次渲染上。
useRef
input
import React, { useEffect, useState, useRef } from "react"; import ReactDOM from "react-dom"; function App() { // 存储对 input 的DOM节点的引用 const inputRef = useRef(); // 将输入值存储在状态中 const [value, setValue] = useState(""); useEffect( () => { // 这在第一次渲染之后运行 console.log("render"); // inputRef.current.focus(); }, // effect 依赖 inputRef [inputRef] ); return ( <input ref={inputRef} value={value} onChange={e => setValue(e.target.value)} /> ); } ReactDOM.render(<App />, document.querySelector("#root"));
在顶部,我们使用useRef创建一个空的ref。 将它传递给input的ref prop ,在渲染DOM 时设置它。 而且,重要的是,useRef返回的值在渲染之间是稳定的 - 它不会改变。
ref
ref prop
因此,即使咱们将[inputRef]作为useEffect的第二个参数传递,它实际上只在初始挂载时运行一次。这基本上是 componentDidMount 效果了。
[inputRef]
再来看看另一个常见的用例:获取数据并显示它。在类组件中,无们通过可以将此代码放在componentDidMount方法中。在 hook 中可以使用 useEffect hook 来实现,当然还需要用useState来存储数据。
useState
下面是一个组件,它从Reddit获取帖子并显示它们
import React, { useEffect, useState } from "react"; import ReactDOM from "react-dom"; function Reddit() { const [posts, setPosts] = useState([]); useEffect(async () => { const res = await fetch( "https://www.reddit.com/r/reactjs.json" ); const json = await res.json(); setPosts(json.data.children.map(c => c.data)); }); // 这里没有传入第二个参数,你猜猜会发生什么? // Render as usual return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } ReactDOM.render( <Reddit />, document.querySelector("#root") );
注意到咱们没有将第二个参数传递给useEffect,这是不好的,不要这样做。
不传递第二个参数会导致每次渲染都会运行useEffect。然后,当它运行时,它获取数据并更新状态。然后,一旦状态更新,组件将重新呈现,这将再次触发useEffect,这就是问题所在。
为了解决这个问题,我们需要传递一个数组作为第二个参数,数组内容又是啥呢。
useEffect所依赖的唯一变量是setPosts。因此,咱们应该在这里传递数组[setPosts]。因为setPosts是useState返回的setter,所以不会在每次渲染时重新创建它,因此effect只会运行一次。
setPosts
[setPosts]
setter
虚接着扩展一下示例,以涵盖另一个常见问题:如何在某些内容发生更改时重新获取数据,例如用户ID,名称等。
首先,咱们更改Reddit组件以接受subreddit作为一个prop,并基于该subreddit获取数据,只有当 prop 更改时才重新运行effect.
Reddit
subreddit
prop
// 从props中解构`subreddit`: function Reddit({ subreddit }) { const [posts, setPosts] = useState([]); useEffect(async () => { const res = await fetch( `https://www.reddit.com/r/${subreddit}.json` ); const json = await res.json(); setPosts(json.data.children.map(c => c.data)); // 当`subreddit`改变时重新运行useEffect: }, [subreddit, setPosts]); return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } ReactDOM.render( <Reddit subreddit='reactjs' />, document.querySelector("#root") );
这仍然是硬编码的,但是现在咱们可以通过包装Reddit组件来定制它,该组件允许咱们更改subreddit。
function App() { const [inputValue, setValue] = useState("reactjs"); const [subreddit, setSubreddit] = useState(inputValue); // Update the subreddit when the user presses enter const handleSubmit = e => { e.preventDefault(); setSubreddit(inputValue); }; return ( <> <form onSubmit={handleSubmit}> <input value={inputValue} onChange={e => setValue(e.target.value)} /> </form> <Reddit subreddit={subreddit} /> </> ); } ReactDOM.render(<App />, document.querySelector("#root"));
在 CodeSandbox 试试这个示例。
这个应用程序在这里保留了两个状态:当前的输入值和当前的subreddit。提交表单将提交subreddit,这会导致Reddit重新获取数据。
顺便说一下:输入的时候要小心,因为没有错误处理,所以当你输入的subreddit不存在,应用程序将会爆炸,实现错误处理就作为你们的练习。
各位可以只使用一个状态来存储输入,然后将相同的值发送到Reddit,但是Reddit组件会在每次按键时获取数据。
顶部的useState看起来有点奇怪,尤其是第二行:
const [inputValue, setValue] = useState("reactjs"); const [subreddit, setSubreddit] = useState(inputValue);
我们把reactjs的初值传递给第一个状态,这是有意义的,这个值永远不会改变。
reactjs
那么第二行呢,如果初始状态改变了呢,如当你输入box时候。
box
记住,useState是有状态的。它只使用初始状态一次,即第一次渲染,之后它就被忽略了。所以传递一个瞬态值是安全的,比如一个可能改变或其他变量的 prop。
使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。
与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。
然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同。
React
useLayoutEffect
虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect。
原文:https://daveceddia.com/useeffect-hook-examples/
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered:
useEffect 里是不能直接传 async function 的
Sorry, something went wrong.
No branches or pull requests
为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!
想象一下:你有一个非常好用的函数组件,然后有一天,咱们需要向它添加一个生命周期方法。
呃...
刚开始咱们可能会想怎么能解决这个问题,然后最后变成,通常的做法是将它转换成一个类。但有时候咱们就是要用函数方式,怎么破?
useEffect
hook 出现就是为了解决这种情况。使用
useEffect
,可以直接在函数组件内处理生命周期事件。 如果你熟悉 React class 的生命周期函数,你可以把useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。来看看例子:在CodeSandbox中尝试一下。
单击“
Show/Hide
”按钮,看看控制台,它在消失之前打印“unmounting...
”,并在它再次出现时打印 “render!
”。现在,点击
Re-render
按钮。每次点击,它都会打render!
,还会打印umounting
,这似乎是奇怪的。为啥每次渲染都会打印 '
unmounting
'。咱们可以有选择性地从
useEffect
返回的cleanup
函数只在组件卸载时调用。React 会在组件卸载的时候执行清除操作。正如之前学到的,effect
在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个effect
进行清除。这实际上比componentWillUnmount
生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。不完全的生命周期
useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。
与其将
useEffect
看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。阻止每次重新渲染都会执行 useEffect
如果希望
effect
较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect
的依赖关系。 如果其中一个依赖项自上次更改后,effect
将再次运行。上面这个示例中,咱们传入
[value]
作为第二个参数。这个参数是什么作用呢?如果value
的值是5
,而且咱们的组件重渲染的时候value
还是等于 5,React 将对前一次渲染的[5]
和后一次渲染的[5]
进行比较。因为数组中的所有元素都是相等的(5 === 5)
,React 会跳过这个effect
,这就实现了性能的优化。仅在挂载和卸载的时候执行
如果想执行只运行一次的
effect
(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的effect
不依赖于props
或state
中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。这样只会在组件初次渲染的时候打印
mounted
,在组件卸载后打印:unmounting
。不过,这隐藏了一个问题:传递空数组容易出现
bug
。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect
时失效,并且可能会导致一些奇怪的问题。只在挂载的时候执行
在这个例子中,一起来看下如何使用
useEffect
和useRef
hook 将input
控件聚焦在第一次渲染上。在顶部,我们使用
useRef
创建一个空的ref
。 将它传递给input
的ref prop
,在渲染DOM 时设置它。 而且,重要的是,useRef
返回的值在渲染之间是稳定的 - 它不会改变。因此,即使咱们将
[inputRef]
作为useEffect
的第二个参数传递,它实际上只在初始挂载时运行一次。这基本上是componentDidMount
效果了。使用 useEffect 获取数据
再来看看另一个常见的用例:获取数据并显示它。在类组件中,无们通过可以将此代码放在
componentDidMount
方法中。在 hook 中可以使用 useEffect hook 来实现,当然还需要用useState
来存储数据。下面是一个组件,它从Reddit获取帖子并显示它们
注意到咱们没有将第二个参数传递给
useEffect
,这是不好的,不要这样做。不传递第二个参数会导致每次渲染都会运行
useEffect
。然后,当它运行时,它获取数据并更新状态。然后,一旦状态更新,组件将重新呈现,这将再次触发useEffect
,这就是问题所在。为了解决这个问题,我们需要传递一个数组作为第二个参数,数组内容又是啥呢。
useEffect
所依赖的唯一变量是setPosts
。因此,咱们应该在这里传递数组[setPosts]
。因为setPosts
是useState
返回的setter
,所以不会在每次渲染时重新创建它,因此effect
只会运行一次。当数据改变时重新获取
虚接着扩展一下示例,以涵盖另一个常见问题:如何在某些内容发生更改时重新获取数据,例如用户ID,名称等。
首先,咱们更改
Reddit
组件以接受subreddit
作为一个prop,并基于该subreddit
获取数据,只有当prop
更改时才重新运行effect
.这仍然是硬编码的,但是现在咱们可以通过包装
Reddit
组件来定制它,该组件允许咱们更改subreddit
。在 CodeSandbox 试试这个示例。
这个应用程序在这里保留了两个状态:当前的输入值和当前的
subreddit
。提交表单将提交subreddit
,这会导致Reddit
重新获取数据。顺便说一下:输入的时候要小心,因为没有错误处理,所以当你输入的
subreddit
不存在,应用程序将会爆炸,实现错误处理就作为你们的练习。各位可以只使用一个状态来存储输入,然后将相同的值发送到
Reddit
,但是Reddit
组件会在每次按键时获取数据。顶部的
useState
看起来有点奇怪,尤其是第二行:我们把
reactjs
的初值传递给第一个状态,这是有意义的,这个值永远不会改变。那么第二行呢,如果初始状态改变了呢,如当你输入
box
时候。记住,
useState
是有状态的。它只使用初始状态一次,即第一次渲染,之后它就被忽略了。所以传递一个瞬态值是安全的,比如一个可能改变或其他变量的prop
。许许多多的用途
使用
useEffect
就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref
的值。与
componentDidMount
、componentDidUpdate
不同的是,在浏览器完成布局与绘制之后,传给useEffect
的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。然而,并非所有
effect
都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React
为此提供了一个额外的useLayoutEffect
Hook 来处理这类effect
。它和useEffect
的结构相同,区别只是调用时机不同。虽然
useEffect
会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的effect
。原文:https://daveceddia.com/useeffect-hook-examples/
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
交流
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered: