react context 做状态管理

138 阅读1分钟

创建context

import React from 'react'

export default React.createContext({});

创建仓库

import React, { useMemo } from 'react';
import StoreContext from './context';
import { storeType } from './type';


export default function StoreProvider<T>(props: { children: React.ReactNode, data: T }) {
    // 创建仓库
    /**
     * 仓库中数据为props.data,
     * 有两个方法:
     * getDate: 获取仓库中的数据
     * setData:设置仓库中的数据
     */
    const store: storeType<T> = useMemo(() => {
        let data = props.data;
        return {
            getData() {
                return data;
            },
            setData(arg) {
                const state = typeof arg === 'function' ? arg(data) : arg;
                data = { ...data, ...state };
            }
        }
    }, [props.data]);

    return <StoreContext.Provider value={store}>{props.children}</StoreContext.Provider>
}

创建仓库相关的钩子

import React, { useCallback, useContext, useEffect, useReducer } from 'react'
import StoreContext from './context';
import { storeType, setDateType } from './type';
type dataType = <T>(selector: any, compare?: (obj1: any, obj2: any) => boolean) => T;

export const useStore = function <T>(): storeType<T> {
    return useContext(StoreContext as any);
}

export function shallowEqual(objA: any, objB: any): boolean {
    if (Object.is(objA, objB)) {
        return true;
    }
    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
        return false;
    }
    const keyA = Object.keys(objA);
    const keyB = Object.keys(objB);
    if (keyA.length !== keyB.length) return false;

    for (let i = 0; i < keyA.length; i++) {
        if (!Object.prototype.hasOwnProperty.call(objB, keyA[i]) || !Object.is(objA[i], objB[i])) {
            return false;
        }
    }
    return true;
}


const subscription: any[] = [];

export const useData: dataType = (selector, compare = shallowEqual) => {
    const { getData } = useStore();  // 从仓库中拿到数据
    /**
     * 利用useReducer创建state, 
     * 初始值为外部传入的 useData((state) => state) === selector((getData()));
     * useReducer返回当前state以及配套的dispatch方法。
     * subscription中存放dispatch方法,当外部dispatch的时候,触发组件更新
     */
    const [state, setState] = useReducer((oldState) => {
        const newState = selector(getData());
        if (!compare(oldState, newState)) {
            return newState;
        }
        return oldState;
    }, selector(getData())) // selector(getData())返回值作为初始值 === state; 仓库中的数据



    useEffect(() => {
        subscription.push(setState); // 发布订阅模式
        return () => {
            const index = subscription.indexOf(setState);
            subscription.splice(index, 1);
        }

    }, [setState])
    return state;
}

export const useDispatch: (<T>() => setDateType<T>) = () => {
    const { setData } = useStore();
    return useCallback((o) => {
        setData(o);
        // 触发全部dispatch
        subscription.forEach((fn) => fn());
    }, [setData])
}

组件中使用

import { Button } from 'antd';
import React, { useEffect } from 'react'
import { useData, useDispatch } from '../store';

const StoreChildren = (props: any) => {
    const dispatch = useDispatch();
    // 获取仓库中的数据

    const compare = (prev: any, next: any) => {
        if (prev.group.length !== next.group.length) {
            return false;
        }
        return true;
    }
    const data = useData<any>((state: any) => state, compare);

    useEffect(() => {
        console.log('引起组件更新了');
    })

    const handleDispatch = () => {
        setTimeout(() => {
            console.log('数据获取回来了');
            const res = [1, 2, 3, 4, 5];
            dispatch({ group: [...res] })
        }, 1000)
    }

    return <div>
        <Button onClick={handleDispatch}>dispatch</Button>
        <Button onClick={() => dispatch({ b: 2 })}>dispatch</Button>
        {data.b && <div>456</div>}
        {
            data.group?.map((_: any, idx: number) => {
                return <p key={idx}>12313</p>
            })
        }
    </div>
}

export default StoreChildren;