若是已经使用过 Hook,相信你必定回不去了,这种用函数的方式去编写有状态组件简直太爽啦。javascript
若是还没使用过 Hook,那你要赶忙升级你的 React(v16.8+),投入 Hook 的怀抱吧。html
至于 Hook 的好处这里就很少说了,上一篇已经讲过了——React Hook上车。java
Hook 虽好,操做不当但是容易翻车的哦。react
下面,咱们就来聊聊在使用过程当中可能遇到的坑吧......git
坑:useState的初始值,只在第一次有效github
证据:npm
当点击按钮修改name的值的时候,我发如今Child组件,是收到了,可是并无经过useState
赋值给name!segmentfault
const Child = ({data}) =>{ console.log('child render...', data) // 每次更新都会执行 const [name, setName] = useState(data) // 只会在首次渲染组件时执行 return ( <div> <div>child</div> <div>{name} --- {data}</div> </div> ); } const Hook =()=>{ console.log('Hook render...') const [name, setName] = useState('rose') return( <div> <div> {count} </div> <button onClick={()=>setName('jack')}>update name </button> <Child data={name}/> </div> ) }
上面咱们已经知道了useState()
只会在第一次渲染的时候才执行,那么这有什么实用价值吗?答案:能够把第一次 render 前执行的代码放入其中。数组
例如:缓存
const instance = useRef(null); useState(() => { instance.current = 'initial value'; });
相似 class component 里的constructor
和componentWillMount
。
啥?你还不知道 immutable 是个啥?甩手就是两个连接:Immutable.js 了解一下、Immutable 详解及在 React 实践。
什么是 Immutable Data?
首先,你要知道 JavaScript 中的对象通常是可变的(Mutable Data),由于使用了引用赋值。
Immutable Data 就是一旦建立,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操做都会返回一个新的 Immutable 对象。
虽然 class component 的 state 也提倡使用 immutable data,但不是强制的,由于只要调用了setState
就会触发更新。
可是使用useState
时,若是在更新函数里传入同一个对象将没法触发更新。
证据:
const [list, setList] = useState([2,32,1,534,44]); return ( <> <ol> {list.map(v => <li key={v}>{v}</li>)} </ol> <button onClick={() => { // bad:这样没法触发更新! setList(list.sort((a, b) => a - b)); // good:必须传入一个新的对象! setList(list.slice().sort((a, b) => a - b)); }} >sort</button> </> )
以前就说过,Hook 产生问题时,90%都是闭包引发的。下面就来看一下这个诡异的bug:
function DelayCount() { const [count, setCount] = useState(0); function handleClickAsync() { setTimeout(function delay() { setCount(count + 1); // 问题所在:此时的 count 为1s前的count!!! }, 1000); } function handleClickSync() { setCount(count + 1); } return ( <div> {count} <button onClick={handleClickAsync}>异步加1</button> <button onClick={handleClickSync}>同步加1</button> </div> ); }
点击“异步加1”按键,而后当即点击“同步加1”按钮。你会惊奇的发现,count 只更新到 1。
这是由于 delay() 是一个过期的闭包。
来看看这个过程发生了什么:
delay() 是一个过期的闭包,它使用在初始渲染期间捕获的过期的 count 变量。
为了解决这个问题,可使用函数方法来更新 count
状态:
function DelayCount() { const [count, setCount] = useState(0); function handleClickAsync() { setTimeout(function delay() { setCount(count => count + 1); // 重点:setCount传入的回调函数用的是最新的 state!!! }, 1000); } function handleClickSync() { setCount(count + 1); } return ( <div> {count} <button onClick={handleClickAsync}>异步加1</button> <button onClick={handleClickSync}>同步加1</button> </div> ); }
上一篇文章中咱们提到过:useEffect的 callback 函数要么返回一个能清除反作用的函数,要么就不返回任何内容。
而 async 函数返回的是 Promise 对象,那咱们要怎么在 useEffect 的callback 中使用 async 呢?
最简单的方法是IIFE
(自执行函数):
useEffect(() => { (async () => { await fetchSomething(); })(); }, []);
useEffect(()=>{ setCount(count); }, [count]);
依赖 count,callback 中又 setCount(count)。推荐启用 eslint-plugin-react-hooks
中的 exhaustive-deps
规则。此规则会在添加错误依赖时发出警告并给出修复建议。
有时候,咱们须要将函数做为依赖项传入依赖数组中,例如:
// 子组件 let Child = React.memo((props) => { useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id]); return ( <div>{props.id}</div> ); }); // 父组件 let Parent = () => { let [id, setId] = useState(0); let [count, setCount] = useState(0); const onChange = (id) => { // coding setCount(id); } return ( <div> {count} <Child onChange={onChange} id={id} /> // 重点:这里有性能问题!!! </div> ); };
代码中重点位置,每次父组件render,onChange引用值确定会变。所以,子组件Child一定会render,子组件触发useEffect,从而再次触发父组件render....循环往复,这就会形成死循环。下面咱们来优化一下:
// 子组件 let Child = React.memo((props) => { useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id]); return ( <div>{props.id}</div> ); }); // 父组件 let Parent = () => { let [id, setId] = useState(0); let [count, setCount] = useState(0); const onChange = useCallback(() => { // 重点:经过useCallback包裹一层便可达到缓存函数的目的 // coding }, [id]); // id 为依赖值 return ( <div> {count} <Child onChange={onChange} id={id} /> // 重点:这个onChange在每次父组件render都会改变! </div> ); };
useCallback
将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给通过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将很是有用。
useCallback(fn, deps)
至关于useMemo(() => fn, deps)
useEffect
里面使用到的 state 的值, 固定在了useEffect
内部,不会被改变,除非useEffect
刷新,从新固定 state 的值。
useRef
保存任何可变化的值,.current
属性老是取最新的值。
function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => { // Set the mutable latest value latestCount.current = count; setTimeout(() => { // Read the mutable latest value console.log(`You clicked ${latestCount.current} times`); }, 3000); });
以上只是收集了一部分工做中可能会遇到的坑,大体分为2种:
之后遇到其余的问题会继续补充...
参考:
react hooks踩坑记录