redux的实现

2019 / 03 / 30

前言

作为一个mobx的深度用户,还是很难让自己去适应一个比mobx的写法要复杂一点点redux的。网上有很多针对mobx和redux在react中使用的对比文章,都会说mobx的优点就是简单,关于两者的对比会在下周抽空写一哈。但是为什么要学redux呢,一来是因为公司需要,二来是这个也是对自己很有好处。

redux相对于mobx完全不同的设计理念可以让我从另一个角度去看待前端的数据管理机制。

下边展示的代码可以在这里获取 https://github.com/soWhiteSoColl/dodo-redux

state, action和reducer

redux的最大的特点就是单项数据流。单项数据流意味着什么呢,意味着数据流动清晰明确,数据从哪个地方到哪个地方然后发生了什么最后变成了什么整个过程非常明白,可以很容易的追溯每一个数据流动的节点。

想要用好redux,首先需要领悟一些概念,包括state, action和reducer,state和action很容易理解,reducer相对来说难一点

state

其实redux的state和react中每个组件的state含义一样,就是一个可以被改变的数据。

action

动作,唯一可以触发事件改变的方式。

reducer

如果知道数组的原型方法reduce的话就能够理解这个概念了。

给定一个数据然后返回一个新的数据,这个就是reduce,而用来做这个事情的函数就叫做reducer。

所以在redux当中reducer就是接受一个state和一个action,然后返回一个新的state的函数。

大概看起来就像这样子。

const action = { type: 'ADD', payload: 'sport' }

const initialState = {
list: ['eat', 'sleep', 'drink']
}

function reducer(state = initialState, action) {
switch (action.type) {
case ADD:
return {
...state,
list: state.list.concat(action.payload)
}

default:
return state
}
}

const newState = reducer(initialState, action)

这个demo里定义了一个state和一个action,然后经过reducer返回一个新的state。

这里一定要注意

reducer必须得返回一个默认的state,否则这就不是一个合格的reducer。

如果站在一个新人的角度来看这段代码,就会觉得好墨迹啊,我就像改变一个state为什么这么要这么复杂啊,

这段代码不就是表达了一个push么。

为什么要这么写代码呢,就是为了更好的表达数据的变化,就像之前说的单项数据流,一个数据从一个地方发生了什么,变成了什么,这个过程非常清晰。

还有一个问题,为什么不用push用concat呢,这里涉及到一个不可变数据的概念,只能用纯函数去操作数据。

不可变数据和纯函数

这个是函数式编程的概念,很多人遇到函数式编程会望而却步,感觉触及到了一个很复杂的知识盲区,其实函数式编程没那么复杂只是一种思路,当然这里不作深入研究,这里只需要知道不可变数据和纯函数是什么就行了。

顾名思义,不可变数据就是不可以被改变的数据,纯函数就是不会改变原始数据的函数,有些函数处理完某个数据,这个数据就变了,比如非常常用的数组方法push。所以为什么不可以变呢?

还是为了让数据变化是可靠的,可追溯,只要数据一直是可靠的,系统就是稳定的,构建再复杂的东西也不会乱掉。比如万一有人在其他地方使用了initialState,那这个数据被改了,这样的话initialState就不再是initalState了,他应该是currentState,如果别人一直在用这个数据写代码,肯定会出问题的。

所以上边不会去使用push,使用push的话initialState就变了,但是用concat的话,并不会改变旧的数据,然后也产生了新的数据,达到了期望的效果。

redux

上边写了那么多并没有说到redux是什么,所以redux到底是什么呢。redux是个工具,reducer,action,state是材料,redux是更好的运用这些材料的工具,很显然上边那样的reducer action用起来并不方便。

在redux里是这么使用这些东西的

const store = createStore(reducer)
store.getState() // ["eat", "sleep", "drink"]
store.dispatch({ type: 'ADD', payload: 'sport' })
store.getState() // ["eat", "sleep", "drink", "sport"]

redux创建了一个新的概念就做store,通过reducer去定义这个store的管理数据的方法,然后把reduce的过程成为dispatch action。

默认会进行一次reducer,创建初始的state,这也是上变reducer必须返回默认的state的原因。

每次dispatch就会触发一次reducer,然后store的state就会改变。

state和reducer被放在store里,所以只需要去关注如何通过dispatch去改变数据就好了。用起来是不是舒服了一丢丢呀。

这就是redux最最核心的工作方法。


redux的实现

初步认识了redux之后

redux一共有五个方法。

1. createStore上边说了。

2. combineReducers用来合并reducers的,这样就可以把不同的state和reducer分开管理的

3. applyMiddleWare 添加中间件的

4. compose 合并函数

5. bindActionCreators 绑定action的

redux.createStore

