如果已经使用过 Hook,相信你一定回不去了,这种用函数的方式去编写有状态组件简直太爽啦。
如果还没使用过 Hook,那你要赶紧升级你的 React(v16.8+),投入 Hook 的怀抱吧。
至于 Hook 的好处这里就不多说了,上一篇已经讲过了——React Hook上车。
Hook 虽好,操作不当可是容易翻车的哦。
下面,我们就来聊聊在使用过程中可能遇到的坑吧......
坑:useState的初始值,只在第一次有效
证据:
当点击按钮修改name的值的时候,我发现在Child组件,是收到了,但是并没有通过useState
赋值给name!
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踩坑记录
使用 JS 及 React Hook 时需要注意过时闭包的坑(文中有解决方法)
原文:https://www.cnblogs.com/chenwenhao/p/12639077.html