完全掌握redux — 源码剖析

吐槽君 分类:javascript

认识redux

前言:什么是redux?我们为什么要用它?我们什么时候用它?redux只能用在react当中吗?他和react-redux什么关系?


问:什么是redux?

答:redux是一个去中心化的状态管理库,它保证了程序行为一致性且易于测试

问:我们为什么要用它?

答:它可以很好的帮助我们对状态的改变进行监听,并且可以在任意地方拿到所有存在的状态,当然你也可以在任意地方对这些状态进行更改。它很好的解决了逐级传递数据时候造成的代码复杂度和难维护性、是同级数据共享的优雅解决方案

问:我们什么时候用它?

答:当你不用它,代码的复杂度更高时,你就需要用它

问:redux只能用在react当中吗?

答:不是的,它可以用在任何js程序中

问:他和react-redux什么关系?

答:Redux只是一个状态管理库,没有任何界面相关的东西;react-redux将react跟redux结合起来,用到了一些react的API


redux核心概念

  1. 需要一个store来存储数据
  2. store里的reducer初始化state并定义state修改规则
  3. 通过dispatch传入action来通知reducer按照action使用的reducer规则来进行state修改

redux flow

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的源码短,但是很精妙,作为学习源码的道路上是一个非常好的案例,因为他不会长到让你没有看下去的信心,看懂以后对你将来写代码的思路会有很大的帮助,同时在看其他开源库的源码时也能给你更多的信心。

回复

我来回复
  • 暂无回复内容