博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
利用 typescript 写 react-redux 和 redux-thunk
阅读量:4083 次
发布时间:2019-05-25

本文共 9331 字,大约阅读时间需要 31 分钟。

react-redux 的常规使用步骤

  • Provider 作为顶层全局状态的提供者,需要传递一个参数,全局状态 store
import { Provider } from 'react-redux';
  • 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.tsimport { 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.tsexport 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.tsdeclare interface IAction {    type: string;    data: any;}// reducer.tsimport { 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.tsimport { combineReducers } from 'redux';import home, { IHomeState } from './pages/Home/reducer';// 每增加一个子 reducer,都在这个接口中同步更新export interface IStore {    home: IHomeState}export default combineReducers({    home});

组件中使用 redux

// Home.tsximport { 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.tsximport { createStore, applyMiddleware } from 'redux';import thunk from 'redux-thunk';const store = createStore(rootReducer, applyMiddleware(thunk));
  • actionCreator 返回的是一个函数,在这个函数中可以做任意的事情,触发任意次数的 dispatch,当然也包括异步操作。
// action.tsimport { 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.tsximport { changeName, changeNameAsync } from './action';// dispatch 可以传入对象、函数,这里不能直接简单的使用 Dispatch 类型const mapDispatchToProps = (dispatch: any) => ({    changeName: (data: any) => dispatch(changeName(data)),    changeNameAsync: () => dispatch(changeNameAsync())});// 也可以使用 bindActionCreatorsconst 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;        // 比较重要的函数 dispatch    function dispatch(action) {        // 这就是为什么也可以在 reducer 中初始化 state        // 也可以发现这里的参数 preloadedState 的优先级高于 reducer 中的 initialState        currentState = currentReducer(currentState, action);    }        // 页面刚打开的时候,调用 createStore,再执行一次 dispatch,更新 currentState    // 所以 redux 开发工具中会有一个 @@INIT 类型的 action    dispatch({ 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 是基础版本中的 storelet 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) } 这样的用法很多,看起来容易头晕,但也就是每执行一次函数传入一次参数。

转载地址:http://wpqni.baihongyu.com/

你可能感兴趣的文章
webpack4源码分析
查看>>
深度剖析:如何实现一个 Virtual DOM 算法
查看>>
Vue2.5从0开发猫眼
查看>>
VUE缓存:动态keep-alive
查看>>
webpack4介绍与总结
查看>>
Vue项目Webpack优化实践,构建效率提高50%
查看>>
从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!
查看>>
前端本地文件操作与上传
查看>>
数据动态绑定的简单实现——基于ES5对象的getter/setter机制
查看>>
vue源码系列文章good
查看>>
前端跨域解决方案
查看>>
Web前端面试——HTTP部分
查看>>
HTTP必知必会——常见面试题总结
查看>>
你不知道的Virtual DOM
查看>>
VUE面试题总结
查看>>
写好JavaScript条件语句的5条守则
查看>>
原生JS中DOM节点相关API合集
查看>>
新手快速上手webpack4打包工具的使用
查看>>
发布 react 组件到 npm
查看>>
定制化你的ReactNative底部导航栏
查看>>