一. redux的实现
为什么我们需要redux,redux为我们解决了什么问题?
组件之间存在大量通信,有时这些通信跨越多个组件,或者多个组件之间共享一套数据,简单的父子组件间传值不能满足我们的需求,自然而然地,我们需要有一个地方存取和操作这些公共状态。
1. 容易误操作
有条件地操作store,防止使用者直接修改store的数据。
2. 可读性很差
给每个操作起个名字。
1. getState实现
export const createStore = () => {
let currentState = {} // 公共状态
function getState() { // getter
return currentState
}
function dispatch() {} // setter
function subscribe() {} // 发布订阅
return { getState, dispatch, subscribe }
}
2.dispatch实现
export const createStore = () => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
switch (action.type) {
case 'plus':
currentState = {
...state,
count: currentState.count + 1
}
}
}
function subscribe() {}
return { getState, subscribe, dispatch }
}
我们把对actionType的判断写在了dispatch中,这样显得很臃肿,也很笨拙,于是我们想到把这部分修改state的规则抽离出来放到外面,这就是我们熟悉的**reducer。** 我们修改一下代码,让reducer从外部传入:
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {}
return { getState, dispatch, subscribe }
}
3.subscribe实现
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
二. react-redux的实现
1. Provider实现
import React from 'react'
import PropTypes from 'prop-types'
export class Provider extends React.Component {
// 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法
static childContextTypes = {
store: PropTypes.object
}
// 实现getChildContext方法,返回context对象,也是固定写法
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
// 渲染被Provider包裹的组件
render() {
return this.props.children
}
}
2. connect实现
- connect根据store和传入的map,将state和dispatch(action)挂载子组件的props上
- 把改组件更新的方法订阅到shore中
export function connect(mapStateToProps, mapDispatchToProps) {
return function(Component) {
class Connect extends React.Component {
componentDidMount() {
//从context获取store并订阅更新
this.context.store.subscribe(this.handleStoreChange.bind(this));
}
handleStoreChange() {
// 触发更新
// 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新
this.forceUpdate()
}
render() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{ ...this.props }
// 根据mapStateToProps把state挂到this.props上
{ ...mapStateToProps(this.context.store.getState()) }
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{ ...mapDispatchToProps(this.context.store.dispatch) }
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
三. redux Middleware实现
增强dispatch
1. applyMiddleware
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from './react-redux'
import { createStore } from './store'
import { reducer } from './reducer'
let store = createStore(reducer)
function logger(store) {
let next = store.dispatch
return (action) => {
console.log('logger1')
let result = next(action)
return result
}
}
function thunk(store) {
let next = store.dispatch
return (action) => {
console.log('thunk')
return typeof action === 'function' ? action(store.dispatch) : next(action)
}
}
function logger2(store) {
let next = store.dispatch
return (action) => {
console.log('logger2')
let result = next(action)
return result
}
}
function applyMiddleware(store, middlewares) {
middlewares = [ ...middlewares ]
middlewares.reverse()
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
applyMiddleware(store, [ logger, thunk, logger2 ])
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
2. 纯函数
之前的例子已经基本实现我们的需求,但我们还可以进一步改进
- 上面这个函数看起来仍然不够“纯”,函数在函数体内修改了store自身的dispatch,产生了所谓的“副作用”
- 从函数式编程(面向功能编程)的规范出发,我们把applyMiddleware(createStore(reducer), middlewares)改成createStore(reducer, heightener),因为中间件是createStore创建时可选的是否增强的部分
// store.js
export const createStore = (reducer, heightener) => {
// heightener是一个高阶函数,用于增强createStore
//如果存在heightener,则执行增强后的createStore
if (heightener) {
return heightener(createStore)(reducer)
}
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action);
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' })//初始化store数据
return { getState, subscribe, dispatch }
}
改造applyMiddleware
const applyMiddleware = (...middlewares) => createStore => reducer => {
const store = createStore(reducer)
//声明变量dispatch,不操作store内部的dispatch,使函数更“纯”
let { getState, dispatch } = store
const params = {
getState,
dispatch: (action) => dispatch(action)
//解释一下这里为什么不直接 dispatch: dispatch
//因为dispatch执行时要用当前上下文的增强后的dispatch,不写成函数会拿到未增强的原始dispatch
}
const middlewareArr = middlewares.map(middleware => middleware(params))
dispatch = compose(...middlewareArr)(dispatch)
return { ...store, dispatch }
}
//compose这一步对应了middlewares.reverse(),是函数式编程一种常见的组合方法
function compose(...fns) {
if (fns.length === 0) return arg => arg
if (fns.length === 1) return fns[0]
return fns.reduce((res, cur) =>(...args) => res(cur(...args)))
}
代码应该不难看懂,在上一个例子的基础上,我们主要做了两个改造
-
使用compose方法取代了middlewares.reverse(),compose是函数式编程中常用的一种组合函数的方式,compose内部使用reduce巧妙地组合了中间件函数,使传入的中间件函数变成
(...arg) => mid1(mid2(mid3(...arg)))这种形式 -
不直接替换dispatch,而是作为高阶函数增强createStore,最后return的是一个新的store
摘抄转载至:juejin.cn/post/684490…