利用 typescript 写 react-redux 和 redux-thunk,以及 thunk 等中间件的实现过程

本文主要是介绍利用 typescript 写 react-redux 和 redux-thunk,以及 thunk 等中间件的实现过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

react-redux 的常规使用步骤

  • Provider 作为顶层全局状态的提供者,需要传递一个参数,全局状态 store
import { Provider } from 'react-redux';
<Provider store={ store }></Provider>
  • store 由 createStore 函数创建生成,需要传递 reducer 纯函数作为参数
import { createStore, combineReducers } from 'redux';
const rootReducer = combineReducers({reducer
});
const store = createStore(rootReducer);
  • reducer 又接收两个参数,state 和 action,根据不同的 action 返回一个新的 state
const reducer = (state, action) => {switch (action) {default:return state;}
};
  • 接下来就可以在组件中使用 redux 了
  • mapStateToProps 会接收全局 state 作为参数,返回一个对象,对象的属性作为提供给组件的 props
{ props.reducer };const mapStateToProps = (state) => ({reducer: state.reducer
})
  • mapDispatchToProps 接收 dispatch 作为参数,返回的对象的属性是方法,方法中会调用这个 dispatch 进行更新,可以往 dispatch 中传递对象(只能触发一次dispatch)或者函数(可以触发任意次数,异步操作当然也可以)
props.action();const mapDispatchToProps = (dispatch) => ({action: () => dispatch(action)
})
  • actionCreator 用来生成传递给 dispatch 的参数,也就是通常会在 actionCreator 中返回 action 对象
// 返回对象
const actionCreator = function(data) {return {type: ACTION_TYPE,data}
}// 使用
props.action(someData);const mapDispatchToProps = (dispatch) => ({action: (data) => dispatch(actionCreator(data))
})

TypeScript 写法

上面的代码在 js 环境下执行没有问题,直接将文件名后缀改为 .ts,不出意外是会提示错误的……

例如:

// action.ts
// error: 参数“data”隐式具有“any”类型
export const CHANGE_NAME = 'CHANGE_NAME';
export const changeName = (data) => ({type: CHANGE_NAME,data
});// reducer.ts
// error: 参数“action”隐式具有“any”类型
import { CHANGE_NAME } from './action';
const initialState = {name: 'lixiao'
};
export default (state = initialState, action) => {switch (action.type) {case CHANGE_NAME:retutn Object.assign({}, state, {name: action.data});default:return state;}
};// rootReducer.ts
import { combineReducers } from 'redux';
import home from './pages/Home/reducer';
export default combineReducers({home
});// Home.tsx
// error: 参数“state”隐式具有“any”类型
//        参数“dispatch”隐式具有“any”类型
//        参数“data”隐式具有“any”类型
const mapStatetoProps = (state) => ({name: state.home.name
});
const mapDispatchToProps = (dispatch) => ({changeName: (data) => dispatch(changeName(data)),
})

ts 做了静态编译,一部分目的就是为了避免随意取属性这样的操作。例如 mapStateToProps 中 state.home 这样的操作,需要告诉 ts,参数中的 state 有哪些属性可以取,同样的道理,拆分之后的每一个 reducer 有哪些属性可以取,都需要明确的告诉 ts,(使用 any 类型就不太友好了)。接下来对这些提示错误的变量进行类型声明。

actionCreator

参数中的 data 作为该类型 action 的负载,变量类型设置为 any 是可以接受的

// action.ts
export const CHANGE_NAME = 'CHANGE_NAME';
export const changeName = (data: any) => ({type: CHANGE_NAME,data
});

reducer

reducer 接收 state 和 action 两个参数,action 通常会有一个动作类型属性 type 和 动作负载 data,可以在整个应用中统一为这个形式;而 这里的state 会在其他连接上 redux 的组件中进行访问,因此有必要定义好一个接口,约定能访问到这个 state 的哪些属性。

// global.d.ts
declare interface IAction {type: string;data: any;
}// reducer.ts
import { CHANGE_NAME } from './action';// 这个接口约定子 reducer 能访问到的属性
export interface IHomeState {name: string;
}
const initialState: IHomeState = {name: 'lixiao'
};
export default (state = initialState, action: IAction): IHomeState => {switch (action.type) {case CHANGE_NAME:return Object.assign({}, state, {name: action.data});default:return state;}
};

rootReducer

作为顶层 state,在子组件中通常会通过它再去访问子 reducer 中的 state,前面已经约定好了子 reducer 中的 state 能够取到哪些属性,现在还需要约定好顶层 state 能取到哪些子 state

// rootReducer.ts
import { combineReducers } from 'redux';
import home, { IHomeState } from './pages/Home/reducer';// 每增加一个子 reducer,都在这个接口中同步更新
export interface IStore {home: IHomeState
}
export default combineReducers({home
});

组件中使用 redux

// Home.tsx
import { Dispatch } from 'redux';
import { IStore } from '../../rootReducer';// 组件的 props 类型
interface IProps {name: string;// 这里的 changeName 和 actionCreator 中的不是同一个,所以返回值类型不是对象changeName(data: any): void;
}
const mapStatetoProps = (state: IStore) => ({name: state.home.name
});
// Dispatch 接收的参数为对象
const mapDispatchToProps = (dispatch: Dispatch) => ({changeName: (data: any) => dispatch(changeName(data)),
})

redux-thunk

在前面的例子中,将 action 传递给 dispatch,然后根据 reducer 函数返回新的 state,这个操作是同步的,类似于 vuex 中的 mutation。redux-thunk 是处理异步 action 的一种解决方式。

