- 抽离 dispatch公共方法
- 让更新的过程,可以被描述 {type, payload}
- 抽离 action方法
dispatch({type: 'add', payload: data})
dispatch
dispatch接管事件,统一派发动作 action
全局拦截和 debugger
import React, { useState, useCallback, useEffect } from 'react'const TODO_KEY = '_TODO_'function TodoList() {const [data, setData] = useState([])// 让更新的过程,可以被描述 {type: '', payload}const dispatch = useCallback((action) => {const {type, payload} = actionswitch (type) {case 'set':return setData(payload)case 'add':return setData(state => [payload, ...state])case 'remove':return setData(state =>state.filter(todo => todo.id !== payload))case 'finish':return setData(state => state.map(todo =>(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo))default:}}, [])// componentDidMountuseEffect(() => {const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')dispatch({type: 'set', payload: getData})}, [])// componentDidUpdateuseEffect(() => {sessionStorage.setItem(TODO_KEY, JSON.stringify(data))}, [data])}
action
函数统一了,就需要用参数来区分:
- 优化 action,用工厂函数创建 action
- 引入 actionCreator概念,用 dispatch派发 action ```javascript export function createSet(payload) { return { type: ‘set’, payload } }
export function createAdd(payload) { return { type: ‘add’, payload } }
页面使用 action```jsximport React, { useState, useCallback, useEffect } from 'react'import {createSet,createAdd,createFinish,createRemove} from './action'const TODO_KEY = '_TODO_'function TodoList() {const [data, setData] = useState([])// 需要传递的函数,用 useCallback包裹起来const dispatch = useCallback((action) => {const {type, payload} = actionswitch (type) {case 'set':return setData(payload)case 'add':return setData(state => [payload, ...state])case 'remove':return setData(state =>state.filter(todo => todo.id !== payload))case 'finish':return setData(state => state.map(todo =>(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo))default:}}, [])// componentDidMountuseEffect(() => {const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')dispatch(createSet(getData))// before// dispatch({type: 'set', payload: getData})}, [])return (<section><InputItem dispatch={dispatch} /><ListItemdispatch={dispatch}data={data}/></section>)}// childfunction TodoItem({ data, dispatch }) {const onChange = useCallback(function (id) {dispatch(createFinish(id))// before// dispatch({ type: 'finish', payload: id })}, [])const onRemove = useCallback(function (id) {dispatch(createRemove(id))// before// dispatch({ type: 'remove', payload: id })}, [])return (<ul>{data.map(item => {const { id, finish, value } = item || {}return (<li key={id} className={finish ? 'finish' : ''}><inputtype="checkbox"checked={finish}onChange={() => onChange(id)}/><label>{value}</label><button onClick={() => onRemove(id)}>×</button></li>)})}</ul>)}
bindActionCreators
再次优化 dispatch函数,把 creatorAction直接传递给 dispatch
期望的封装的方法
- 接收 payload的参数
- 返回带 action的 dispatch函数,省略额外的 dispatch ```jsx const addTodo = (payload) => dispatch(createAdd(payload)) const removeTodo = (payload) => dispatch(createRemove(payload))
// 可能有很多个,就用到 bindActionCreators.js const actionCreators = { addTodo: addTodo, removeTodo: removeTodo, }
bindActionCreators.js```jsx/*** @param {Object} actionCreators* @param {Function} dispatch* @return {Object}*/function bindActionCreators(actionCreators, dispatch) {const proxy = {}const keysArray = Object.keys(actionCreators)for(const key of keysArray) {proxy[key] = (...args) => {const actionCreator = actionCreators[key]const action = actionCreator(...args)dispatch(action)// dispatch({type: 'add', payload: []})}}return proxy}export default bindActionCreators
组件调用
bindActionCreators({ addTodo: createAdd }, dispatch)
import React, { useState, useCallback, useEffect } from 'react'import {createSet,createAdd,createFinish,createRemove} from './action'import bindActionCreators from './bindActionCreators'const TODO_KEY = '_TODO_'function TodoList() {const [data, setData] = useState([])// 让更新的过程,可以被描述 {type: '', payload}const dispatch = useCallback((action) => {const {type, payload} = actionswitch (type) {case 'set':return setData(payload)case 'add':return setData(state => [payload, ...state])case 'remove':return setData(state =>state.filter(todo => todo.id !== payload))case 'finish':return setData(state => state.map(todo =>(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo))default:}}, [])// componentDidMountuseEffect(() => {const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')dispatch(createSet(getData))// before// dispatch({type: 'set', payload: getData})}, [])return (<section><InputItem{...bindActionCreators({ addTodo: createAdd }, dispatch)}// before dispatch={dispatch}/><ListItem{...bindActionCreators({onRemove: createRemove,onFinish: createFinish,}, dispatch)}// before dispatch={dispatch}data={data}/></section>)}// childfunction TodoItem({ data, onRemove, onFinish }) {return (<ul>{data.map(item => {const { id, finish, value } = item || {}return (<li key={id} className={finish ? 'finish' : ''}><inputtype="checkbox"checked={finish}onChange={() => onFinish(id)}/><label>{value}</label><button onClick={() => onRemove(id)}>×</button></li>)})}</ul>)}
reducer
更新数据,以数据的维度来更新;
以 action的维度只能针对一种业务
reducer拆分数据,以数据维度,来更新数据,返回更新后的数据
/*** @param {*} state 初始化的所有数据* @param {*} action {type, payload}* @return 返回更新后的数据*/function reducer(state = {}, action) {const { type, payload } = actionconst { data, value } = stateswitch(type) {case 'set':return {...state,data: payload,value: value + 3}case 'add':return {...state,data: [payload, ...data],value: value + 10}case 'remove':const newData = data.filter(todo => todo.id !== payload)return {...state,data: newData}case 'finish':return {...state,data: data.map(todo =>(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)}}return state}export default reducer
以上的 reducer方法混合个各种数据的更新,引起了副作用,
让每个字段独立的有自己的 reducer来更新数据,类似于
const reducers = {data(state = {}, action) {const { type, payload } = actionconst { data } = stateswitch(type) {case 'set':return {...state,data: payload,value: value + 3}case 'add':return {...state,data: [payload, ...data],value: value + 10}case 'remove':const newData = data.filter(todo => todo.id !== payload)return {...state,data: newData}case 'finish':return {...state,data: data.map(todo =>(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)}}return state},value(state, action) {}}
combineReducers
function combineReducers(reducers) {return (state, action) => {const proxy = {}const keysArray = Object.keys(reducers)for(const key of keysArray) {proxy[key] = reducers[key](state[key], action)}return {...state,...proxy}}}export default combineReducers
多个 reducers
按照数据的维度来拆分,不同的数据之间不会相互影响,
每个函数的 state代表当前的值
缺点:
- 不能读取其他数据的值
默认是同步的,不能异步 ```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 =>
(todo.id === payload) ? { ...todo, finish: !todo.finish } : todo)
} return state },
value(state = {}, action) { const { type } = action switch (type) { case ‘set’:
return state + 3
case ‘add’:
return state + 10
} return state } }
// 接收一个总的 reducer,返回更新后的数据 function combineReducers(reducers) { return (state, action) => { const proxy = {} const keysArray = Object.keys(reducers)
for(const key of keysArray) {proxy[key] = reducers[key](state[key], action)}return {...state, // 传入的 state,返回修改后的 state...proxy}
} }
export default combineReducers(reducers)
<a name="4GgWO"></a>## 异步Actionredux本身也不能处理异步的逻辑,<br />react-redux插件可以处理 asyncAction```jsximport React, { useState, useRef, useEffect, memo } from 'react'import {createSet,createAdd,createFinish,createRemove} from './action'import bindActionCreators from './bindActionCreators'import reducer from './combineReducers'const TODO_KEY = '_TODO_'// 初始化的所有数据let store = {data: [],value: 0}const ListItem = memo(TodoItem)function TodoList() {const [data, setData] = useState([])const [value, setValue] = useState(10)useEffect(() => {Object.assign(store, { data, value })}, [data, value])function dispatch(action) {const setters = {data: setData,value: setValue,}// 异步的逻辑, action默认返回 Object,如果是 Function,说明有异步逻辑// 返回最新 state的一个函数,在组件的上下文中,每次渲染周期返回的都不是最新的 state// 在全局创建一个 store,存储所有的 state,如何同步呢?useEffectif (typeof action === 'function') {// 通过函数参数来获取最新的 state,即全局的 storereturn action(dispatch, () => store)}// 核心的一个代码const newState = reducer(store, action)for(let key in newState) {if (!setters[key]) break;setters[key](newState[key])}}// componentDidMountuseEffect(() => {const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')dispatch(createSet(getData))}, [])// componentDidUpdateuseEffect(() => {sessionStorage.setItem(TODO_KEY, JSON.stringify(data))}, [data])return (<section><InputItem{...bindActionCreators({ addTodo: createAdd }, dispatch)}/><ListItem{...bindActionCreators({onRemove: createRemove,onFinish: createFinish,}, dispatch)}data={data}/></section>)}export default TodoList
