react Hooks的一些理解
hooks出来也挺久了,断断续续也用了一段时间,是时候写点文档之类的东西来补充一下认知了,因为我发现在写文档的时候经常能发现之前发现不了的盲点,帮助最近深入理解。
hooks有两个强依赖:1、function组件;2、setXxx,其中set后的第一个字母必须大写;
-
从最简单的开始吧:
useState,总的来说这个可以理解为class组件的setState
import React, { useState } from 'react';
export default FunctionTest = () => {
const [ value, setValue ] = useState(0);
const add = () => {
setValue(val+=1);
}
return (
<div>
<p>{value}</p>
<button onClick={add}>add</button>
</div>
)
}这样每次点击add按钮数值就会加一,这有个问题是value和setValue必须是在数组里声明,第一位是值,第二位是方法;我之前看过一些解析的说为什么用数组而不是对象,主要是为了命名方便,如果是对象那么value和setValue的值名字就是固定了,这显然是很不灵活的方式;
还有一个需要注意的地方是如果value的值是引用类型而不是基础类型的话,不修改引用是无法触发rerender的,比如以下例子就会没有效果;
const [ arr, setArr ] = useState([1]);
setArr(arr.push(2));这样是无法触发rerender的,就是说即使调用了这个方法,你的页面也不会有更新;因为是在原数组进行的操作,react默认引用地址不发生改变就不会去rerender,这个是和class组件的一个重大区别,因为class组件中只要调用了setSate,即使什么都不做也会触发render,function组件中是行不通的,不管是对象还是数组,想触发rerender就必须去set返回一个新的引用。
-
第二个很常用的就是useEffect。
import React, { useEffect, useState } from 'react';
export default FunEffect = () => {
const [ value, setValue ] = useState(0);
useEffect(() => {
// 模拟一个延迟加载
setTimeout(() => {
const data = 100;
setValue(data);
}, 100)
}, [])
return(
<div>{value}</div>
);
};这里模拟了一个100毫秒的延迟之后加载val的场景,所以useEffect可以看做是componentDidMount,componentDidUpdate和componentWillUnmount的结合,怎么来区分呢?那就要靠第二个参数的传入值了。如果传入一个[],就代表这个副作用和任何值都不关联,就可以认为这个副作用 === componentDidMount,但是如果我们传了某个值,他就和这个值产生了关联。
useEffect(() => {
// 模拟一个延迟加载
setTimeout(() => {
const data = 100;
setValue(data);
}, 100)
}, [value]);那么你会发现不管你在别的地方如何setValue,最终你会得到value的值为100;
如果我们用componentDidUpdate来模拟的话应该是这样的:
componentDidUpdate(prevProps, prevState){
if(prevState.value !== this.state.value){
// 我就不写时间延迟了,加上也是一样的
this.setState({ value: 100});
}
}那么如果我们数组传入的是一个引用类型的值呢?react会做一层浅比较,引用的值变了依旧会触发副作用;
最后就是如何模拟componentWillUnmount,useEffect有一个return的可选值,这个return就是让你在组件销毁的时候要做的事情:
// 偷懒借用一个官方的例子
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它
注意:effect不能使用async方式
// 这样式错误的,会导致react直接报错
useEffect(async() => {
const data = await xxx();
}, [])正确的方式是这样的:
useEffect(() => {
getFeachData();
}, []);
const getFeachData = async () => {
const feachData = await xxx();
setXXX();
} -
基础hooks最后一个是useContext
这个说实话我实际项目中没用到过,所以也只能搬一点官网之类的教程来说了:
const value = useContext(MyContext);
接收一个 context 对象(
React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的<MyContext.Provider>
的value
prop 决定。当组件上层最近的
<MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给MyContext
provider 的 contextvalue
值。即使祖先使用React.memo
或shouldComponentUpdate
,也会在组件本身使用useContext
时重新渲染。这个呢,就比较像class中的context,但是很遗憾我class组件中也基本没用过,所以就只能粗略说下。
其实说起来也很简单的,应用场景就是跨组件传值的时候,把想要传下去的数据用Provider包裹,那么他的所有后代组件都能获取到这个数据,然后祖代的数据更新会触发到所有引用该数据的子组件的rerender。
-
额外hooks不会都说,只说我用过的,怕误导人,首先是useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);官网的例子很简单,但是经验告诉我们,看起来简单的用法会在某个不知名的地方给与我们沉重的打击,我感觉useCallback和useMemo就是很真实的案例。
首先看到用法类似与副作用,会传入一些值,这些值也和副作用类似,是数组的数据变化会引起callback中的数据变化。就是说在callback中用的数据要在数组中传入,否则你获取的就是他的“快照”,这个快照让我理解了一段时间才明白,就是类似与shouldComponentUpdate中的prevState,就是上一次的值。举个例子:
const [ val, setVal ] = useState({ def: { val: 1 } });
const [ test, setTest ] = useState(0);
const add = useCallback(() => {
console.log(test);
const nextVal = Object.assign({}, {def: { val: val.def.val + 1 }});
setVal(nextVal);
}, [val]);
const testFun = () => {
setTest(test+1)
}
return (
<div className="App">
<header className="App-header">
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<p>{val.def.val}</p>
<button onClick={add}>add</button>
<button onClick={testFun}>test</button>
</header>
</div>
);类似这样 我们随便点几下test按钮,然后点add的时候发现第一次打印的test是0,之后才变化,这样的做法官方说是为了性能,减少不必要的rerender,但是确实增加很多理解成本。而且这个只是在本组件中,但是实际中会有很多父子组件的交互,并且可能很多时候是class组件和function组件的混合,这种情况会有一些反直觉的问题。
具体可以看这里;
然后useMemo和useCallback比价类似,官方也说了:
useCallback(fn, deps)
相当于useMemo(() => fn, deps)
。基本就是一个返回function,一个返回值的区别;
-
还有就是useRef
const refContainer = useRef(initialValue);
可以参见我的这篇文章;
其余的我没有实际用过,也没反向有什么特殊的坑,就暂时不介绍了,怕误人子弟(逃。