实现redux

212 阅读3分钟

redux 作用

简单来说, redux 就是一个state容器。 类似于我们在开发web应用中,把数据挂载到windows对象上,然后取数据从windows中取, 但是这样容易污染上下文,同时数据变动不可预测。 而redux中自定义state,只能通过getState获取数据, 不会污染上下文。同时只能通过dispatch action来触发相应的reducer,数据变动可以预测(单向)

redux 核心API

store 存储数据的容器

  • store中常用api
    • getState() // 获取store 中state
    • dispatch(action) // 触发相应action
    • subscribe(listener) // 订阅监听, 即监听dispatch触发
    • ...

下面使用的例子, state 基本结构如下

{
 todos: [{
   text: 'Eat food',
   completed: true
 }, {
   text: 'Exercise',
   completed: false
 }],
}

Reducers

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([{ text: action.text, completed: false }])
    default:
      return state
  }
}

reducer 是一个纯函数, 将state 和 action相关联, 接受(state, action) 作为参数, 返回一个新的state。

简单理解: 根据规则改变state

Actions

{ type: 'ADD_TODO', text: 'Go to swimming pool' }

action 是一个 plain object (普通对象)的形式,在使用中, 我们一般定义的格式: { type: '', payload: {}}

action定义了修改state的规则。 只有通过dispatch(触发) action 才能修改store中相应的state

使用redux

  1. store
import { createStore } from 'redux'
import todoReducer from '../reducer'

const store = createStore(todoReducer)

export default store
  1. reducer
const defaultState = [
    {
      text: "Eat food",
      completed: true,
    },
    {
      text: "Exercise",
      completed: false,
    },
]

const reducer = (state = defaultState, action) => {
    const { type, payload } = action || {}
    const { text } = payload || {}
    switch (type) {
        case 'ADD_TODO':
          return state.concat([{ text, completed: false }])
        default:
          return state
    }
}

export default reducer
  • app.js
import React from 'react'
import store from './store'
import './App.css';

class App extends React.Component {
    state = {
        value: '',
    }
    componentDidMount() {
        store.subscribe(() => {
            this.forceUpdate()
        })
    }
    handleConfirm = () => {
        const { value } = this.state
        if (value) {
            const { dispatch } = store
            dispatch({ type: 'ADD_TODO', payload: { text: value } })
            this.setState({
                value: ''
            })
        }
    }
    handleChange = (e) => {
        this.setState({
            value: e.target.value
        })
    }
    render() {
        const { value } = this.state
        const { getState } = store
        const todoList = getState()
        return (
            <div className="App">
                <input value={value} onChange={this.handleChange} />
                <button onClick={this.handleConfirm}>确定</button>
                {(todoList || []).map((todoItem, index) => {
                    const { text } = todoItem || {}
                    return <div key={index}>
                        内容: { text }
                    </div>
                })}
            </div>
          );
    }
}

export default App;
  • result result

实现redux 核心api

  • 实现createStore
    createStore函数接受reducer, 返回一个store. 其中store包含dispatch, getState, subscribe等方法
const createStore = (reducer) => {
    let currentState
    let currentListeners = []
    let nextListeners = currentListeners
    const dispatch = (action) => {
        currentState = reducer(currentState, action)
        currentListeners = nextListeners
        currentListeners.forEach(listener => listener())
        return action
    }

    const getState = () => {
        return currentState
    }

    const ensureCanMutateNextListeners = () => {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }

    const subscribe = (listener) => {
        nextListeners.push(listener)
        ensureCanMutateNextListeners()
        return () => {
            ensureCanMutateNextListeners()
            const index = nextListeners.indexOf(listener)
            nextListeners.splice(index, 1)
            currentListeners = null
        }
    }

    const store = {
        dispatch,
        getState,
        subscribe,
    }

    dispatch({type: 'init_____'})

    return store
}

export {
    createStore
}

现在将 上面实例中的redux 引用换成我们自己写的, 效果是一样的。

