UNPKG

think-react-store

Version:

基于react hooks 和 context 实现的类似与 redux 的数据流工具

283 lines (238 loc) 7.39 kB
import React, { useContext, useReducer } from 'react' import { get, run, isAsyncFunction, inWhich, transBool } from './util' import is from 'ramda/src/is' import isEmpty from 'ramda/src/isEmpty' const StoreContext = React.createContext() const LOADING = 'hook-loading-clear' let initialState = {}, initStore = {}, //传入的store asyncKey, newData, methodsName = {}, //所有方法名 actionAsync //触发的异步方法 function doRun(key, actionArr, type, store, state, payload) { let res actionArr.forEach(item => { if (get([key, item, type], store)) { res = run([key, item, type], store, state, payload) } }) newData = res return res } function reducer(state, action) { const { key, type, payload } = action const _key = key || asyncKey return { ...state, [_key]: { ...state[_key], ...doRun(_key, ['methods', 'reducers', 'effects'], type, initStore, state[_key], payload) } } } function StoreProvider(props) { const { store, middleware } = props; if (!store) { console.error(` store 必须有值,类似于: <StoreProvider store={store}> `) return null } const hasLoadingMiddleware = middleware?.filter(item => item.name === 'loading')?.length const propsCacheExit = props?.cache?.length if (hasLoadingMiddleware) { function setLoadingTrue() { if (isEmpty(methodsName)) { Object.keys(store).forEach(item => { methodsName[item] = { ...transBool(get([item, 'methods'], store), false), ...transBool(get([item, 'reducers'], store), false), ...transBool(get([item, 'effects'], store), false) } }) } } setLoadingTrue() } initStore = store // 中间件 function middlewareReducer(prevState, action) { if (!action) { return store; } if (middleware) { if (!is(Array, middleware)) { throw new Error('middleware中间件必须为数组') } } let nextState = reducer(prevState, action); middleware && middleware.forEach(item => { const newState = item(store, prevState, nextState, action, actionAsync, asyncKey, props?.cache) if (newState) { nextState = newState } }) return nextState; } Object.keys(initStore).forEach(item => { initialState[item] = initStore[item]['state'] }); if (hasLoadingMiddleware && !initialState['loading']) { initialState['loading'] = methodsName } let [state, origin_dispatch] = useReducer(middlewareReducer, initialState) const dispatch = async (action, payload, key) => { actionAsync = undefined if (get([asyncKey, 'reducers', action.type], initStore)) { return origin_dispatch(action); } if (!isAsyncFunction(action) && inWhich('effects', initStore[action.key || asyncKey], action.type)) { asyncKey = action.key actionAsync = action.type if (hasLoadingMiddleware) { origin_dispatch({ key: asyncKey, type: LOADING, payload: actionAsync }) } const func = get([action.key, 'effects', action.type], initStore) return await func(origin_dispatch, state, action.payload) } if (isAsyncFunction(action) || get([asyncKey, 'effects', action], initStore)) { asyncKey = key actionAsync = action.name if (hasLoadingMiddleware) { origin_dispatch({ key: asyncKey, type: LOADING, payload: action.name, }) } return await action(origin_dispatch, state, payload); } if (!isAsyncFunction(action) && is(Function, action)) { if (is(Object, action())) { const _action = get([action().key, 'methods', action().type], initStore) || get([action().key, 'effects', action().type], initStore) if (is(Function, _action)) { asyncKey = action().key actionAsync = action().type if (hasLoadingMiddleware) { origin_dispatch({ key: asyncKey, type: LOADING, payload: actionAsync }) } return await _action(origin_dispatch, state, action().payload); } } } return origin_dispatch(action); } return ( <StoreContext.Provider value={{ state, dispatch }}> {props.children} </StoreContext.Provider> ); }; function useDispatchHook(key) { const store = useContext(StoreContext) if (key) { asyncKey = key } return store.dispatch } function useStateHook(key) { const store = useContext(StoreContext) return key ? get(['state', key], store) : get(['state'], store) } /*返回state和方法 { user: {id:1,getUser(state,payload){xxxx}} } */ function useStoreHook() { const { state, dispatch } = useContext(StoreContext) let _store = {} Object.keys(state).forEach(key => { let _methods = {} const methods = { ...get([key, 'methods'], initStore), ...get([key, 'reducers'], initStore), ...get([key, 'effects'], initStore) } const methodsArr = methods && Object.keys(methods) is(Array, methodsArr) && methodsArr.forEach(item => { if (isAsyncFunction(methods[item]) || inWhich('effects', initStore[key], item)) { _methods[item] = async (payload) => { await dispatch(methods[item], payload, key) } } if (!isAsyncFunction(methods[item]) && is(Function, methods[item])) { _methods[item] = (payload) => { dispatch({ key: key, type: item, payload }) } } }) _store[key] = { ...state[key], ..._methods } }) return _store } function connect(mapStateToProps, mapDispatchToProps) { return function (Comp) { return function (props) { const store = useStoreHook() const { state } = useContext(StoreContext) const rootState = state let methods = {} Object.keys(store).forEach(item => { // const _state = rootState[item] methods[item] = {} Object.keys(store[item]).forEach(i => { if (is(Function, store[item][i])) { if (isAsyncFunction(store[item][i]) || inWhich('effects', initStore[item], i)) { methods[item][i] = function (dispatch, state, payload) { return new Promise(async (resolve, reject) => { try { await store[item][i](dispatch, rootState, payload) resolve(newData) } catch (err) { reject(err) } }) } } else { methods[item][i] = function (state, payload) { return new Promise((resolve, reject) => { try { store[item][i](state, payload) resolve(state) } catch (err) { reject(err) } }) } } } }) }) const stateToProps = mapStateToProps(state) const dispatchToProps = mapDispatchToProps(methods) return <Comp {...stateToProps} {...dispatchToProps} {...props} /> } } } export { StoreContext, StoreProvider, useStateHook, useDispatchHook, useStoreHook, connect }