react hooks实现class版的setState -> useClassState

65 阅读2分钟

针对刚从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>
                )
	}
}

输出结果

Kapture 2023-03-20 at 11.02.35.gif

可以看到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>
        )
}

Kapture 2023-03-20 at 11.08.02.gif

可以看到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>
        )
}

Kapture 2023-03-20 at 11.14.56.gif

和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};)