createStore有三个参数,分别是reducer,初始化用的state,和一个enhancer,enhancer在最最后的applyMiddleware里有介绍。可以先跳过。

createStore就收一个reducer返回一个store,store主要作用是通过getState获得state和通过dispatch来修改state。

另外还可以通过subscribe去实现订阅,subscribe的事件会在每次dispatch的时候执行。还有一个这里没有实现的功能是observable。subscribe和observable通常需要配合使用。这里简单的实现了subscribe。

实现一下的话大概就是这样子。

export default function createStore(reducer, initialState, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer, initialState)
}

let currentState = reducer(initialState || undefined, {})
let listeners = []

function getState() {
return currentState
}

function dispatch(action) {
currentState = reducer(currentState, action)

for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

function subscribe(listener) {
listeners.push(listener)

return function unsubscribe() {
listeners = listeners.filter(item => item === listener)
}
}

return {
getState,
dispatch,
subscribe
}
}

redux.combineReducers

这个函数接受一个对象作为参数,对象的属性必须是reducer函数,然后返回一个新的reducer。

新的reducer函数的默认state,也会和combineReducers接受的对象参数的数据格式保持一致。

const reducer = combineReducers({
todoList: todoListReducer,
counter: counterReducer
})
const store = createStore(reducer)
store.getState() // { todoList: {}, counter: {} }

实现一下的话就是这个样子

export default function combineReducers(reducers) {
return function combinedReducer(prevStates = {}, action) {
const nextStates = {}

Object.entries(reducers).forEach(([name, reducer]) => {
const prevState = prevStates[name]
const nextState = reducer(prevState, action)
nextStates[name] = nextState
})

return nextStates
}
}

redux.compose

这是一个工具函数,ramda里也有这个函数。就是把函数拼接起来

function increment(num) {
return num + 1
}
function plus(num) {
return num * 2
}

const value = compose(
increment,
plus
)(10) // 21

但需要注意的是这里不是先执行increment再执行plus,而是反着来的,相当于 increment(plus(10)),这个方法也是ramda中的一个函数,顺着写的话叫做pipe。

export default function compose(...functions) {
if (functions.length === 0) return arg => arg

if (functions.length === 1) return functions[0]

return functions.reduce((res, fn) => (...args) => res(fn(...args)))
}

redux.bindActionCreators

这个方法直译的话意思是绑定动作的创建者。

这个方法可以把dispatch函数绑定到react的class或者函数上,实现非常简单。一般也没人会使用这个函数,在react-redux的源码里有用到这个实现绑定到组件

function bindActionCreator(creator, dispatch) {
return function() {
return dispatch(creator.apply(this, arguments))
}
}

function bindActionCreators(creators, dispatch) {
if (typeof creators === 'function') {
return bindActionCreator(creators, dispatch)
}

if (creators && typeof creators === 'object') {
const res = {}
for (const key in creators) {
res[key] = bindActionCreator(creators[key], dispatch)
}
return res
}
}

redux.applyMiddleware

学习middleware的时候需要认识一个新的东西叫做enhancer或者decorator,是用来加强功能的一种写法。

下边是一个demo。可以很明白的看出来enhancer有什么用,不改变旧的函数的执行,但赋予了额外的功能。

function enhancer(fn) {
return (...args) => {
console.log('before exec fn')
const result = fn(...args)
console.log('after exec fn')
return result
}
}

fn('blablabla')
enhancer(fn)('blabla') // 带着log的fn

applyMiddleware会返回一个可以加强createStore的enhancer,enhancer也是createStore的第三个参数。如果可以理解上边的那段代码,那么applyMiddleware就能很容易的理解了。

import compose from './compose'

export default function applyMiddleware(...middlewares) {
return function enhancer(createStore) {
return function newCreateStore(...args) {
const store = createStore(...args)
const chain = middlewares.map(middleware => middleware(store))
let dispatch = compose(...chain)(store.dispatch)

return {
...store,
dispatch
}
}
}
}

middleware也会返回一个新的enhancer,但是这个enhancer是给dispatch用的。所以实现一个middleware和实现一个applyMiddleware看起来非常的相似,也和enhancer的demo的代码看起来很相似。如果可以理解koa的洋葱模型的话,那这些问题就都可以非常轻松的理解了。

下边这个是一个middleware的demo,可以在dispatch之前和之后分别进行一次log。

import { createStore, combineReducers, applyMiddleware } from 'redux'

const reducer = combineReducers({ todos })

function logger({ getState }) {
return function dispatchEnhancer(dispatch) {
return function newDispatch(action) {
console.log('will dispatch', action)
const returnValue = dispatch(action)
console.log('state after dispatch', getState())
return returnValue
}
}
}

const store = createStore(reducer, undefined, applyMiddleware(logger))


嗨,请先 登录

加载中...
(๑>ω<๑) 又是元气满满的一天哟