场景
我的目的是通过Effect
来模拟组件的componentDidMount
,在渲染完成之后,通过setTimeout
来处理操作,向keyIndex
中push一个新的元素,并更新keyIndex
,但是这个操作我确定只会执行一次。
错误代码如下:
const [keyIndex, setKeyIndex] = React.useState([]);
React.useEffect(() => {
setTimeout(() => {
console.log('err log')
let array = [].concat(keyIndex);
let key = timeLine[0].id;
array.push(key);
setKeyIndex(array)
},2000);
}, [setKeyIndex]);
代码中我加了一句console.log('err log')
,在组件被执行之后,控制台无限输出err log
问题原因
在Hooks中,虽然可以通过向useEffect
传递第二个参数(此处我是在结尾传递了[setKeyIndex]
),来达到在传递的数组没有变化时,不对组件进行render操作。
但是这个第二个参数是有限制的—这里的问题就是,第二个参数是引用类型
什么是引用类型:
- 值类型(基本类型):数值(number)、布尔值(boolean)、null、undefined、string(在赋值传递中会以引用类型的方式来处理)。
- 引用类型:对象、数组、函数。
根源
useEffect会比较前一次渲染和后一次渲染的值,即使两个值在数值上是相等的,但是在堆栈中对应的地址并不相同,所以 === 操作的时候,值肯定为false。所以引用类型比较不出来数据的变化,会不停地得到false值,并对组件进行render,造成死循环。
解决方法
const keyIndexRef = useRef(keyIndex);
keyIndexRef.current = keyIndex;
通过useRef
,将keyIndex
数据绑定到keyIndexRef.current
上,此时的keyIndexRef
是一个可变值盒子,他自身都是可以被更改的。useRef
的特点是会在每次渲染时返回同一个 ref
对象,这就解决了我们的render前后数据始终不会相等(===)的问题。
在我这里,修改后的代码如下:
React.useEffect(() => {
setTimeout(() => {
let array = [].concat(keyIndexRef.current);
let key = timeLine[0].id;
array.push(key);
setKeyIndex(array);
});
},[])
- 通过setData 接收函数作为参数,并利用闭包和参数来实现数据更新
用一个类似的例子来进行举例
const [flag, setFlag] = useState(false);
function dealClick() {
setFlag(!flag);
setTimeout(() => {
setFlag(flag => !flag);
}, 2000);
}
return (
<button onClick={dealClick}>{flag ? "true" : "false"}</button>
);
另外,如果useEffect
不放置任何参数,则在组件化的过程中会且仅执行一次。但是如果还是出现改变引用类型的错误的话,每次执行都会拿到的keyIndex
的默认初始值,实际setKeyIndex
的结果并不会被更新上去