- 抽离 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} = action
switch (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:
}
}, [])
// componentDidMount
useEffect(() => {
const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
dispatch({type: 'set', payload: getData})
}, [])
// componentDidUpdate
useEffect(() => {
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
```jsx
import 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} = action
switch (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:
}
}, [])
// componentDidMount
useEffect(() => {
const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
dispatch(createSet(getData))
// before
// dispatch({type: 'set', payload: getData})
}, [])
return (
<section>
<InputItem dispatch={dispatch} />
<ListItem
dispatch={dispatch}
data={data}
/>
</section>
)
}
// child
function 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' : ''}>
<input
type="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} = action
switch (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:
}
}, [])
// componentDidMount
useEffect(() => {
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>
)
}
// child
function TodoItem({ data, onRemove, onFinish }) {
return (
<ul>
{
data.map(item => {
const { id, finish, value } = item || {}
return (
<li key={id} className={finish ? 'finish' : ''}>
<input
type="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 } = action
const { data, value } = state
switch(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 } = action
const { data } = state
switch(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>
## 异步Action
redux本身也不能处理异步的逻辑,<br />react-redux插件可以处理 asyncAction
```jsx
import 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,如何同步呢?useEffect
if (typeof action === 'function') {
// 通过函数参数来获取最新的 state,即全局的 store
return action(dispatch, () => store)
}
// 核心的一个代码
const newState = reducer(store, action)
for(let key in newState) {
if (!setters[key]) break;
setters[key](newState[key])
}
}
// componentDidMount
useEffect(() => {
const getData = JSON.parse(sessionStorage.getItem(TODO_KEY) || '[]')
dispatch(createSet(getData))
}, [])
// componentDidUpdate
useEffect(() => {
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