1. 抽离 dispatch公共方法
    1. 让更新的过程,可以被描述 {type, payload}
  2. 抽离 action方法
    1. dispatch({type: 'add', payload: data})

dispatch

dispatch接管事件,统一派发动作 action
全局拦截和 debugger

  1. import React, { useState, useCallback, useEffect } from 'react'
  2. const TODO_KEY = '_TODO_'
  3. function TodoList() {
  4. const [data, setData] = useState([])
  5. // 让更新的过程,可以被描述 {type: '', payload}
  6. const dispatch = useCallback((action) => {
  7. const {type, payload} = action
  8. switch (type) {
  9. case 'set':
  10. return setData(payload)
  11. case 'add':
  12. return setData(state => [payload, ...state])
  13. case 'remove':
  14. return setData(state =>
  15. state.filter(todo => todo.id !== payload))
  16. case 'finish':
  17. return setData(state => state.map(todo =>
  18. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
  19. )
  20. default:
  21. }
  22. }, [])
  23. // componentDidMount
  24. useEffect(() => {
  25. const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
  26. dispatch({type: 'set', payload: getData})
  27. }, [])
  28. // componentDidUpdate
  29. useEffect(() => {
  30. sessionStorage.setItem(TODO_KEY, JSON.stringify(data))
  31. }, [data])
  32. }

action

函数统一了,就需要用参数来区分:

  1. 优化 action,用工厂函数创建 action
  2. 引入 actionCreator概念,用 dispatch派发 action ```javascript export function createSet(payload) { return { type: ‘set’, payload } }

export function createAdd(payload) { return { type: ‘add’, payload } }

  1. 页面使用 action
  2. ```jsx
  3. import React, { useState, useCallback, useEffect } from 'react'
  4. import {
  5. createSet,
  6. createAdd,
  7. createFinish,
  8. createRemove
  9. } from './action'
  10. const TODO_KEY = '_TODO_'
  11. function TodoList() {
  12. const [data, setData] = useState([])
  13. // 需要传递的函数,用 useCallback包裹起来
  14. const dispatch = useCallback((action) => {
  15. const {type, payload} = action
  16. switch (type) {
  17. case 'set':
  18. return setData(payload)
  19. case 'add':
  20. return setData(state => [payload, ...state])
  21. case 'remove':
  22. return setData(state =>
  23. state.filter(todo => todo.id !== payload))
  24. case 'finish':
  25. return setData(state => state.map(todo =>
  26. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
  27. )
  28. default:
  29. }
  30. }, [])
  31. // componentDidMount
  32. useEffect(() => {
  33. const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
  34. dispatch(createSet(getData))
  35. // before
  36. // dispatch({type: 'set', payload: getData})
  37. }, [])
  38. return (
  39. <section>
  40. <InputItem dispatch={dispatch} />
  41. <ListItem
  42. dispatch={dispatch}
  43. data={data}
  44. />
  45. </section>
  46. )
  47. }
  48. // child
  49. function TodoItem({ data, dispatch }) {
  50. const onChange = useCallback(function (id) {
  51. dispatch(createFinish(id))
  52. // before
  53. // dispatch({ type: 'finish', payload: id })
  54. }, [])
  55. const onRemove = useCallback(function (id) {
  56. dispatch(createRemove(id))
  57. // before
  58. // dispatch({ type: 'remove', payload: id })
  59. }, [])
  60. return (
  61. <ul>
  62. {
  63. data.map(item => {
  64. const { id, finish, value } = item || {}
  65. return (
  66. <li key={id} className={finish ? 'finish' : ''}>
  67. <input
  68. type="checkbox"
  69. checked={finish}
  70. onChange={() => onChange(id)}
  71. />
  72. <label>{value}</label>
  73. <button onClick={() => onRemove(id)}>×</button>
  74. </li>
  75. )
  76. })
  77. }
  78. </ul>
  79. )
  80. }

bindActionCreators

再次优化 dispatch函数,把 creatorAction直接传递给 dispatch
期望的封装的方法

  1. 接收 payload的参数
  2. 返回带 action的 dispatch函数,省略额外的 dispatch ```jsx const addTodo = (payload) => dispatch(createAdd(payload)) const removeTodo = (payload) => dispatch(createRemove(payload))

// 可能有很多个,就用到 bindActionCreators.js const actionCreators = { addTodo: addTodo, removeTodo: removeTodo, }

  1. bindActionCreators.js
  2. ```jsx
  3. /**
  4. * @param {Object} actionCreators
  5. * @param {Function} dispatch
  6. * @return {Object}
  7. */
  8. function bindActionCreators(actionCreators, dispatch) {
  9. const proxy = {}
  10. const keysArray = Object.keys(actionCreators)
  11. for(const key of keysArray) {
  12. proxy[key] = (...args) => {
  13. const actionCreator = actionCreators[key]
  14. const action = actionCreator(...args)
  15. dispatch(action)
  16. // dispatch({type: 'add', payload: []})
  17. }
  18. }
  19. return proxy
  20. }
  21. export default bindActionCreators

组件调用

  1. bindActionCreators({ addTodo: createAdd }, dispatch)
  1. import React, { useState, useCallback, useEffect } from 'react'
  2. import {
  3. createSet,
  4. createAdd,
  5. createFinish,
  6. createRemove
  7. } from './action'
  8. import bindActionCreators from './bindActionCreators'
  9. const TODO_KEY = '_TODO_'
  10. function TodoList() {
  11. const [data, setData] = useState([])
  12. // 让更新的过程,可以被描述 {type: '', payload}
  13. const dispatch = useCallback((action) => {
  14. const {type, payload} = action
  15. switch (type) {
  16. case 'set':
  17. return setData(payload)
  18. case 'add':
  19. return setData(state => [payload, ...state])
  20. case 'remove':
  21. return setData(state =>
  22. state.filter(todo => todo.id !== payload))
  23. case 'finish':
  24. return setData(state => state.map(todo =>
  25. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
  26. )
  27. default:
  28. }
  29. }, [])
  30. // componentDidMount
  31. useEffect(() => {
  32. const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
  33. dispatch(createSet(getData))
  34. // before
  35. // dispatch({type: 'set', payload: getData})
  36. }, [])
  37. return (
  38. <section>
  39. <InputItem
  40. {...bindActionCreators({ addTodo: createAdd }, dispatch)}
  41. // before dispatch={dispatch}
  42. />
  43. <ListItem
  44. {
  45. ...bindActionCreators({
  46. onRemove: createRemove,
  47. onFinish: createFinish,
  48. }, dispatch)
  49. }
  50. // before dispatch={dispatch}
  51. data={data}
  52. />
  53. </section>
  54. )
  55. }
  56. // child
  57. function TodoItem({ data, onRemove, onFinish }) {
  58. return (
  59. <ul>
  60. {
  61. data.map(item => {
  62. const { id, finish, value } = item || {}
  63. return (
  64. <li key={id} className={finish ? 'finish' : ''}>
  65. <input
  66. type="checkbox"
  67. checked={finish}
  68. onChange={() => onFinish(id)}
  69. />
  70. <label>{value}</label>
  71. <button onClick={() => onRemove(id)}>×</button>
  72. </li>
  73. )
  74. })
  75. }
  76. </ul>
  77. )
  78. }

reducer

更新数据,以数据的维度来更新;
以 action的维度只能针对一种业务
reducer拆分数据,以数据维度,来更新数据,返回更新后的数据

  1. /**
  2. * @param {*} state 初始化的所有数据
  3. * @param {*} action {type, payload}
  4. * @return 返回更新后的数据
  5. */
  6. function reducer(state = {}, action) {
  7. const { type, payload } = action
  8. const { data, value } = state
  9. switch(type) {
  10. case 'set':
  11. return {
  12. ...state,
  13. data: payload,
  14. value: value + 3
  15. }
  16. case 'add':
  17. return {
  18. ...state,
  19. data: [payload, ...data],
  20. value: value + 10
  21. }
  22. case 'remove':
  23. const newData = data.filter(todo => todo.id !== payload)
  24. return {
  25. ...state,
  26. data: newData
  27. }
  28. case 'finish':
  29. return {
  30. ...state,
  31. data: data.map(todo =>
  32. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
  33. }
  34. }
  35. return state
  36. }
  37. export default reducer

以上的 reducer方法混合个各种数据的更新,引起了副作用,
让每个字段独立的有自己的 reducer来更新数据,类似于

  1. const reducers = {
  2. data(state = {}, action) {
  3. const { type, payload } = action
  4. const { data } = state
  5. switch(type) {
  6. case 'set':
  7. return {
  8. ...state,
  9. data: payload,
  10. value: value + 3
  11. }
  12. case 'add':
  13. return {
  14. ...state,
  15. data: [payload, ...data],
  16. value: value + 10
  17. }
  18. case 'remove':
  19. const newData = data.filter(todo => todo.id !== payload)
  20. return {
  21. ...state,
  22. data: newData
  23. }
  24. case 'finish':
  25. return {
  26. ...state,
  27. data: data.map(todo =>
  28. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
  29. }
  30. }
  31. return state
  32. },
  33. value(state, action) {
  34. }
  35. }

combineReducers

  1. function combineReducers(reducers) {
  2. return (state, action) => {
  3. const proxy = {}
  4. const keysArray = Object.keys(reducers)
  5. for(const key of keysArray) {
  6. proxy[key] = reducers[key](state[key], action)
  7. }
  8. return {
  9. ...state,
  10. ...proxy
  11. }
  12. }
  13. }
  14. export default combineReducers

多个 reducers

按照数据的维度来拆分,不同的数据之间不会相互影响,
每个函数的 state代表当前的值
缺点:

  1. 不能读取其他数据的值
  2. 默认是同步的,不能异步 ```jsx /**

    • */ const reducers = { data(state = {}, action) { const { type, payload } = action

      switch (type) { case ‘set’: return payload

      case ‘add’: return [payload, …state]

      case ‘remove’: return state.filter(todo => todo.id !== payload)

      case ‘finish’: return state.map(todo =>

      1. (todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)

      } return state },

    value(state = {}, action) { const { type } = action switch (type) { case ‘set’:

    1. return state + 3

    case ‘add’:

    1. return state + 10

    } return state } }

// 接收一个总的 reducer,返回更新后的数据 function combineReducers(reducers) { return (state, action) => { const proxy = {} const keysArray = Object.keys(reducers)

  1. for(const key of keysArray) {
  2. proxy[key] = reducers[key](state[key], action)
  3. }
  4. return {
  5. ...state, // 传入的 state,返回修改后的 state
  6. ...proxy
  7. }

} }

export default combineReducers(reducers)

  1. <a name="4GgWO"></a>
  2. ## 异步Action
  3. redux本身也不能处理异步的逻辑,<br />react-redux插件可以处理 asyncAction
  4. ```jsx
  5. import React, { useState, useRef, useEffect, memo } from 'react'
  6. import {
  7. createSet,
  8. createAdd,
  9. createFinish,
  10. createRemove
  11. } from './action'
  12. import bindActionCreators from './bindActionCreators'
  13. import reducer from './combineReducers'
  14. const TODO_KEY = '_TODO_'
  15. // 初始化的所有数据
  16. let store = {
  17. data: [],
  18. value: 0
  19. }
  20. const ListItem = memo(TodoItem)
  21. function TodoList() {
  22. const [data, setData] = useState([])
  23. const [value, setValue] = useState(10)
  24. useEffect(() => {
  25. Object.assign(store, { data, value })
  26. }, [data, value])
  27. function dispatch(action) {
  28. const setters = {
  29. data: setData,
  30. value: setValue,
  31. }
  32. // 异步的逻辑, action默认返回 Object,如果是 Function,说明有异步逻辑
  33. // 返回最新 state的一个函数,在组件的上下文中,每次渲染周期返回的都不是最新的 state
  34. // 在全局创建一个 store,存储所有的 state,如何同步呢?useEffect
  35. if (typeof action === 'function') {
  36. // 通过函数参数来获取最新的 state,即全局的 store
  37. return action(dispatch, () => store)
  38. }
  39. // 核心的一个代码
  40. const newState = reducer(store, action)
  41. for(let key in newState) {
  42. if (!setters[key]) break;
  43. setters[key](newState[key])
  44. }
  45. }
  46. // componentDidMount
  47. useEffect(() => {
  48. const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
  49. dispatch(createSet(getData))
  50. }, [])
  51. // componentDidUpdate
  52. useEffect(() => {
  53. sessionStorage.setItem(TODO_KEY, JSON.stringify(data))
  54. }, [data])
  55. return (
  56. <section>
  57. <InputItem
  58. {...bindActionCreators({ addTodo: createAdd }, dispatch)}
  59. />
  60. <ListItem
  61. {
  62. ...bindActionCreators({
  63. onRemove: createRemove,
  64. onFinish: createFinish,
  65. }, dispatch)
  66. }
  67. data={data}
  68. />
  69. </section>
  70. )
  71. }
  72. export default TodoList