基于hooks api手写dva

您所在的位置:网站首页 python生成list全为0 基于hooks api手写dva

基于hooks api手写dva

2023-10-13 22:51| 来源: 网络整理| 查看: 265

0、为什么不使用react-redux

学习react最早使用react-redux,但是写法太麻烦,后来接触蚂蚁金服umi dva这一套体系,开始dva,简化了redux的写法,同时有了model的概念。 后来学习hooks,感觉hooks那种纯函数的写法更加优雅,一直使用hooks一直爽,后面学习到hooks的useContext、useReducer相关api,这些api配合context完全可以代替react-redux。

还有一个原因,有次在b站看教程React Hooks教学教程 | 案例详解 - useReducer,里面看到了这段话,感觉也很有道理:

WechatIMG1059.png

于是有了以下代码:

1、雏形 useFeature

我有一段时间使用以下这种写法,虽然脱离了react-redux,但是缺点是不支持异步:

import React, { useReducer, useContext } from 'react'; /** * context */ const IndexContext = React.createContext(); /** * reducer */ const initialState = { lookingRecord: null, }; const initStateFunc = () => { return initialState; }; const reducer = (state, action) => { const { type, ...restState } = action; if (type == 'SAVE') { return { ...state, ...restState, }; } return state; }; const ContextProvider = (props) => { const [state, dispatch] = useReducer(reducer, initialState, initStateFunc); return ( {props.children} ); }; /** * hooks */ const useFeature = () => { const { state, dispatch } = useContext(IndexContext); return { state, dispatch, }; }; export { ContextProvider, useFeature };

最终暴露出来的的是一个provider和一个userFeatrue,其实就是React.createContext创建了一个context,去包裹我想共享数据的页面或者组件,然后被包裹的组件里去使用useFeature就能读取到state也能拿到dispatch去修改state

使用 import { ContextProvider, useFeature } from './features/index'; const IndexWrap = (props) => ( ); export default IndexWrap;

组件中:

WechatIMG1055.png

缺点

不支持异步,只能请求之后再去dispatch修改state

2、目前的版本:基于context、useContext、useReducer的手写dva--useModel

看过其他人写的帖子分析redux-thunk,好像就是判断action是function还是对象,如果是function就把dispatch传过去,于是仿造这种写法,在原基础上支持异步,写到最后竟然发现其实这不就是dva么。。。

相关目录

dva目录.png

index.js:入口文件

core.js: 核心方法

global.js和login.js是不同的模块,根据业务可以在index.js中添加

我们先看入口文件:

index.js import React from "react"; import { generateLoadingModel, generateProvider, generateUseModel } from "@/models/core.js"; import global from "./global"; import login from "./login"; const allModel = { loading: generateLoadingModel(), global, login }; /** * redux */ const IndexContext = React.createContext(); // Provider const ContextProvider = generateProvider({ context: IndexContext, allModel }); /** * @returns {{ * state:object; * dispatch:function; * getLoading:function; * }} */ const useModel = generateUseModel({ context: IndexContext, allModel }); export { ContextProvider, useModel };

最终暴露出去的就是一个provider和一个useModel,这里provider和useModel的使用方式和原来的useFeature是一样的,provider最外层包裹,内部使用useModel可以拿到state和dispatch

看几个关键的方法:

generateUseModel : 生成一个useModel,关键代码:里面的thunkDispatch方法 /** * 生成一个useModel * @param {object} options * @param {*} options.context * @param {object} options.allModel * @param {function} [options.dealExport] */ export function generateUseModel({ context, allModel, dealExport }) { /** * @returns {{ * state, * dispatch, * getLoading * }} */ return function () { const { state, dispatch } = useContext(context); const stateRef = useRef(state); useEffect(() => { stateRef.current = state; }, [state]); function getState() { return { ...stateRef.current }; } // loading function setLoading(type, flag) { dispatch({ modelName: "loading", methodName: "setLoading", payload: { type, loading: flag }, dispatch: thunkDispatch }); } function getLoading(type) { return state.loading[type] || false; } /** * 最终暴露出去的,外面用到的dispatch 可以处理异步 * @param {string} type // 'global/toggle' * @param {*} payload // 自定义携带参数 */ function thunkDispatch(type, payload) { const modelName = type.split("/")[0]; const methodName = type.split("/")[1]; const modelAction = allModel[modelName].actions?.[methodName]; if (modelAction) { // 异步 setLoading(type, true); modelAction({ getState, payload, dispatch: thunkDispatch }).finally(() => { setLoading(type, false); }); } else { // 同步 dispatch({ modelName, methodName, payload, dispatch: thunkDispatch }); } } // 暴露出去 const defaultExport = { state, dispatch: thunkDispatch, getLoading }; if (dealExport) { return dealExport(defaultExport); } return defaultExport; }; }

这里和雏形版useFeature的区别最大的一点是 我们暴露出去的dispatch并不是useReducer()出来的原生的dispatch,是thunkDispatch,这里先看一下model的结构和dispatch(指thunkDispatch)在组件中的使用方法:

model的结构:

例:globalModel

function toggleCollapsedAjax(flag) { return new Promise((resolve) => { setTimeout(() => { resolve(!flag); }, 1000); }); } const initialState = { isCollapsed: false }; const model = { name: "global", state: initialState, actions: { async toggleCollapsedFunc({ dispatch, getState, payload }) { await toggleCollapsedAjax(); const state = getState().global; dispatch("global/save", { isCollapsed: !state.isCollapsed }); } }, reducers: { save({ state, payload }) { return { ...state, ...payload }; } } }; export default model;

这里actions是异步的方法,reducers是同步的方法

看下组件中dispatch的调用:

WechatIMG1056.png

thunkDispatch方法接收两个参数,type是这种格式:'global/toggleCollapsedFunc'

"/"之前是模块名字,后面是调用的方法,可以是action也可以是reducer,我们在thunkDispatch方法中会去判断, 如果是action就先调用action同时把dispatch传进去,如果不是action那就走原生的dispatch,它会触发原生的reducer:

/** * 生成reducer * @param {object} options * @param {object} options.allModel * @returns */ export function generateReducer({ allModel }) { /** * reducer 原生的dispatch触发的 就是这个 * @param {*} allState * @param {object} options * @returns */ return function (allState, options) { const { modelName, methodName, payload, dispatch } = options; const modelReducer = allModel[modelName].reducers?.[methodName]; const oldModelState = allState[modelName]; // 调用model里的reducer const newModelState = modelReducer({ state: oldModelState, payload, dispatch }); return lodash.cloneDeep({ ...allState, [modelName]: newModelState }); }; }

调用对应model里的reducer方法去生成一个新的state,然后根据modelName去修改整个state

这样就是实现了redux的异步调用:)

3、实现dva的loading

dva框架有个方便的地方,就是当你调用一个异步请求的时候,它会自动生成一个loading的状态,看一下我是如何实现的:

allModel中总会有一个loadingModel,它是通过generateLoadingModel这个方法生成的:

/** * 生成一个loading model * @returns model */ export function generateLoadingModel() { return { name: "loading", state: {}, reducers: { setLoading({ state, payload, dispatch }) { const { type, loading } = payload; const newState = { ...state }; if (loading) { newState[type] = true; } else { delete newState[type]; } return newState; } } }; }

因为每个model中的action都是promise,可以使用.finally的写法

WechatIMG1070.png

在thunkDispatch中,如果调用的是action,就在调用前setLoading为true,finally的时候就setLoading为false,注意,在loadingModel的reducer中的setLoading方法,如果set为false的时候,其实是删除掉这个属性,并不是真的把loading的值变成false,因为一个应用的请求一般很多,防止loadingModel这个state属性过于多

看一下setLoading方法:

// loading function setLoading(type, flag) { dispatch({ modelName: "loading", methodName: "setLoading", payload: { type, loading: flag }, dispatch: thunkDispatch }); } function getLoading(type) { return state.loading[type] || false; }

使用方法

在页面中通过getLoading的方法获取:

WechatIMG1057.png

总结

基本原理就是使用useContext创建全局状态,useReducer创建dispatch去更新state,主要对dispatch做了封装

组件中哪里需要就引入useModel,随时可以拿到dispatch和state

通过以上的写法,现在项目中基本不需要react-redux了

另外也有局部useModel的写法,详细可以看github :)

如果发现有不对的地方或者建议 欢迎讨论

github项目地址:github.com/jiqishoubi/…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3