Redux 基础概念

1. 安装依赖

npm i redux, react-redux -S

2. 安装 Chrome 调试插件

  • React Develop Tools

  • Redux DevTools

Redux DevTools 设置:

 const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

3. createStore 源码

以下是 redux 中 createStore 代码的简化版:

function createStore (reducer, preloadedState, enhancer) {
  let currentReducer = reducer                // 存放 reducer
  let currentState = preloadedState           // 闭包,存放 state
  let currentListeners = []
  let nextListeners = currentListeners        // 存放所有的 listener
  let isDispatching = false

  function getState () {
    return currentState                       // 返回闭包中 state 的结果
  }

  function subscribe (listener) {
    nextListeners.push(listener)              // 新增 listener

    return function unsubscribe () {
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)          // 移除 listener
    }
  }

  function dispatch (action) {
    if (isDispatching) {
      // 防止在 reducer 中再次执行 dispatch
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      // 执行 dispatch 时,dispatch 为 true
      // 相当于锁住了该状态,比如,防止在 reducer 中再次执行 dispatch
      isDispatching = true
      // 将闭包的 state, 和 action 进行计算,从而更新 state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 在 dispatch 之后,通知所有的 listener
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  dispatch({ type: `@@redux/INIT+随机数` })

  return {
    dispatch,
    subscribe,
    getState
  }
}

即实际上就是通过闭包创建 state,同时对外暴露更新 state 数据( dispatch 方法)获取 state 数据( getState 方法 ) 的实现。

4. 简单示例

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import { createStore } from 'redux'

### 定义 reducer ###
const initialState = 0 // 初始 state
// reducer 就是一个函数,接受当前的 state 和 action,返回新的 state
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload
    default:
      return state
  }
}


### 定义 action ###
const action = {
  type: 'ADD',
  payload: 2
}


### Dispatch 组件 ###
const Dispatch = connect()(props => {
  function handleClick () {
    props.dispatch(action)
  }
  return <div onClick={handleClick}>
    click me, + 2
  </div>
})


### mapStateToProps ### 
const mapStateToProps = (state, ownProps) => {
  console.log(ownProps)   // ownProps 为组件自身 props,在此例中,是 { a: 1 }
  return {
    count: state
  }
}

### Show 组件 ###
const Show = connect(
  mapStateToProps
)(props => {
  console.log(props)
  return <div>
    show state {props.count}
  </div>
})

### App 组件 ###
function App () {
  return <div>
    <Dispatch />
    <Show a={1} />  // {a:1} 是 Show 组件自身的 props
  </div>
}

### 创建 Store 对象 ###
// createStore 接受一个 reducer 作为参数
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

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

如上所示,就是简单的 redux demo,来总结几点:

  • reducer 就是一个函数,接受当前的 state 和 action,返回新的 state. 可以在 reducer 中定义 state 初始值。

  • action 是一个对象,应该有 type 和 payload 两个属性,分别用于描述这是什么类型的 action,以及 该 action 携带的值。其中 type 是必填的。对于 action 还有一个社区规范, action 社区规范

  • 一个组件被 connect 之后,props 上就会有 dispatch 方法,该方法的使用示例: dispatch(action)

  • 如果一个组件需要接受 redux 中的 state,必须定义 mapStateToProps, mapStateToProps 的第二个参数 ownProps 为组件自身的 props。 此时当 redux 中的 state 发生变化,props 就会发生改变。

5. 什么是 Action Creator

在之前的示例中,我们这样定义 action

const action = {
  type: 'ADD',
  payload: 2
}

这样的问题在于,有多少个 action 就需要定义多少个这样的变量。于是就诞生了 Action Creator

function addTodo(text) {
  return {
    type: 'ADD',
    text
  }
}
const action = addTodo('Learn Redux');

即,ction Creator 就是一个函数,用于创建 action

6. Reducer

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

可能会好奇,为什么这个函数叫做 reducer 呢? 因为,这个函数可以作为 数组 reduce 方法的参数

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

// arr.reduce((previousValue, current) => {})
const total = actions.reduce(reducer, 0); // 3

6.1 reducer必须是纯函数

因此 Reducer 函数里面不能改变 state,而必须返回一个全新的对象:

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
}

7. Reducer 的拆分

在简单示例中,redux state 是一个简单值,state = 0

在实际开发中, state 可能是一个对象: state = { foo: xxx, bar: xxx }

示例代码:

const initialState = { // 初始 state
  foo: {
    count: 0
  },
  bar: {
    count: 0
  }
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_FOO':
      return Object.assign({}, state, {
        foo: {
          count: state.foo.count + 1
        }
      })
    case 'ADD_BAR':
      return Object.assign({}, state, {
        bar: {
          count: state.bar.count + 2
        }
      })
    default:
      return state
  }
}

