Open shaozj opened 4 years ago
本文从实际示例出发,分析 hooks 在使用时容易遇到的陷阱,以及提出如何避免掉进 hooks 陷阱的方法。
下方的代码存在什么问题?
import React, { useState, useEffect } from 'react'; function App() { const [data, setData] = useState(); useEffect(async () => { const result = await fetch( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }, []); return ( <div> {data} </div> ); } export default App;
答:会出现 warning,useEffect 应该返回一个清理函数或者什么都不返回。 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => ...) are not supported, but you can call an async function inside an effect. 正确写法:
useEffect(() => { const fetchData = async () => { const result = await fetch( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }; fetchData(); }, []);
下方强制更新组件的代码能 work 吗?
const setUpdate = useState()[1]; const forceUpdate = () => setUpdate(); forceUpdate();
答:不能 work,在函数式组件中,如果 setState 没有修改对应 state 的值(浅比较相等),那么不会触发 re-render,这点和 class 组件不同,class 组件只要 setState 就会触发 re-render,因为它每次会合并 state 中所有属性,生成一个新的 state。
正确写法:
const setUpdate = useState()[1]; const forceUpdate = () => setUpdate({}); forceUpdate();
下方代码存在什么问题?
function Timer() { const [count, setCount] = React.useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(intervalId); }, []); return ( <div>The count is: {count}</div> ); }
答:这是一个最常见的说明 useEffect 陷阱的例子了。在上方的代码运行后,count 首先为 0,一秒后更新为 1,之后就不变化了。这是由闭包导致的,useEffect 中的 count 值始终为 0,setInterval 每次执行都将其更新为 1。 方案1:依赖数组中每个依赖的外部变量都添加进去(性能不佳)
function Timer() { const [count, setCount] = React.useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(intervalId); }, [count]); return ( <div>The count is: {count}</div> ); }
方案2:使用 useRef 生成一个可变对象,记住我们的变量(个人最推荐,适用于所有需要依赖外部变量的情况)
function Timer() { const [count, setCount] = React.useState(0); const countRef = React.useRef(0); useEffect(() => { const intervalId = setInterval(() => { countRef.current = countRef.current + 1; setCount(countRef.current); }, 1000); return () => clearInterval(intervalId); }, []); return ( <div>The count is: {count}</div> ); }
方案3:如果是依赖外部变量做 setState,可以用 functional-updates 然后把依赖的变量去除(很适合这个例子,但是适用范围有限)
function Timer() { const [count, setCount] = React.useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(count => count + 1); }, 1000); return () => clearInterval(intervalId); }, []); return ( <div>The count is: {count}</div> ); }
我想使用类似于 component 组件中的 this 该怎么办,用它跟踪变量的变化,但是不触发 re-render. 答:这个问题前面其实已经给出答案了,那就是使用 useRef。
以下代码存在什么问题?该如何修改?
let stopPolling = false; function pay({ visible }) { const [data, setData] = useState(); function polling() { if (stopPolling) { return; } fetch().then( if (res.finished) { setData(res.data); } else { setTimeout(polling, 1000); } ); } useEffect(() => { stopPolling = !visible; }, [visible]); return <div onClick={polling}>{data}</div>; }
最后这个问题留作课后作业吧
本文从实际示例出发,分析 hooks 在使用时容易遇到的陷阱,以及提出如何避免掉进 hooks 陷阱的方法。
问题一
下方的代码存在什么问题?
答:会出现 warning,useEffect 应该返回一个清理函数或者什么都不返回。 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => ...) are not supported, but you can call an async function inside an effect.
正确写法:
问题二
下方强制更新组件的代码能 work 吗?
答:不能 work,在函数式组件中,如果 setState 没有修改对应 state 的值(浅比较相等),那么不会触发 re-render,这点和 class 组件不同,class 组件只要 setState 就会触发 re-render,因为它每次会合并 state 中所有属性,生成一个新的 state。
正确写法:
问题三
下方代码存在什么问题?
答:这是一个最常见的说明 useEffect 陷阱的例子了。在上方的代码运行后,count 首先为 0,一秒后更新为 1,之后就不变化了。这是由闭包导致的,useEffect 中的 count 值始终为 0,setInterval 每次执行都将其更新为 1。
方案1:依赖数组中每个依赖的外部变量都添加进去(性能不佳)
方案2:使用 useRef 生成一个可变对象,记住我们的变量(个人最推荐,适用于所有需要依赖外部变量的情况)
方案3:如果是依赖外部变量做 setState,可以用 functional-updates 然后把依赖的变量去除(很适合这个例子,但是适用范围有限)
问题四
我想使用类似于 component 组件中的 this 该怎么办,用它跟踪变量的变化,但是不触发 re-render.
答:这个问题前面其实已经给出答案了,那就是使用 useRef。
问题五
以下代码存在什么问题?该如何修改?
最后这个问题留作课后作业吧