xingbofeng / xingbofeng.github.io

counterxing的博客
https://xingbofeng.github.io
175 stars 18 forks source link

Redux深入学习 #7

Open xingbofeng opened 7 years ago

xingbofeng commented 7 years ago

动机

基本思想

三个原则

基础

Action

Action是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch()action 传到 store

const CHANGE_NUM = 'CHANGE_NUM'
{
  type: CHANGE_NUM,
  result: 2,
}

action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action

Action 创建函数

Redux 中的action创建函数只是简单的返回一个 action:

function changeNum(result) {
  return {
    type: CHANGE_NUM,
    result
  }
}

这样做将使action创建函数更容易被移植和测试。

Reducer

Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新state。而这正是 reducer 要做的事情。

reducer 就是一个纯函数,接收旧的 stateaction,返回新的 state

(previousState, action) => newState

state 范式化:开发复杂的应用时,不可避免会有一些数据相互引用。建议你尽可能地把 state范式化。把所有数据放到一个对象里,每个数据以ID为主键,不同实体或列表间通过 ID 相互引用数据。把应用的 state 想像成数据库。这种方法在 normalizr 文档里有详细阐述。

保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:

function numApp(state = { num: 1 }, action) {
  switch (action.type) {
    case CHANGE_NUM:
      return Object.assign({}, state, {
        num: action.result,
      })
    default:
      return state
  }
}

如果状态很庞大,可以拆分多个子reducer,每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducerstate 参数都不同,分别对应它管理的那部分 state 数据

最后所有的子 reducer 可以用 combineReducers() 合并,combineReducers 接收一个对象,生成一个函数,这个函数来调用你的一系列reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。

const app = combineReducers({
  num,
  person,
})

Store

Store 有以下职责:

根据已有的 reducer 来创建 store : let store = createStore(app)

完整例子:

import {createStore} from 'redux'
//action函数
const CHANGE_NUM = 'CHANGE_NUM'
function changeNum(result) {
  return {
    type: CHANGE_NUM,
    result
  }
}

//reducer函数
function numApp(state = { num: 1 }, action) {
  switch (action.type) {
    case CHANGE_NUM:
      return Object.assign({}, state, {
        num: action.result,
      })
    default:
      return state
  }
}

// store

let store = createStore(numApp)

console.log(store.getState())

// 每次 state 更新时,打印日志
// subscribe()返回一个函数用来注销监听器
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

// 发起一系列 action
store.dispatch(changeNum(1))
store.dispatch(changeNum(2))
store.dispatch(changeNum(3))

unsubscribe() // 注销监听器

store.dispatch(changeNum(3))

Redux & React

React 更多的是 view 层,每个组件本身是通过propsstate的更新来更新组件。state是各个组件内部的属性,外部无法较好的访问和控制。所以 ReduxReact App 状态的控制是通过 props 完成的。

将store注入组件

使用指定的 react-redux 组件 <Provider> 可以让所有组件都可以访问 store ,而不必显式地传递它。

因为 storestate 是整个 App 的,当前组件并不一定都需要。不显式传递的好处是,组件可以需要什么 state ,就拿什么。

//index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import numApp from './reducers'
import App from './components/App'