const actionFoo = {
  type: 'ADD_FOO',
  payload: 1
}

const actionBar = {
  type: 'ADD_BAR',
  payload: 2
}

const Dispatch = connect()(props => {
  return <div>
    <div onClick={() => props.dispatch(actionFoo)}>
    action foo, + 1
    </div>
    <div onClick={() => props.dispatch(actionBar)}>
    action bar, + 2
    </div>
  </div>
})

const mapStateToPropsFoo = (state, ownProps) => {
  return {
    foo: state.foo
  }
}

const Foo = connect(
  mapStateToPropsFoo
)(props => {
  return <div>
    foo {props.foo.count}
  </div>
})

const mapStateToPropsBar = (state, ownProps) => {
  return {
    bar: state.bar
  }
}

const Bar = connect(
  mapStateToPropsBar
)(props => {
  return <div>
    bar {props.bar.count}
  </div>
})

function App () {
  return <div>
    <Dispatch />
    <Foo />
    <Bar />
  </div>
}

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

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

在上面这个示例中, foo 和 bar 是 state 的两个根属性(实际开发中,可能对应两个子组件,子页面),其实是互不影响,但是他们的 Reducer 却写在了同一个文件中,在大型应用中,必然导致 Reducer 函数非常庞大

于是我们尝试将 Reducer 拆分为两个小文件:

// 单独的 foo reducer
// 此时的 state 即为 state.foo
const fooReducer = (state, action = {}) => {

  switch (action.type) {
    case 'ADD_FOO':
      return Object.assign({}, state, {
        count: state.count + action.payload
      })
    default:
      return state
  }
}

// 单独的 bar reducer
const barReducer = (state, action = {}) => {
  switch (action.type) {
    case 'ADD_BAR':
      return Object.assign({}, state, {
        count: state.count + action.payload
      })
    default:
      return state
  }
}

// 初始 state
const initialState = {
  foo: {
    count: 0
  },
  bar: {
    count: 0
  }
}

const reducer = (state = initialState, action = {}) => {
  return {
    // 只传入对应的 state 根属性
    foo: fooReducer(state.foo, action),
    bar: barReducer(state.bar, action)
  }
}

即通过将对应的 子 state,传入到子 reducer 中,从而更新根 state.

redux 内部也暴露了 combindReducers 来组合 子 reducer:

注意,默认 state 的定义划分到子的 reducer 里了

import { createStore, combineReducers } from 'redux'

// 注意, 此时 initialState 是在这里定义 `state = { count: 0 }`
// 最终形成的根初始state 为 `state = { foo : { count: 0 } }`
const fooReducer = (state = { count: 0 }, action = {}) => {
  switch (action.type) {
    case 'ADD_FOO':
      return Object.assign({}, state, {
        count: state.count + action.payload
      })
    default:
      return state
  }
}

const barReducer = (state = { count: 0 }, action = {}) => {
  switch (action.type) {
    case 'ADD_BAR':
      return Object.assign({}, state, {
        count: state.count + action.payload
      })
    default:
      return state
  }
}

const reducer = combineReducers({
  foo: fooReducer,
  bar: barReducer
})

// 等同于
function reducer(state = {}, action) {
  return {
    foo: fooReducer(state.foo, action),
    bar: barReducer(state.bar, action)
  }
}

因此,我们可以大概写出 combineReducers 的实现:  
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        // 如 nextState['foo'] = reducers['foo'](state['foo'], action);
        return nextState;
      },
      {} 
    );
  };
};

同时,如果 fooReducer 改为 foo, 那么可以简写:

import { createStore, combineReducers } from 'redux'

// 命名为 foo
const foo = (state = { count: 0 }, action = {}) => {
  switch (action.type) {
    case 'ADD_FOO':
      return Object.assign({}, state, {
        count: state.count + action.payload
      })
    default:
      return state
  }
}

// 可以简写
const reducer = combineReducers({
  foo,
  bar
})

8. 工作流程梳理

以下是 redux 的工作流程图:

  1. store 通过 matchStateToProps,将数据更新到 React Component 上

  2. 组件点击时,dispatch( actionCreator(xx) ), 其中 actionCreator(xx) 会生成一个 action

  3. 当 dispatch(action) 时,redux 内部会将 (state,action) 作为参数传入到 Reducers 里

  4. Reducers 通过执行完,返回新的 state。此时 store 通过 matchStateToProps 再更新 React Component

参考文档

Last updated