针对刚从class转到hooks同学使用,或者有class组件转Function组件的需求。使用起来简单粗暴,和class版本的state基本一致。
useState创建state和class版本的state有什么区别
useState导出的setState方法,传入的参数会直接覆盖,而class中的setState会进行一个浅合并。
class的setState
class InnerComp extends React.Component{
state = {
a: 1,
b: {
c: 1,
d: 2
}
}
componentDidMount(){
setTimeout(() => {
this.setState({ b: { c: 3, d: 4, e: 5 } })
}, 1000)
}
render(){
return (
<div>
<div>stateByClassComponent:{JSON.stringify(this.state)}</div>
</div>
)
}
}
输出结果
可以看到newState
是和oldState
进行浅合并的,所以oldState
中的属性a
被保留了下来。
hooks-useState实现
const App = () => {
const [state, setState] = useState({ a: 1, b: { c: 1, d: 2 } })
useEffect(() => {
setTimeout(() => {
setState({ b: { c: 3, d: 4, e: 5 } })
}, 1000)
}, [])
return (
<div>
<div>state: {JSON.stringify(state)}</div>
</div>
)
}
可以看到newState
直接覆盖了oldState
,所以newState
中没有属性a
了。
useClassState -> 代码实现
import { useState, useRef, useMemo, useCallback } from 'react';
type pick<T> = {
[P in keyof T]?: T[P];
};
export type State<T> = pick<T> | ((state: T) => pick<T>);
export type Dispatch<T> = (state: State<T>, cb?: (current: T) => void) => void;
export type UseStateClass<T> = (initialState: T) => [T, Dispatch<T>];
export default <T>(initialState: T) => {
const [state, setState] = useState<T>(initialState);
const stateRef = useRef<T>(initialState);
const dispatch: Dispatch<T> = useCallback((newState: State<T>, cb?: (current: T) => void) => {
const prevState = stateRef.current;
const relNewState = newState instanceof Function ? newState(prevState) : newState;
let isAllSame = true;
for (let key in relNewState) {
if (prevState[key] !== relNewState) {
isAllSame = false;
break;
}
}
if (isAllSame) {
return;
}
const finState = {
...prevState,
...relNewState,
};
setState(finState);
stateRef.current = finState;
cb && setTimeout(() => cb(stateRef.current), 0);
}, []);
return useMemo<[T, Dispatch<T>, { current: T }]>(
() => [state, dispatch, stateRef],
[dispatch, state]
);
};
实际效果
const App = () => {
const [classState, setClassState] = useClassState({ a: 1, b: { c: 1, d: 2 } })
useEffect(() => {
setTimeout(() => {
setClassState({ b: { c: 3, d: 4, e: 5 } })
}, 1000)
}, [])
return (
<div>
<div>classState: {JSON.stringify(classState)}</div>
</div>
)
}
和class版本的setState一致,进行了浅合并。
使用说明
// 创建state时,导出数据格式为数组
// 0->state本体,1->setState方法,2->stateRef可以永远拿到最新的state
const [state, setState, stateRef] = useClassState({ data: {name: 'sh', age: 11} } );
// setState方法
// 第一个参数可以是state也可以是function。
// setState(newState);
// setState((prevState) => newState);
// 第二个参数callback可选,类型: (state) => void;。会把最新的state当做实参传入cb。
// 第二个参数调用时机,和class的setState类似,在上树后调用。(但实际上新版本的react中,调用时机可能不太准确,应为setState可打断机制)
// setState({ isLoading: true }, (state) => {console.log(state};)