用TS实现一个简单自定义hook

409 阅读2分钟

场景

我们需要一个状态更新后执行一段逻辑,该逻辑只在更新状态后(不包括初始化)时执行,该逻辑需要用到状态更新前后的值。

用基础hooks,useEffect也能实现需求不过需要额外的处理.

  1. 判断时更新时触发还是初始化时触发,过滤掉初始化的副作用
  2. 用一个额外的变量记录变更前的状态,并在useEffect中更新

useStateCb思路

  1. 仿照class的setState,接受状态变更的回调,回调中注入preval和currval
  2. 用useEffect做触发回调的时机
  1. 用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

如果返回值时数组的话,一定要声明返回值的类型是元组,不然类型推断会推断成联合类型的数组