在对 redux 的使用过程中,了解到 redux 中的一些核心概念和方法,为了达到 “知其然,也知其所以然” 的学习目标,尝试从应用层面出发,剖析原理,手撸 redux 和 react-redux 中的核心方法。
一. redux 中的基本概念
整个工作流如图所示,涉及到以下核心概念:
-
Store:状态树,存储对象状态的地一个容器
-
Action :操作 store 的行为载荷,通过 store.dispatch 传递到 store
-
Reducers:真正操作 store 的方法,可以查看之前的状态,也可以响应接收到的 action 并返回一个新的状态
以下概念为 React-redux 中才具备的:
-
Provider:一个外层容器,配合 connect 实现父子层级组件的通信
-
Connect:连接 React 组件与 redux store 的一个方法,接收 Provider 组件提供的 store,返回一个高阶组件,将响应的 state 和 dispatch 作为属性参数传给内部组件
二. redux 核心方法实现
在手撸核心方法之前,先回忆一下使用 redux 的流程:
-
首先需要声明一个
reducer
,然后调用createStore
来实例化一个store
-
紧接着如果要获取对象的状态,就需要调用
store.getState
方法来获取 -
如果要修改对象的状态,就需要调用
store.dispatch
方法来修改 -
最后不要忘了使用
store.subscribe
方法来添加监听函数
根据上述回顾,可以知道实例化的 store
包含三个方法 getState
,dispatch
, subscribe
,其次为了实现对对象的存储与事件的监听,还需要两个变量 currentState
和 currentListeners
分别用于存储当前对象状态和监听队列。于是可以 createStore
函数的基本结构就清楚了
export function createStore(reducer, enhancer) {
// 先忽略 enhancer 的操作
// ...
let currentState, currentListeners = []
function getState() {
// todo
}
function dispatch(action) {
// todo
}
function subscribe(listener) {
// todo
}
return {getState, dispatch, subscribe}
}
有了基础框架之后,再来仔细思考一下三个方法都具备了哪些功能:
-
getState
方法:没有参数,能够返回当前的对象状态,故而函数内部很简单,就是直接返回currentState
; -
dispatch
方法:传入一个action
,将其代理到reducer
中,由reducer
去执行真正的状态更改并得到新的状态,同时触发状态变更事件,也即需要执行所有的监听函数; -
subscribe
方法:传入一个listener
,将其添加到监听队列中,一旦store
中的状态发生变更,listener
将被执行。
createStore
实现如下:
export function createStore(reducer, enhancer) {
if(enhancer) {
// 如果存在 enhancer,则对本函数进行转换,再将 reducer 传入
return enhancer(createStore)(reducer)
}
let currentState, currentListeners = []
function getState() {
return currentState
}
function subscribe(listener) {
currentListeners.push(listener)
// 触发一个不可能存在的 action,使得 currentState 具备一个初始值
dispatch('@roadlin/myRedux')
}
function dispatch(action) {
currentState = reducer(currentState, action)
// 遍历监听队列,依次执行监听事件
currentListeners.forEach(v => v())
return action
}
return {
getState,
subscribe,
dispatch
}
}
除了上述的 createStore
方法,redux 还提供了其它 API,比如当存在多个 reducer 时,需要调用 combineReducers
进行合并;当要引入中间件时,需要使用 applyMiddleware
进行注入。同样对这两个 API 的功能进行拆分分析,有助于理解其内部实现:
-
combineReducers
:传入的参数是一个对象,包含多个 reducers,最终经处理合并之后,返回一个合并之后的 reducer,代码描述如下:
combineReducers({
count: countReducer,
cart: cartReducer
})
// 上述代码合并之后得到一个整体的 reducer
function reducer(state = {}, action) {
return {
count: countReducer(state.count, action),
cart: cartReducer(state.cart, action)
}
}
合并之后,在调用 dispatch
的时候,会将 action
传递给合并后的 reducer
, 合并后的reducer
中会遍历获取每个 key 值对应的状态,传递给相应的原始 reducer
执行,也即无论 action
是操作哪一个状态,所有的 reducer
都会执行一次
-
applyMiddleware
: 传入的参数是 n 个中间件(n ≥ 1),其主要作用是包装store
原始的dispatch
方法,使其支持中间件的功能,比如applyMiddleware(thunk)
之后dispatch
中支持异步操作。当 n > 1时,中间件是从右往左进行链式调用,也即最后的中间件处理之后的结果交给前一个中间件处理
combineReducers
和 applyMiddleware
实现如下:
function combineReducers(reducersObj) {
// 1. 首先做了去重和判断,去除重复的 key 值,保证传入的每一个属性值都是 reducer 函数,保证一个 key 只对应一个 reducer
let finnalReducers = {}
for(let key in reducersObj) {
if(typeof reducersObj[key] === 'function') {
finnalReducers[key] = reducersObj[key]
}
}
// 2. 中间还做了下判断,保证传入的 reducers 中不包括复合型的 reducer,也就是 combineReudcers 处理后的结果不能用于再一次的 combineReducers
// ... 该部分省略
// 3. 返回一个合并后的 reducer 函数 combination(state ={}, action)
// 3.1 内部遍历了所有的 reducers,获取上一次 key 值对应的 state,调用对应的 reducer 函数,执行 action
// 3.2 循环过程中判断是否更新了 state 值,如果更新了,则返回新的 state,否则依旧返回旧的 state
return function combination(state = {}, action) {
let hasChange = false, nextState = {}
for(let key in finnalReducers) {
let previousStateForKey = state[key]
let nextStateForKey = reducersObj[key](previousStateForKey, action)
nextState[key] = nextStateForKey
hasChange = hasChange || nextStateForKey !== previousStateForKey
}
return hasChange ? nextState : state
}
}
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
let midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
let middleChain = middlewares.map(middleware => middleware(midApi))
dispatch = compose(middleChain)(store.dispatch)
return {
...store,
dispatch
}
}
}
// 遵循从右到左的链式调用,所以 compose(f, h, g) 等价于 (...args) => f(h(g(...args)))
export function compose(...funcs) {
if(funcs.length === 0) {
return arg => arg
}
if(funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((ret, item) => (...args) => ret(item(...args)))
}
/*******************华丽分割线*************************/
// 除此之外,redux 中还有别的函数,比如:
/*
调用 connect 时,如果 mapDispatchToProps 是对象时执行 bindActionCreators 如
@connect(
state => ({goods: state.goods}),
{addGood, deleteGood, asyncAdd}
)
*/
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args))
}
export function bindActionCreators(creators, dispatch) {
return Object.keys(creators).reduce((ret, item) => {
ret[item] = bindActionCreator(creators[item], dispatch)
return ret
}, {})
}
三. react-redux 核心方法实现
在应用 react-redux 时,主要用到了 <Provider></Provider>
组件以及 connect
函数,同样的,先分析它们的主要功能:
-
Provider
组件:通过属性传参的方式传入store
参数,并将其传给子组件,同时渲染内部组件。为了实现子组件中可以读取到store
参数,需要借助context
上下文来传递,作为父组件,其需要设置childContextTypes
-
connect
函数:传入的是两个 map,返回一个高阶组件,将 redux 中的state
和dispatch
变成组件的props
-
为了从
Provider
中获取到store
,需要设置contextTypes
-
高阶组件最后返回的新组件中,也即经
connect
装饰之后的组件可以直接通过this.props[key]
读取到 redux 中的数据和方法,故而需要执行两个 map,获取到 redux 数据和方法,最后以props
的方式传给新组件 -
此外,还需要设置监听事件,当 redux 中的数据发生更改时,更新当前组件的
props
-
import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './myRedux'
export class Provider extends React.Component{
static childContextTypes = {
store: PropTypes.object
}
getChildContext() {
return {store: this.store}
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
return this.props.children
}
}
export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => WrapComponent =>{
return class NewComponent extends React.Component {
static contextTypes = {
store: PropTypes.object
}
constructor(props, context) {
super(props, context)
this.state = {
props: {}
}
}
// 在组件渲染前更新,否则因为读不到对应的 props 属性值而报错
componentWillMount() {
const {store} = this.context
// 注册监听事件
store.subscribe(() => this.update())
this.update()
}
update() {
const {store} = this.context
const stateProps = mapStateToProps(store.getState())
const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
// 整合所有的参数,包括组件自身的参数、redux 中的 state 及 dispatch
this.setState({
props: {
...this.state.props,
...stateProps,
...dispatchProps
}
})
}
render() {
return <WrapComponent {...this.state.props}></WrapComponent>
}
}
}
网友评论