useEffect 是 React Hooks 的核心,要保证理解它的运行机制和正确的使用方法才能避免这样那样的坑。在之前的工做中,由于它我碰到过无数个坑,好比拿到的值是旧的,该执行的时候不执行,不应执行的时候执行了……javascript
因此为了不如上尴尬、节省绞尽脑汁找 bug 的时间、保护我们的发际线,必定要认真学会如何正确使用 useEffect hook。java
俗话说,知已知彼,百战不殆,咱们先了解下神马是 Effect。其实你们在开发过程当中或多或少的都接触过 side effect 的概念,即ajax
useEffect
则是专门用来编写
目前市面上的文章,包括官方文档都让咱们把 useEffect
想象成 componentDidMount
, componentDidUpdate
,componentWillUnmount
三个生命周期的结合体。其实并否则,若是非要把 useEffect
的运行机制往生命周期上靠,会形成一些逻辑上的困惑,进而产生 bug。咱们所要作的,就是把 useEffect
当成一个全新的特性,专门为函数式组件服务的,这样用起来才不会迷茫。下面咱们经过实例来演示它的各类用法。json
useEffect
必然会在 render 的时候执行一次,其余的运行时机取决于如下状况:数组
useEffect
hook 接受两个参数,第一个是要执行的代码,第二个是一个数组,指定一组依赖的变量,其中任何一个变量发生变化时,此 effect 都会从新执行一次。useEffect
的执行代码中能够返回一个函数,在每一次新的 render 进行前或者组件 unmount 之时,都会执行此函数,进行清理工做。咱们先看一个简单的例子,想看完整代码和随意把玩的,请点击下边按钮浏览器
咱们首先看最顶层 <App />
的代码:app
function App() {
const [showList, setShowList] = useState(false);
const [postCount, setPostCount] = useState(5);
return (
<div className="App"> <button onClick={() => setShowList(!showList)}> {showList ? "隐藏" : "显示"} </button> <button onClick={() => setPostCount(previousCount => previousCount + 1)}> 增长数量 </button> {showList && <PostList count={postCount} />} </div> ); } 复制代码
此组件用来显示一系列的文章列表,以及控制文章列表是否显示的按钮和控制显示多少条文章的按钮。咱们用 showList
state 来控制 <PostList />
的显示与否。这是为了让 <PostList />
组件 unmount 再 render ,以证实每次它 render 和 unmount 的时候,useEffect
hook 都会跑一次。 <PostList />
组件的代码以下:异步
function PostList({ count = 5 }) {
useEffect(() => {
let p = document.createElement("p");
p.innerHTML = `当前文章数量:${count}`;
document.body.append(p);
});
return (
<ul> {new Array(count).fill("文章标题").map((value, index) => { return ( <li key={index}> {value} {index + 1} </li> ); })} </ul>
);
}
复制代码
该组件展现了一个 <ul>
列表,为了简单起见,生成了一些无聊的文章标题。咱们重点来看一下 useEffect
所作的操做:async
p
元素p
的文本为当前文章的数量p
到 body
的最后在这里,此 effect 并无返回任何值,也没有给它传递任何一个参数,那会是什么样的效果呢?ide
答案是,此 effect 会在每次 count
或 showList
改变时每点击一次 显示
或 增长数量
按钮,咱们新追加的 p
都会再追加一次。这也是形成内容泄露的坑,若是咱们在这里添加了太多耗内存的东西而没有清理,不用多久浏览器就崩溃了~ 解决方法很简单,给 useEffect
添加一个返回值,并在里边删除咱们追加的 p
元素便可:
useEffect(() => {
let p = document.createElement("p");
p.innerHTML = `当前文章数量:${count}`;
document.body.append(p);
return () => {
p.remove();
};
});
复制代码
这样咱们在点击按钮的时候,确保只有一个 p
在当前页面上。看,这样写起来是否是比分散在 componentDidMount
和 componentWillUnmount
中方便多了?咱们能够方便的在同一个做用域中方便的拿到 p
的引用,直接删除它便可。
为了继续深刻 useEffect
hook,我仿照实际工做遇到的状况,编写了一个例子,这里咱们用 useEffect
进行数据抓取,一样的显示博客文章列表,完整代码请点击下方按钮查看:
在本例中,<PostList />
组件的代码作了一些修改,首先咱们定义两个新的 state:
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
复制代码
咱们一开始以为 fetch
是异步操做,那么得给 useEffect
hook 传递个 async 的函数吧?错,传递给 useEffect 的函数不能是 async 的, 由于 async 的本质是返回一个 Promise,而 useEffect
惟一接收的返回值是个函数。使用 async 会收到如下异常:
Warning: An effect function must not return anything besides a function, which is used for clean-up.
// 错误
useEffect(async () => {
// const response = await fetch("https://jsonplaceholder.typicode.com/posts");
});
复制代码
正确的写法是,把抓取数据的逻辑定义到一个单独的函数中,而后在 useEffect
中调用它:
useEffect(() => {
// const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const loadPosts = async () => {
setLoading(true);
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${count}`
);
const data = await response.json();
setPosts(data);
setLoading(false);
};
loadPosts();
}, [count]);
复制代码
它所作的操做是,在请求数据前,把 loading
状态设置为 true,而后根据 count
的值去取对应数量的文章列表,把返回值更新到 posts
state 中,再把 loading
设置为 false。最后根据 loading
的状态,咱们显示 加载中
或 文章列表
:
if (loading) return <div>loading...</div>;
return (
<ul> {posts.slice(0, count).map((post, index) => { return <li key={post.id}>{post.title}</li>; })} </ul>
);
复制代码
上边的例子中咱们给 useEffect
传递了第二个参数,并把 count
做为依赖的值,每当 count
变化时,此 effect 都会从新执行一次,去加载新的数据。另外,若是咱们隐藏列表,再点击 显示
按钮时,effect 也会再跑一次,由于点击隐藏时,<PostList />
组件被 unmount ,而后再次显示时会从新 render ,咱们能够根据 loading...
这个标志就能够看出来了。
若是咱们去掉第二个参数,那么就会陷入死循环的坑,为何呢?由于 effect 执行时,会更新 posts
和 loading
这两个 state,而 state 变化时,组件又会从新 render 一次,根据 useEffect
在每次 render 必执行一次的定律不可贵出结论。
那么若是咱们给它一个空数组呢?那就不管怎么点击增长数量,此 effect 都不会从新执行,致使永远只加载默认 5 篇文章。
咱们能够再试试添加一个其余属性来测试 useEffect
依赖数组的特性。在 <App />
组件中咱们添加一个布局状态 vertical
和修改布局的按钮,用来控制 <PostList>
组件的横向、纵向布局:
// APP
function App() {
// 其它代码省略
const [vertical, setVertical] = useState(true);
return (
<div className="App"> {/* 其它代码省略 */} <button onClick={() => setVertical(prev => !prev)}>更改布局</button> {showList && <PostList count={postCount} vertical={vertical} />} </div> ); } // PostList function PostList({ count = 5, vertical = false }) { // 其它代码省略 useEffect(() => { // const response = await fetch("https://jsonplaceholder.typicode.com/posts"); const loadPosts = async () => { setLoading(true); const response = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=${count}` ); const data = await response.json(); setPosts(data); setLoading(false); }; loadPosts(); }, [count, vertical]); // 在这里添加 vertical 做为依赖 // 其它代码省略 } 复制代码
在这里咱们给第二个参数添加了 vertical
依赖,这样每次点击 更改布局
按钮时,文章列表都会加载一次,这种适合在布局改变时须要从新请求数据的状况:
若是不须要从新加载数据,只须要把 vertical
从依赖数组里去掉就能够了。
看看你们对频繁使用的 useEffect
的用法用对了没有?来标一下重点:
componentDidMount
, componentDidUpdate
,componentWillUnmount
三个生命周期的结合体哦(人家有本身的想法)。useEffect
中更新了 state,且没有指定依赖数组,或 state 存在于依赖数组中,就会形成死循环。你们掌握了吗?有什么问题欢迎评论或私信我!若是以为文章有帮助请关注博主我哦,感谢,比心。