Loading...
墨滴

Phil

2021/04/28  阅读:13  主题:前端之巅同款

redux

理解redux, 并简单实现

redux 详细用法参考官网

redux 作用

Redux is a predictable state container for JavaScript apps.

简单来说, redux 就是一个state容器。 在react 项目中, 我们可以通过redux 来保存共享的状态。

redux 核心API

store

存储数据的容器

{
 todos: [{
   text'Eat food',
   completedtrue
 }, {
   text'Exercise',
   completedfalse
 }],
}
  • store中常用api
    • getState() // 获取store 中state
    • dispatch(action) // 触发相应action
    • subscribe(listener) // 订阅监听, 即监听dispatch触发
    • ...

Reducers

to tie state and actions together, we write a function called a reducer. it’s just a function that takes state and action as arguments, and returns the next state of the app.

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

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

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

Actions

An action is a plain JavaScript object that describes what happened

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",
      completedtrue,
    },
    {
      text"Exercise",
      completedfalse,
    },
]

const reducer = (state = defaultState, action) => {
    const { type, payload } = action || {}
    const { text } = payload || {}
    switch (type) {
        case 'ADD_TODO':
          return state.concat([{ text, completedfalse }])
        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 , 返回一个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 引用换成我们自己写的, 效果是一样的。

  • 实现combineReducers
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
    }
}

countReducer.js

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

export default countReducer

修改app.js

...
    handleConfirm = () => {
        const { value } = this.state
        if (value) {
            const { dispatch } = store
            dispatch({ type'ADD'payload1})
            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
combineReducers
  • 增强createStore: createStore接受第二个参数enhancer

compose.js

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

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

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

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



// 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
// }

// f3(f2(f1('omg')))

// compose(f1, f2, f3) 返回一个函数

// console.log(compose(f1, f2, f3)("omg"));

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

// import { createStore, combineReducers, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import applyMiddleware from '../cRedux/applyMiddleware'
import { createStore, combineReducers } from '../cRedux'
import { todosReducer, countReducer } from '../reducer'

const reducers = combineReducers({
    todosReducer,
    countReducer
})

const enhancer = applyMiddleware(thunk, logger)

const store = createStore(reducers, enhancer)

export default store

更新app.js

...
    handleADD = () => {
        const { dispatch } = store
        // dispatch({ type: 'ADD', payload: 1})
        // 如果想要异步, 一般两种方法,
        // 1. 扩展dispatch方法,使得支持异步, 形式dispatch(fn), fn为一个异步方法
        dispatch((dispatch, getState) => {
            setTimeout(() => {
                dispatch({ type'ADD'payload1})
            }, 500)
        })
        // 2. 将dispatch放到异步执行完之后执行
        // setTimeout(() => {
        //     dispatch({ type: 'ADD', payload: 1})
        // }, 500)
    }
...
增强createStore结果
增强createStore结果

通过applyMiddleware, 使用了中间件redux-thunk、和redux-logger, 来分别使得store.dispatch支持接受函数, 和调用时显示响应操作log

完整代码

完整代码

Phil

2021/04/28  阅读:13  主题:前端之巅同款

作者介绍

Phil