代码参考仓库中Tag 1.0 部分

  • 实现combineReducers

在实际中, 我们不会只存在一个reducer, 而是有多个reducer, 所以combineReducers 方法提供将多个reducer包裹起来执行, 返回一个新reducer函数

const combineReducers = (reducers) => {
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}
    for(let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }
    const finalReducerKeys = Object.keys(finalReducers)
    return (state = {}, action) => {
        const nextState = {}
        let hasChanged = false
        for (let i = 0; i < finalReducerKeys.length; i++) {
            const key = finalReducerKeys[i]
            const reducer = finalReducers[key]
            const prevStateForKey = state[key]
            const nextStateForKey = reducer(prevStateForKey, action)
            nextState[key] = nextStateForKey
            hasChanged = hasChanged || nextStateForKey !== prevStateForKey
        }
        hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
        return hasChanged ? nextState : state
    }
}

案例中增加一个recuder(只是例子,现实中不会这么写), 用于描述todoList 个数

countReducer.js

const countReducer = (state = 2, action) => {
    const {type, payload} = action
    switch (type) {
        case 'ADD': return state + payload
        default: return state
    }
}

export default countReducer

修改app.js

...
    handleConfirm = () => {
        const { value } = this.state
        if (value) {
            const { dispatch } = store
            dispatch({ type: 'ADD', payload: 1})
            dispatch({ type: 'ADD_TODO', payload: { text: value } })
            this.setState({
                value: ''
            })
        }
    }
  ...
      render() {
        const { value } = this.state
        const { getState } = store
        const { todosReducer: todoList, countReducer: count } = getState()
        return (
            <div className="App">
                <input value={value} onChange={this.handleChange} />
                <button onClick={this.handleConfirm}>确定</button>
                <div>{count}</div>
                {(todoList || []).map((todoItem, index) => {
                    const { text } = todoItem || {}
                    return <div key={index}>
                        内容: { text }
                    </div>
                })}
            </div>
          );
    }

combineReducers

代码参考仓库中Tag 2.0 部分

  • 实现增强createStore

createStore接受第二个参数enhancer。 现实中我们可能需要查看log日志,同时我们触发的dispatch 可能是异步的, 但是上面实现中, dispatch 只能接受普通的对象。 我们通过middleware 增强dispatch, 让dispatch支持接受函数。

我们需要安装 redux-thunk(支持异步), redux-logger(支持日志)。 因为这些middleware 不在redux 范围内, 所以这次我们不实现, 源码也很简单, 可以参考github 上面

compose.js

function compose(...funcs) {
    if (funcs.length === 0) {
        return (arg) => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}


export default compose

applyMiddleware.js

import compose from '../compose'
function applyMiddleware(...funcs) {
    return (createStore) => {
        return (reducer) => {
            const store = createStore(reducer)
            let dispatch = store.dispatch
            const middlewareApi = {
                getState: store.getState,
                dispatch: (action, ...args) => dispatch(action, ...args)
            }

            const chain = funcs.map(func => func(middlewareApi))
            dispatch = compose(...chain)(dispatch)

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

export default applyMiddleware

更新store.js

import logger from "redux-logger"
import thunk from "redux-thunk"
import { createStore, applyMiddleware } from '../cRedux'
import reducers from '../reducer'

const enhancer = applyMiddleware(thunk, logger)

const store = createStore(reducers, enhancer)

export default store

更新app.js


    handleConfirm = () => {
        const { value } = this.state;
        if (value) {
            const { dispatch } = store;
            // dispatch({ type: "ADD_TODO", payload: { text: value } });
            // 使用redux-thunk 支持异步
            dispatch((dispatch, getState) => {
                setTimeout(() => {
                    dispatch({ type: "ADD_TODO", payload: { text: value } });
                }, 1000)
            })
            dispatch({ type: "ADD", payload: 1 })
            this.setState({
                value: "",
            });
        }
    };

增强createStore结果

代码参考仓库中Tag 3.0 部分

完整代码

完整代码