如何获取函数式组件的上一个state(props)
2020年1月30日 • ... • ☕️ 3 min read
当react里还只有class component的时候,我们通常会使用componentWillUpdate
或者componentWillReceiveProps
来获取将要变更的状态,并与当前的状态对比。这在某些情况下很有用,比如只有状态变更时才触发的一些操作或者更新。关于二者的区别不具体说了,文档很多,而且也用的少了。
时间来到函数式组件(function component)的天下,state变更、组件didMount都能用hooks解决,但是一直也没见一个usePreviousState/Props的钩子出来,这就让人很郁闷。因为函数式组件的一个问题(?)是没有this
,所以没有作用域,自然也无处保存状态和变量。所有的useState带来的状态变更,实际都是重新调用一遍函数,然后取新的结果。
那么有没有办法获取之前的状态呢?有,使用useRef获取之前状态。
关于useRef的特性
首先,useRef并非只能用于DOM的ref,它和createRef创建的ref是不一样的。听起来有点绕,只需要记住,它是一个对象,该对象利用自带的current属性来保存任意值。所以,不是只有createRef或者forwardRef传入的对象才能被它使用,任何值都可以。
除此之外,要记住useRef保存了一个贯穿组件生命周期的对象,且它不会触发re-render。只要不手动更改current属性的内容,那么它的内容不会改变(很方便放个observer或者timer啥的)。
举个例子
新建了一个组件,接收名为type的参数,并判断“type是否变更”来触发state更新。比如tab切换的容器等带状态的组件都会用到这个逻辑。
// 存储一个类型,当传入新的类型,执行变更
const typeRef = useRef(); // 暂存type
useEffect(() => {
if (typeRef.current !== props.currentType) {
typeRef.current = props.currentType;
// 执行其他state变更
}
}, [props.currentType]);
问题
1、既然函数式组件不保存状态,那么useRef存储在哪里
useRef的调用栈如下:
useRef
-mountRef(或者updateRef)
-mountWorkInProgressHook
这里把hooks存储到一个workInProgressHook链表里。用memorizedState
属性存储ref。并使用Object.seal(ref)
防止对象属性的添加和删除。
整个workInProgressHook
,都保存在内存,操作会在某个恰当的时机统一更新/修改/替换链表节点,以保证数据是一致的。
2、为什么要用ref.current
来保存,直接存给ref不行吗
抛开代码层面(useRef使用了Object.seal(ref)
来防止修改),如果可以修改的话,那么会造成什么情况?
如果可以直接存储到ref,就是
let someRef = useRef();
someRef = 'foo';
此时,someRef存储的不再是一个包含了current
属性的对象,而是一个字符串。接下来会把这个值直接存入memorizedState
里。然后,someRef作为一个局部变量,你可以任意更改它对值,但是更改不会对memorizedState
里的值造成影响。
但是如果存入的是一个对象,那么你只要改变了current
,那所有使用了current
属性的地方,都会受到影响。
说到底,这还是一个传引用的使用案例。