React只是一个视图层的框架,负责把数据映射成DOM元素。但应用程序每每涉及到大量的数据交互和网络请求,修改数据的频率会很高,因此须要一种规范来约束对数据的更新,使得任何修改均可以被追踪,这样才不惧应用程序的复杂性,实现良好的调试性能和可扩展能力。Redux是一个数据流和数据容器管理工具。这篇文章中有一组生动的对比图看有无Redux时数据的处理方式。javascript
下面咱们看一个简单的 todo-list 的例子。 html
//TodoApp.js
const [todos, setTodos] = useState([])
const addTodo = () => {}
const removeTodo = () => {}
const toggleTodo = () => {}
<AddTodo addTodo={addTodo} />
<TodoList removeTodo={removeTodo} toggleTodo={toggleTodo} todos={todos} /> 复制代码
//TodoList.js
{
todos.map(todo => (
<TodoItem removeTodo={removeTodo} toggleTodo={toggleTodo} /> )) } 复制代码
{
type: 'add',
payload: todo
}
复制代码
咱们称它为Action,每次对todos进行操做时都发出这样一个Action,就能够很清楚的看到在对todos进行了什么操做,此次操做携带的数据是什么。直接把这样一个Action对象丢给todos,todos是不知道该怎么办的,因此todos须要一个管家(dispatch)帮它处理而后把处理结果告诉它。下面让咱们用代码来实现一下dispatch。java
const dispatch = (action) => {
const { type, payload } = action
swicth(type) {
case 'set':
//set的逻辑
break;
case 'add':
//add的逻辑
break;
case 'remove':
//remove的逻辑
break;
case 'toggle':
//toggle的逻辑
break;
}
}
复制代码
有了dispatch这个管家,如今处理addTodo的业务逻辑就很简单了,只须要segmentfault
dispatch({
type: 'add',
payload: todo
})
复制代码
由于每一次操做都是一个Action,而每个Action都只有两个参数(type, payload),当操做频繁时每次都写上面的代码会很麻烦,因此咱们考虑构建一个创造Action的函数actionCreator,这样咱们就不用每次都手动生成Action了。由于有不少个Action,对应就会有不少个actionCreator,因此咱们考虑把全部的actionCreator放在一个单独的文件actionCreators.js里bash
//actionCreators.js
export const add = payload => ({
type: 'add',
payload
})
export const remove = payload => ({
type: 'remove',
payload
})
export const toggle = payload => ({
type: 'toggle',
payload
})
复制代码
而后把actionCreators.js引入到TodoList.js中,如今咱们处理addTodo就只须要网络
//TodoList.js
import * as actionCreators from './actionCreators';
dispatch(actionCreators.add(payload))
复制代码
仔细看看每次操做都须要dispatch来派发Action,咱们能够考虑再封装一次,把dispatch也隐藏起来。框架
const addTodo = payload => dispatch(actionCreators.add(payload))
复制代码
这样咱们每次处理addTodo就只须要调用addTodo函数便可。这样的封装操做有不少个,咱们能够批量实现一下,咱们但愿获得下面这样的结果异步
{
addTodo: payload => dispatch(actionCreators.add(payload)),
removeTodo: payload => dispatch(actionCreators.remove(payload)),
toggleTodo: payload => dispatch(actionCreators.toggle(payload))
}
复制代码
因而咱们编写一个bindActionCreators函数来批量封装获得咱们想要的结果函数
function bindActionCreators(actionCreators, dispatch) {
const ret = {}
for(let key in actionCreators) {
ret[key] = function(...args) {
const actionCreator = actionCreators[key]
const action = actionCreator(...args)
dispatch(action)
}
}
return ret
}
复制代码
如今咱们能够这样实现一个addTodo的操做工具
const {
add: addTodo,
remove: removeTodo,
toggle: toggleTodo
} = bindActionCreators(actionCreators, dispatch)
addTodo(payload)
复制代码
由于TodoList的逻辑很简单,因此咱们这样改造完没有看到很明显的优点,因此让咱们改造一下项目,让项目变得稍微复杂一点,咱们新添加一个incrementCount变量,每次新添加一个todo,incrementCount就会加一。
const [todos, setTodos] = useState([])
const [incrementCount, setIncrementCount] = useState(0)
const dispatch = (action) => {
const { type, payload } = action
swicth(type) {
case 'set':
//set的逻辑
setIncrementCount(c => c + 1)
break;
case 'add':
//add的逻辑
setIncrementCount(c => c + 1)
break;
case 'remove':
//remove的逻辑
break;
case 'toggle':
//toggle的逻辑
break;
}
}
复制代码
如今咱们看代码能够发现多个Action有一样的逻辑,须要重复编码实现,这是由于咱们是从Action的维度来执行的数据更新逻辑,可是这些Action操做都是为了更新数据,为了更加清晰,咱们能够考虑从数据的维度来整理数据的更新逻辑,咱们但愿有这样的一个reducer,它接收state和action而后返回更新后的state数据,每个数据有本身单独的reducer,而后返回合并后的多个reducer,如今让咱们用代码来实现一下。
//reducers.js
const reducers = {
todos(state, action) {
const { type, payload } = action
switch(type) {
case 'set':
return //set的逻辑
case 'add':
return //add的逻辑
case 'remove':
return //remove的逻辑
case 'toggle':
return //stoggle的逻辑
}
return state
},
incrementCount(state, action) {
const { type } = action
switch(type) {
case 'set':
return state + 1
case 'add':
return state + 1
}
return state
}
}
function combineReducers(reducers) {
return function reducer(state, action) {
const changed = {}
for(let key in reducers) {
changed[key] = reducers[key](state[key], action)
}
return {
...state,
...changed
}
}
}
export default combineReducers(reducers)
复制代码
如今在TodoList.js中引入reducers.js,而后改写dispatch函数
const dispatch = (action) => {
const state = {
todo,
incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
复制代码
reducer的意义在于可以从数据字段的维度来处理action。
上面说了这么多咱们都是在处理同步的Action,如今让咱们思考一下如何处理异步的Action。最直接的想法就是咱们先处理异步的逻辑,异步结束后再派发一次Action,下面让咱们用代码来实现一下
//异步的Action
export const add = text => (dispatch, state) => {
setTimeout(() => {
const { todos } = state
if(!todos.find(todo => todo.text === text)) {
dispatch({
type: 'add',
payload: {
id: Date.now(),
text,
complete: false
}
})
}
}, 3000)
}
复制代码
如今咱们的dispatch只能处理对象,不能处理异步Action的函数,因此让咱们改写一下dispatch让它能够支持对函数的处理
const dispatch = (action) => {
const state = {
todo,
incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
if('function' === typeof action) {
action(dispatch, state)
return
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
复制代码
这样咱们就实现了一个异步的Action,咱们但愿增长todo时先判断原有的todo列表中是否包含新添加todo的内容,若是是再也不添加,若是不是再添加。这时咱们进行调试若是在3s以前删掉了重复的Action,咱们会发现3s后这个重复的Action仍是被添加到了todo的列表中。这是由于add函数拿到的数据是3s前的数据,为了不这种状况的出现,咱们会考虑用函数动态的获取state里面的数据,例如
addTodo(dispatch, () => state)
复制代码
可是state这个对象老是在异步action发起以前临时构成的,若是在3s内作了一些操做,那么数据其实已经发生改变,异步Action内获取到的仍是旧的数据。在每次渲染周期state都会改变,因此咱们能够在组件以外建立一个store来存储全部的state
let store = {
todo: [],
incrementCount: 0
}
//TodoList组件内同步数据
useEffect(() => {
Object.assign(store, {
todos,
incrementCount
})
}, [todos, incrementCount])
复制代码
如今让咱们改写dispatch
const dispatch = (action) => {
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
if('function' === typeof action) {
action(dispatch, () => store)
return
}
const newState = reducer(store, action)
for(let key in newState) {
setters[key](newState[key])
}
}
复制代码
改写异步Action
//异步的Action
export const add = text => (dispatch, getState) => {
setTimeout(() => {
const { todos } = getState()
if(!todos.find(todo => todo.text === text)) {
dispatch({
type: 'add',
payload: {
id: Date.now(),
text,
complete: false
}
})
}
}, 3000)
}
复制代码
咱们能够用actionCreators来生成一次操做的Action,用dispatch来派发这个Action,用reducer来更新数据,用bindActionCreators封装多个Action的派发操做,用combineReducers将多个reducer合并成一个。
实际上Redux也只有最基本的功能,它自己不具有对异步Action的处理,可是在Reudx的整个流程中,在Action被dispatch派发到达reducer以前能够通过多个中间件的处理,这些中间件能够加强dispatch的功能,好比Redux-thunk中间件就可让dispatch具有处理异步Action的能力。若是想要对 Redux Store 进行更深层次的加强定制,就须要使用 Store Enhancer,利用 Store Enhancer 能够加强 Redux Store 的 各个 方面。
Action -> dispatch -> 各类中间件 -> reducer -> store
复制代码
我写文章比较少,因此逻辑可能不是很清晰,若是有问题欢迎你们在评论区中提出,咱们一块儿学习讨论。本文是学习React劲爆新特性Hooks 重构去哪儿网火车票PWA这门课后,将老师讲的内容加上一点点本身的理解写成的。顺便安利一下这门课,老师讲的超级棒!!!再推荐一本书,程墨的《深刻浅出React和Redux》,里面对Redux的原理也讲解的十分清晰。