完全掌握redux — 源码剖析
认识redux
前言:什么是redux?我们为什么要用它?我们什么时候用它?redux只能用在react当中吗?他和react-redux什么关系?
问:什么是redux?
答:redux是一个去中心化的状态管理库,它保证了程序行为一致性且易于测试
问:我们为什么要用它?
答:它可以很好的帮助我们对状态的改变进行监听,并且可以在任意地方拿到所有存在的状态,当然你也可以在任意地方对这些状态进行更改。它很好的解决了逐级传递数据时候造成的代码复杂度和难维护性、是同级数据共享的优雅解决方案
问:我们什么时候用它?
答:当你不用它,代码的复杂度更高时,你就需要用它
问:redux只能用在react当中吗?
答:不是的,它可以用在任何js程序中
问:他和react-redux什么关系?
答:Redux只是一个状态管理库,没有任何界面相关的东西;react-redux将react跟redux结合起来,用到了一些react的API
redux核心概念
- 需要一个store来存储数据
- store里的reducer初始化state并定义state修改规则
- 通过dispatch传入action来通知reducer按照action使用的reducer规则来进行state修改
Reducers接收旧的 state 和 action,返回新的 state
action是Reducers中的某一个规则,dispatch是调用action的方法
举例:可以理解为一个人背着书包去自动售货机买可乐,书包就是store,reducers是自动售货机,action是投进去的硬币,dispatch是投硬币的动作
认识reduce函数
正式开始前,我们再来点开胃菜,掌握一个es6的内置数组方法Array.reduce
让我们看一段代码
const arr = [1, 2, 3]
const reducer = (accumulator, currentValue) => accumulator + currentValue
const result = arr.reduce(reducer)
console.log(result)
点击展开查看答案
1 + 2 + 3 = 6
是不是很简单,那我们来改变一下
我们把数字变成函数再看看
function f1 (arg) {
console.log('f1===', arg)
return arg
}
function f2 (arg) {
console.log('f2===', arg)
return arg
}
function f3 (arg) {
console.log('f3===', arg)
return arg
}
const fn = [f1, f2, f3].reduce((acc, current) => (...arg) => acc(current(...arg)))
const fn = [f1, f2, f3].reduce((acc, current) => {
return (...arg) => {
return acc(current(...arg)) // f1(f2(...arg)) ===> fuction a (...arg) { return f1(f2(...arg)) }
// function b (...arg) { return a(f3(...arg)) }
// b('yzy')
// a(f3('yzy')) ===> f1(f2(f3('yzy')))
// a('yzy')
// f1(f2('yzy'))
// f1('yzy')
}
})
fn('yzy')
点击展开查看答案
答案就是:
f3===, yzy
f2===, yzy
f1===, yzy
fn('yzy') 可以看作为 fn(f1(f2(f3())))
详细应为:
第一次: function a (...arg) { return f1(f2(...arg)) }
第二次: function b (...arg) { return a(f3(...arg)) }
第三次: b('yzy')
执行后:fn(f1(f2(f3())))
扩展知识点:这里我们可以写成一个函数compose【注意:这就是redux源码中的compose】
function compose (...funcs) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((acc, current) => (...arg) => acc(current(...arg)))
}
有没有感觉似曾相识,这就是函数式编程中常用到的compose,后期我会单独做一次函数式编程的分享,这里简单的解释一下为什么compose是函数式编程中常用到的基础函数,因为函数式编程讲究柯里化,就是一个函数只干一件事情。
举例:我们有一个业务需求:把大象放进冰箱里。通常我们可能会写一个方法putElephantInBox,整个放大象的逻辑我们都会写在这个函数中。但如果是函数式编程呢?我们就得一步一步来了。那么把大象放进冰箱分几步?第一步把冰箱门打开(openBox)、第二步把大象放进去(putElephant)、第三步把冰箱门关上(closeBox)。但是我们的需求是把大象放进冰箱,我们需要把这个三个函数合并起来,这个时候就需要用到compose。
redux使用样例
创建一个store,src/store/index.js
import { createStore } from 'redux'
// reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state
// 初始化状态
const initState = {
count: 0
}
// 创建更新规则
function reducer (state = initState, action) {
switch (action.type) {
case 'add': return { ...state, count: state.count + 1 }
default: return state
}
}
export default createStore(reducer)
创建一个使用页面test,src/reduxTest.js
import React from 'react'
import store from './store'
class ReduxTest extends React.Component {
componentDidMount () {
store.subscribe(() => {
this.forceUpdate()
})
}
add = () => {
store.dispatch({
type: 'add'
})
}
render () {
return (
<div>
<h3>{store.getState().count}</h3>
<button click={this.add}>点击累加</button>
</div>
)
}
}
// 巧妙一点 --- 更新放在根文件下
import ReactDOM from 'react-dom'
import store from './src'
const render = () => {
ReactDOM.render(<App />, document.getElementById('#app'))
}
render()
store.subscribe(render)
准备工作算是就绪了,到现在为止你应该觉得很简单吧!确实很简单,鉴于redux的核心源码真的很短小,接下来我们直接写一个吧!
手写reduce源码
1.创建一个createStore.js
function createStore (reducer, enhancer) {
if (enhancer) {
enhancer(createStore)(reducer)
}
let currentState
let currentListeners = []
function getState () {
return currentState
}
function dispatch (action) {
currentState = reducer(currentState, action)
currentListeners.forEach(listener => {
listener()
})
return action
}
function subscribe (listenerCallback) {
currentListeners.push(listenerCallback)
// 返回退订函数
return () => {
const index = currentListeners.indexOf(listenerCallback)
currentListeners.splice(index, 1)
// splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目
// 注释:该方法会改变原始数组
}
}
// 初始化
dispatch({type: 'yzy'})
return {
getState,
dispatch,
subscribe
}
}
2.创建一个中间件applyMiddleware.js
// 首先我们看看中间件是怎么使用的:createStore(reducer, applyMiddleware(thunk, logger))
// 所以我们首先得返回一个函数,这个函数做了什么?
// 答:这个函数接收createStore作为参数执行并返回一个函数接收reducer
// 这里也是一个闭包
function applyMiddleware (...middlewares) {
return (createStore) => (reducer) => {
// 我想,看到这里你一定会觉得很巧妙吧
const store = createStore(reducer)
const getState = store.getState
let dispatch = store.dispatch
let enhancerMid = {
getState: getState,
dispatch: (action, ...args) => dispatch(action, ...args),
}
// 执行中间件
const middlewareChain = middlewares.map((middleware) => middleware(enhancerMid)) // thunk({getState, dispatch})
// const middlewareChain = [thunk({getState, dispatch}), logger({getState, dispatch})]
// 得到加强版的dispatch
dispatch = compose(...middlewareChain)(store.dispatch)
return {...store, dispatch}
}
}
function compose(...funcs) {
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((acc, current) => (...args) => acc(current(...args)));
}
3.编写中间件使用一下
function thunk ({getState, dispatch}) => {
return (next) => (action) => {
if (typeof action === "function") {
return action(dispatch, getState)
}
return next(action)
}
}
function logger({getState}) {
return (next) => (action) => {
console.log("====================================")
console.log(action.type + "执行了!") //sy-log
const prevState = getState()
console.log("prev state", prevState) //sy-log
const returnValue = next(action)
const nextState = getState()
console.log("next state", nextState) //sy-log
console.log("====================================")
return returnValue
}
}
// 这里为什么要这么写,我们来分析一下
// 假设现在就是thunk和logger这两个中间件
// 首先我们知道middlewareChain这个数组里面都是这样的函数:(next) => (action) => { ... },
// 这里我们把数组中的两个函数分别命名为 _thunk 和 _logger
// componse里面返回的就是(...arg) => _thunk(_logger(...arg)),我们把这个函数命名为resFn
// resFn(store.dispatch) 将会得到 _thunk(_logger(store.dispatch))
// _logger(store.dispatch)执行后得到:
// (action) => { console.log("===") ... console.log("===") return returnValue },我们把这个函数命名为logger_core
// 上面这个函数作为参数next被传入到_thunk函数
// _thunk(logger_core)被执行
// 返回函数(action) => { if (typeof action === 'function') { ... } return next(action) }
// 经过判断传入的参数action是一个函数,故执行这个函数,并传入修改规则action
// logger_core(action)被执行
// 注意:(action) => { if (typeof action === "function") { ... } return next(action) }是最终被使用的dispatch
// 结束
到这里,咱们的redux源码剖析就讲完了,其实不难,但确实需要好好消化一下。redux的源码短,但是很精妙,作为学习源码的道路上是一个非常好的案例,因为他不会长到让你没有看下去的信心,看懂以后对你将来写代码的思路会有很大的帮助,同时在看其他开源库的源码时也能给你更多的信心。