let store = createStore(numApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

根组件想获取到store,可使用 react-reduxconnect() 方法来生成。

const mapStateToProps = (state) => {
  return {
    num:  state.num,
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    changeNum: (result) => {
      dispatch(changeNum(result))
    }
  }
}
import { connect } from 'react-redux'

export default connect(mapStateToProps, mapDispatchToProps)(App);

一般只需要在渲染根组件时使用即可,这样做可以保证数据流从上至下的一致性。

高级

异步Action

Middleware

Redux middleware提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

通过一个例子理解Middleware 记录日志:记录每次触发的action以及state变化

let action = changeNum(2)

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
function dispatchWithLog(store, action) {
    console.log('dispatching', action)
    store.dispatch(action)
    console.log('next state', store.getState())
}

// 然后用它替换原本的dispatch
dispatchAndLog(store, changeNum(2))
let next = store.dispatch
store.dispatch = function dispatchWithLog(action) {
    console.log('dispatching', action)
    next(action)
    console.log('next state', store.getState())
}

到此,每次触发一个action都会有记录

function enhanceDispatchWithLog(store) {
    let next = store.dispatch
    return function dispatchWithLog(action) {
        console.log('dispatching', action)
        let result = next(action)
        console.log('next state', store.getState())
        return result
    }
}

// 改变dispatch的行为
const dispatchWithLog = enhanceDispatchWithLog(store)
// 触发一个action
dispatchWithLog(changeNum(2))

到此,完成了一个dispatch的改造函数。此时出现了另一个问题,我们还想给dispatch添加其他功能,应该怎么办呢

比如我们想在dispatch函数里,添加自定义的callback,以便我们在每次触发action时,可以调用callback函数。

const callback = () => { //do somethine... }

function enhanceDispatchWithCallback(store) {
    let next = store.dispatch
    return function dispatchWithCallback(action) {
        let result = next(action)
        callback()
        return result
    }
}

const dispatchWithCallback = enhanceDispatchWithCallback(store)

dispatchWithCallback(changeNum(2))

到此我们又改造了一个dispatch函数。但是上述的改造是一个替换过程,即连续多个dispatch的改造只有最后一个会生效。因此新的需求又来了 —— 如何让dispatch函数同时具有以上两个增强功能呢

想要实现这个需求,我们需要将每次dispatch增强函数改造后的dispatch(在此称为next),传进下一个改造函数,使得每次增强前,dispatch都已经具备了之前增强功能


function enhanceDispatchWithLog(store) {
    // let next = store.dispatch
    return function newDispatch(next) {
        return function dispatchWithLog(action) {
            console.log('dispatching', action)
            let result = next(action)
            console.log('next state', store.getState())
            return result
        }
    }    
}

const callback = () => { //do something... }
function enhanceDispatchWithCallback(store) {
    // let next = store.dispatch
    return function newDispatch(next) {
        return function dispatchWithCallback(action) {
            let result = next(action)
            callback()
            return result
        }
    }
}

// 上面就是`middleware`的普遍实现,`middleware` 接收了一个名为 `next` 的 `dispatch 函数`,并返回一个`新的 dispatch 函数`,返回的函数会被作为下一个 `middleware` 的 `next()`,以此类推。  接着把它们写成箭头函数的形式

const enhanceDispatchWithLog = store => next => action => {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
}

// 第二个middleware需要先把callback包起来

const wrapDispatchCallback = (callback) => {
    return store => next => action => {
        let result = next(action)
        if (callback && typeof callback === 'function') callback()
        return result
    }
}

const callback = () => { //do something... }

// 最终middleware如下:
const enhanceDispatchWithCallback = wrapDispatchCallback(callback)

// 现在在写一个函数实现middleware的级联, 其目的为:传入原始store和middlewares,返回改造过dispatch方法的store

const applyMiddleware = function(store, ...middlewares) {
    let dispatch = store.dispatch
    middlewares.reverse().forEach((middleware) => {
        dispatch = middleware(store)(dispatch)
    })

    return Object.assign({}, store, { dispatch })
}

为了保证只能应用 一次middlewareapplyMiddleware将作用在createStore()上而不是 store 本身。因此他的输入输出由(store, middlewares) => { return store }, 改为 (...middlewares) => createStore => { return createStore }

const applyMiddleware = (...middlewares) => createStore => {
    return (store) => {
        const createdStore = createStore(store)
        let dispatch = createdStore.dispatch
        middlewares.reverse().forEach((middleware) => {
            dispatch = middlewares(createdStore)(dispatch)
        })
        return Object.assign({}, createdStore, { dispatch })
    }
}