优雅的实现全局状态管理
自从16.8.0加入hook后,在小项目中已经不需要依赖redux或者mobx进行状态管理了。
通过使用useState就可以轻松创建状态,然后再结合useContext以及createContext
就可以实现在任何组件中访问state。
废话不多说,直接上代码。
// App.tsx import React, { createContext, useReducer } from "react"; import Head from "./Head"; import Body from "./Body"; import { useStore, getStore, Provider, Providers } from "../../../src/index"; const sleep = async t => new Promise(resolve => setTimeout(resolve, t)); const actions = { increment(state) { return { count: state.count + 1 }; }, async decrement(state) { await sleep(2000); return { count: state.count - 1 }; } }; function App() { const testStore = getStore("test", actions); const testValue = useStore(testStore, { count: 1, name: "my name" }); return ( <Providers stores={[testStore]} values={[testValue]}> <div className="App"> <Head></Head> <Body></Body> </div> <p>1</p> </Providers> ); } export default App;
首先使用getStore去创建一个store并且给一个name以及传入自定义到actions。然后使用useStore去使用store并且给store赋初值。
然后是使用Providers组件将store注入到根组件中。一下代码是具体实现部分。
import { createContext, useContext, useState } from "react";
import * as React from "react";
import Store from "./store";
// import { Reducer } from "./type";
import { ParamsException } from "./utils/exceptions";
import { isPromise } from "./utils/baseUtil";
const storeList: Store[] = [];
/**
* Get store list.
*/
export const getStoreList = (): Store[] => {
return storeList;
};
/**
* Determines whether the specified store exists.
*
* @param storeName store name
*/
export const hasStore = (storeName: string): boolean => {
const storeObj: Store = storeList
.filter(item => item.getName() === storeName)
.pop();
return storeObj ? true : false;
};
/**
* Get the specified store.
* If the store does not exist, it will be created.
*
* @param storeName store name
* @param actions If the store has never been obtained, please pass in the reducer parameter.
*/
/**
* Get the specified store.
* If the store does not exist, it will be created.
*
* @export
* @template T - The action type you defined.
* @param {string} storeName - The store name, you can find the corresponding store through `storeName`
* @param {T} [actions] - The action object can provide multiple actions, and each action corresponds to an operation.
* @returns {Store} - The Store object
*/
export function getStore<T>(storeName: string, actions?: T): Store {
let storeObj: Store = storeList
.filter(item => item.getName() === storeName)
.pop();
if (storeObj) {
return storeObj;
}
if (!actions) {
throw new ParamsException(
"If the store has never been obtained, please pass in the reducer parameter."
);
}
const context = createContext(null);
storeObj = new Store(storeName, actions, context);
storeList.push(storeObj);
return storeObj;
}
/**
* The store reducer interface.
*
* @interface StoreReducer
* @template T - The action type you defined.
* @template U - The state type you defined.
*/
export interface StoreReducer<T, U> {
state: U;
actions: T;
}
/**
* Use the store hook.
*
* @export
* @template T
* @template U
* @param {Store} store - The store instance you want to use.
* @param {*} initialState - Initial state.
* @returns {StoreReducer<T, U>} - `StoreReducer` is an interface that provides `state` and `actions` to operate `state`.
*/
export default function useStore<T, U>(
store: Store,
initialState
): StoreReducer<T, U> {
// const value = useReducer(store.getActions(), initialState, initializer);
// return value;
const actions = {} as T;
console.log("store.getReducer()", store.getActions());
const [state, setState] = useState(initialState);
Object.keys(store.getActions()).forEach(name => {
actions[name] = (arg): any => {
const res = store.getActions()[name].call(this, state, arg);
if (isPromise(res)) {
Promise.resolve(res).then(ret => {
setState({ ...state, ...ret });
});
} else {
setState({ ...state, ...res });
}
};
});
return { state, actions };
}
interface ProviderProps<T, U> {
store: Store;
value: StoreReducer<T, U>;
children: React.ReactNode[] | React.ReactNode;
}
/**
* Store Provider component.
* This component will distribute the state in the store to its children.
* If you need to use multiple stores at the same time, you should use `Providers`.
*
* @export
* @template T - The action type you defined.
* @template U - The state type you defined.
* @param {ProviderProps<T, U>} props - React component props.
* @returns {React.FunctionComponentElement<React.ProviderProps<ProvidersProps<T, U>>>}
*/
export function Provider<T, U>(
props: ProviderProps<T, U>
): React.FunctionComponentElement<React.ProviderProps<ProvidersProps<T, U>>> {
const { store, value, children } = props;
const childrenArr = Array.isArray(children) ? children : [children];
return React.createElement(
store.getContext().Provider,
{ value },
...childrenArr
);
}
interface ProvidersProps<T, U> {
stores: Store[];
values: StoreReducer<T, U>[];
children: React.ReactNode[] | React.ReactNode;
}
/**
* Store Provider component.
* This component will distribute the state in the store to its children.
*
* @export
* @template T - The action type you defined.
* @template U - The state type you defined.
* @param {ProvidersProps<T, U>} props - React component props.
* @returns {React.FunctionComponentElement<React.ProviderProps<ProvidersProps<T, U>>>}
*/
export function Providers<T, U>(
props: ProvidersProps<T, U>
): React.FunctionComponentElement<React.ProviderProps<ProvidersProps<T, U>>> {
const { stores, values, children } = props;
const childrenArr = Array.isArray(children) ? children : [children];
if (stores.length !== values.length) {
throw new ParamsException("Stores and values must correspond one by one.");
}
const queue = [];
stores.map((item, index) => {
if (index === 0) {
queue.push(
React.createElement(
item.getContext().Provider,
{ value: values[index] },
...childrenArr
)
);
} else {
queue.push(
React.createElement(
item.getContext().Provider,
{ value: values[index] },
queue.pop()
)
);
}
});
return queue.pop();
}
/**
* Use context to get the state provided in the store.
* `useContext` is the encapsulation method of `useStoreContext`.
* The provider must be injected into the global component before use.
*
* @export
* @template T - The action type you defined.
* @template U - The state type you defined.
* @param {Store} store - Pass in the store whose context you want to use
* @returns {StoreReducer<T, U>} - `StoreReducer` is an interface that provides `state` and `actions` to operate `state`.
*/
export function useStoreContext<T, U>(store: Store): StoreReducer<T, U> {
return useContext(store.getContext());
}
可以看到核心代码都在useStore函数中,这里会创建一个state并且将actions中的方法代理一下。当调用actions里当方法当时候会执行代理方法,代理方法会先执行actions里的方法,调用完成后再执行setState从而改变状态。
是不是很简单呢。代码已经放到github上了,源代码地址:https://github.com/qq865738120/react-neat
把毕生的精力,都化作认知。不浪费丁点,也不挥霍一丝。

浙公网安备 33010602011771号