React Hooks 方式下 props 到 state 的值同步
React Hooks 方式下 props 到 state 的值同步
考虑如下的问题: 页面有两个计时器 A,B,两个计时器都能自由计数。但 B 的初始值由 A 提供,一旦 A 的值变化后,立即同步给 B。
这里涉及到将 props 值变化的检测和将变化后的值同步到 state 的问题。
Class 组件的实现
传统 Class 方式下,很容易想到即将废弃的生命周期函数 UNSAFE_componentWillReceiveProps
,将 nextProps
与当前的 props
比较便可知道 A 计数器的值是否发生了变化。
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.initialCount !== this.props.initialCount) {
this.setState({
count: nextProps.initialCount
})
}
}
检测到变化后通过 setState
立即将最新的值同步到了 state 上,完成了功能。一套流程下来是很熟悉的操作。但既然已经不提倡使用 UNSAFE_componentWillReceiveProps
,肯定有其他官方推荐的方式。
React 官方文档确实有相应描述:
If you used componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
针对这里需要将 props 值同步到 State 的情况,这里不讨论了。
Hooks 方式的实现
使用 Hooks 方式时,因为是函数类型的组件,是没有相应生命周期来做这个事情的。如果单单只是检测 props 的变化,可以这样写:
useEffect(() => {
console.log('initialCount changed', props.initialCount);
}, [props.initialCount])
useEffect(fn, option)
会在每次 render 后执行,第二个参数 [props.initialCount]
则限定其只在 props.initialCount
变化时才执行,完美符合文章开头的要求。
因为是在 render 后执行,所以此时拿到的是最新的 props 值。如果想要获取旧数据,则需要借助 useRef
。
Class 组件下,ref
用来绑定对 DOM 的引用,hooks 方式下,根据官网文档的描述,它的作用就灵活些了,并不限定于引用 DOM。通过 useRef
创建的对象其 current
属性可存储任意值,作用等同于 Class 上的静态属性。
联想到 useEffect
是在 render 后执行,所以可在 useEffect
中将新的 props 同步到 ref 上,而每次 render 使用的 ref 是同步之前的旧值,这样能够在组件中同时获取到新旧 props。下面看代码比较直观:
以下示例代码来自 React 官方文档
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
+ prevCountRef.current = count;
});
+ const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
至此,解决了技术的问题,结合开头的需求,计数器可以这么写:
import React, { useState, useRef, useEffect } from 'react';
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount)
const prevCountRef = useRef(initialCount);
useEffect(() => {
prevCountRef.current = initialCount;
})
if (initialCount !== prevCountRef.current && count !== initialCount) {
setCount(initialCount)
}
return <div>
<p>counter2:{count}</p>
<div>
<button onClick={() => { setCount(count + 1) }}>+</button>
</div >
</div >
}
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<h3>hook ver</h3>
<p>counter1:{count}</p>
<div>
<button onClick={() => { setCount(count + 1) }}>+</button>
</div>
<Counter initialCount={count}></Counter>
</div>
);
}
export default App;
总结
针对在函数组件中需要获取旧数据的问题,目前是使用 useRef
间接实现,但从文档上的描述来看,不排除 React 未来的版本中会自带类似 usePrevious
这样命名的 hook 开箱即用,就没有那么麻烦了。毕竟,获取上一次的值也是比较常见的操作。