本文共 9331 字,大约阅读时间需要 31 分钟。
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; }};
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))})
上面的代码在 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
类型就不太友好了)。接下来对这些提示错误的变量进行类型声明。
参数中的 data
作为该类型 action
的负载,变量类型设置为 any
是可以接受的
// action.tsexport const CHANGE_NAME = 'CHANGE_NAME';export const changeName = (data: any) => ({ type: CHANGE_NAME, data});
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; }};
作为顶层 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});
// 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)),})
在前面的例子中,将 action
传递给 dispatch
,然后根据 reducer 函数返回新的 state
,这个操作是同步的,类似于 vuex
中的 mutation
。redux-thunk 是处理异步 action 的一种解决方式。
一个很常见的场景,发送 ajax 请求成功之后触发 dispatch。最简单的实现方式是将这个操作封装成一个函数:
const asyncAction = () => { dispatch({ type, data }); ajax().then(res => dispatch(res));}
若这个操作需要在多个地方调用呢?复制粘贴这个函数也是可行的,但更好的做法是使用 redux-thunk 这样的中间件,在 actionCreator 中生成一个包含异步操作的 action
。
// index.tsximport { createStore, applyMiddleware } from 'redux';import thunk from 'redux-thunk';const store = createStore(rootReducer, applyMiddleware(thunk));
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))
使用一次之后就发现,同步 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/