异步 action

一个很常见的场景,发送 ajax 请求成功之后触发 dispatch。最简单的实现方式是将这个操作封装成一个函数:

const asyncAction = () => {dispatch({ type, data });ajax().then(res => dispatch(res));
}

若这个操作需要在多个地方调用呢?复制粘贴这个函数也是可行的,但更好的做法是使用 redux-thunk 这样的中间件,在 actionCreator 中生成一个包含异步操作的 action

redux-thunk 用法

  • 在创建 store 的时传入 redux-thunk 中间件
// index.tsx
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';const store = createStore(rootReducer, applyMiddleware(thunk));
  • actionCreator 返回的是一个函数,在这个函数中可以做任意的事情,触发任意次数的 dispatch,当然也包括异步操作。
// action.ts
import { Dispatch } from 'redux';export const CHANGE_NAME = 'CHANGE_NAME';
export const changeName = (data: any) => ({type: CHANGE_NAME,data
});
export const changeNameAsync = (data?: any) => (dispatch: Dispatch) => {dispatch(changeName('loading'));fetch('/api').then(res => res.json()).then(res => dispatch(changeName(res.data)));
}
  • 在组件中使用这个返回函数的 action
// Home.tsx
import { changeName, changeNameAsync } from './action';// dispatch 可以传入对象、函数,这里不能直接简单的使用 Dispatch 类型
const mapDispatchToProps = (dispatch: any) => ({changeName: (data: any) => dispatch(changeName(data)),changeNameAsync: () => dispatch(changeNameAsync())
});
// 也可以使用 bindActionCreators
const mapDispatchToProps = (dispatch: Dispatch) => (bindActionCreators({changeName,changeNameAsync
}, dispatch))

redux-thunk 实现过程

使用一次之后就发现,同步 action 与异步 action 的区别在于,同步操作 dispatch 一个对象,而异步操作是 dispatch 一个函数,在这个函数中可以包含异步、dispatch 同步 action 等任意操作。

createStore

基础版本

在引入 redux-thunk 之前,生成 store 的方式为

const store = createrStore(reducer, initialState)

第二个参数 initialState 为初始状态,可选

// 两个参数
function createStore(reducer, preloadedState) {let currentReducer = reducer;let currentState = preloadedState;// 比较重要的函数 dispatchfunction dispatch(action) {// 这就是为什么也可以在 reducer 中初始化 state// 也可以发现这里的参数 preloadedState 的优先级高于 reducer 中的 initialStatecurrentState = currentReducer(currentState, action);}// 页面刚打开的时候,调用 createStore,再执行一次 dispatch,更新 currentState// 所以 redux 开发工具中会有一个 @@INIT 类型的 actiondispatch({ type: ActionTypes.INIT });
}

高级版本

引入 redux-thunk 之后,生成 store 的方式也有所变化

const store = createrStore(reducer, initialState, enhancer)

第三个参数是一个函数,接下来看看传入第三个参数时, createStore 内部是如何处理的

function createStore(reducer, preloadedState, enhancer) {// 由于第二个参数是可选的,所以需要一些代码进行参数数量和参数类型的检测// ts 就方便了……// 检测通过之后就提前 return 了,return enhancer(createStore)(reducer, preloadedState)
}

进行一下代码转换

const store = createStore(rootReducer, applyMiddleware(thunk));
// enhancer 就是 applyMiddleware(thunk)
// preloadedState 为 undefined
// 代码转换
const store = applyMiddleware(thunk)(createStore)(rootReducer);

applyMiddleware

import compose from './compose';export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)let dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)}const chain = middlewares.map(middleware => middleware(middlewareAPI))dispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
}

接着进行代码装换

const store = applyMiddleware(thunk)(createStore)(rootReducer);
// 相当于在执行下面这串代码
const store = createStore(rootReducer) // 这个 store 是基础版本中的 store
let dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')
}const middlewareAPI = {getState: store.getState,// dispatch: (rootReducer) => dispatch(rootReducer)dispatch: dispatch
}
const chain = [thunk].map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// 这是最终返回的 store 
return {...store,dispatch
}

再看看 thunk(middlewareAPI)compose 做了什么

thunk

// redux-thunk/src/index.js 简化之后
const thunk = ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState);}return next(action);
};

compose

function compose(...funcs) {if (funcs.length === 0) {return arg => arg}if (funcs.length === 1) {return funcs[0]}return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

传入的多个中间件,会依次作为参数;只有一个中间件则直接返回这个函数

dispatch 怎么被拓展的

const store = createStore(rootReducer)
// const chain = [thunk].map(middleware => middleware(middlewareAPI))
// const chain = [thunk(middlewareAPI)];
const chain = [(next => action => {if (typeof action === 'function') {return action(dispatch, store.getState);}return next(action);
})]
// dispatch = compose(...chain)(store.dispatch)
dispatch = action => {if (typeof action === 'function') {return action(dispatch, store.getState);}return store.dispatch(action);
}

当传给 dispatch 的参数不是函数时,就是基础版本,直接调用 reducer 函数进行更新 state;传入的是函数时,执行函数体的内容并把 dispatch 传进去,在这个函数内部就又可以使用 dispatch 做想做的事情了。

总结

大部分函数都是 redux 提供的,闭包用的比较多,(params1) => (params2) => { someFunction(params1, params2) } 这样的柯里化用法很多,看起来容易头晕,但也就是每执行一次函数传入一次参数。

这篇关于利用 typescript 写 react-redux 和 redux-thunk,以及 thunk 等中间件的实现过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/508926

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time