场景
我们需要一个状态更新后执行一段逻辑,该逻辑只在更新状态后(不包括初始化)时执行,该逻辑需要用到状态更新前后的值。
用基础hooks,useEffect也能实现需求不过需要额外的处理.
- 判断时更新时触发还是初始化时触发,过滤掉初始化的副作用
- 用一个额外的变量记录变更前的状态,并在useEffect中更新
useStateCb思路
- 仿照class的setState,接受状态变更的回调,回调中注入preval和currval
- 用useEffect做触发回调的时机
- 用useRef做回调函数的句柄,并保存preval的值
import { useState, useRef, useEffect} from "react";
type Current<T> = {
cb: (state: T, prveState?:T) => void,
prve: T
}
type Cb<T> = Current<T>["cb"]
type Result<T> = [T, (newstate: any, cb: Current<T>["cb"]) => void ]
export default function useXState<T>(initState:T): Result<T> {
const [state, setState] = useState<T>(initState);
let isUpdate = useRef<Current<T>>();
// useRef创建的值ref在整个生命周期中引用都是不变的
// 我们可以用ref.current保存一些变量
const setXState = (newstate:T, cb:Cb<T>) => {
isUpdate.current = {
cb,
prve: state
};
setState(newstate)
}
useEffect(() => {
if (isUpdate.current && isUpdate.current.cb) {
const {cb, prve} = isUpdate.current;
cb(state, prve);
}
}, [state]
);
return [state, setXState]
}
import React, {useEffect} from "react";
import styles from './index.less';
import useXState from "@_hook/useXState";
const Test =() => {
const [val, setVal] = useXState(1);
// const [pre, setPre] = useState(1);
useEffect(()=>{
console.log(val, 'useEffect')
}, [val])
function addVal() {
setVal(val+1, (val, preval) => {
console.log(val,preval)
})
};
return (<>
<div className={styles.test}>val-{val}</div>
{/* <div className={styles.test}>pre-{pre}</div> */}
<button onClick={addVal}>但这里加1</button>
</>)
}
export default Test
骚操作-渲染前校验
如果你有需求给setVal设置阀值,可以把useXState的useEffect换成useLayoutEffect,
但不建议这么做,数据校验的工作应该在提交set前处理好
const Test =() => {
const [val, setVal] = useXState(1);
// const [pre, setPre] = useState(1);
useEffect(()=>{
console.log(val, 'useEffect')
}, [val])
function addVal() {
setVal(val+1, (val, preval) => {
if(val > 10) {
this.setVal(preval)
}
})
};
return (<>
<div className={styles.test}>val-{val}</div>
{/* <div className={styles.test}>pre-{pre}</div> */}
<button onClick={addVal}>但这里加1</button>
</>)
}
关于使用TS
如果返回值时数组的话,一定要声明返回值的类型是元组,不然类型推断会推断成联合类型的数组