如何获取函数式组件的上一个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属性的地方,都会受到影响。

说到底,这还是一个传引用的使用案例。

#react

SideEffect is a blog for front-end web development.
Code by Axiu / rss