一、为什么要使用Redux?
这是老生常谈的问题了,几乎每一个写Redux的文章里都会提到这个问题。在阮一峰老师的文章中提到过,“如果你不知道是否需要 Redux,那就是不需要它。”如何理解这句话呢?我们从概念的角度和实际项目中可能出现问题的角度来解答。
使用场景
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的。只有符合了下面的几种情况,才是Redux的使用场景:
1.用户的使用方式复杂
2.不同身份的用户有不同的使用方式(比如普通用户和管
理员)
3.多个用户之间可以写作
4.与服务器大量交互,或者使用了WebSocket
联系实际
1.假设产品经理提出了这样一个需求:
写一个简单的Logger来实现用户的动作跟踪记录。
我们可能会这么写:
在MVVM框架中(以纯Vue举例):
methods: {
handleLogin () {
console.log('[Logger] 用户登录')
...
},
handleLogout () {
console.log('[Logger] 用户退出登录')
...
}
}
2.接着产品经理说:“在上述需求的基础上,记录用户的操作时间”。
此时,我们会在每个输出中添加一个new Date():
methods: {
handleLogin () {
console.log('[Logger] 用户登录', new Date())
...
},
handleLogout () {
console.log('[Logger] 用户退出登录', new Date())
...
}
}
3.需求3:把控制台中有关Logger的输出全部去掉
于是前端们又要一个个地把Logger注释掉。。。
4.需求4:正式上线后,自动收集bug,并还原当时的场景
到了要完全还原当时的使用场景时,前端开始手无足措了。因为您不知道这个报错,用户是怎么一步一步操作得来的
就算知道用户是如何操作得来的,但在您的电脑上,测试永远都是通过的
※ 如何解决撤销的需求
使用Redux,将应用中 所有的动作与状态都统一管理,让一切都有据可循。
这样一来,便可以在开发调试的过程中 撤销与重做 了。并且可以完整的记录下每个动作。随时随地能够恢复到之前的状态。
二、Store
2.1 Store是什么?
state是应用的状态。
store是state的管理者。
二者的关系是:state = store.getState()
{
//这是一个应用初始state
counter: 0,
todos: []
}
2.2 生成一个store
Redux规定,一个应用只应有一个单一的 store,其管理着唯一的应用状态 state。
想生成一个store,需要调用Redux的createStore.
import {createStore} from 'redux'
...
const store = createStore(reducer,initialState)//store是靠reducer生成的
在上面代码中,createStore接收两个参数,返回新生成的 Store 对象:
reducer(reducer是一个函数,负责更新并返回一个新的state)和
initialState(用于前后端同构的数据同步)
2.3 通过store更改状态state
Redux对Store的规定
- 不能直接修改应用的状态 state
不能直接修改应用的状态state,也就是说,下面的行为是不允许的:
那么应当如何修改state呢?//禁止使用下面的方法 var state = store.getState() state.counter = state.counter + 1
必须使用 dispatch(action)函数 来改变state2.4 Store的四个函数
getState()//获取整个 state
dispatch(action)//触发 state 改变的【唯一途径】
subscribe(listener) //可以理解成是 DOM 中的 addEventListener
replaceReducer(nextReducer) // 一般在 Webpack Code-Splitting 按需加载的时候用
那么此时,我们已经知道了 改变state方式,我们自然想知道,要将什么 内容传给state。
带着这个疑问,引入了Action这个概念。
三、Action
3.1 Action是什么?
Action的本质是一个包含 type 属性的 对象。
type: 描述用户行为,是我们追踪用户行为的关键。
举个例子,如果想要 增加一个待办事项:
{
//代码1
type:'ADD_TODO',
payload:{
id: 1,
content: '待办事项1',
completed: false
}
}
3.2 生成Action
//生成一个“新增一个待办事项”的action
let id=1
function addTodo(content){
return{
type:'ADD_TODO',
payload:{
id: 1,
content: '待办事项1',
completed: false
}
}
}
上面的函数就是 Action Creator。它的本质是创造action的一个函数,返回值是一个action(对象)。
3.3 将对象action的内容传给state
结合3.2的代码,此时有一个表单如下:
<input type="text" id="todoInput" />
<button id="btn">提交</button>
当在输入框输入“代办事项2”后,点击一下提交按钮,我们的state状态需要改变成:
{
counter: 0,
todos: [{
id: 1,
content: '待办事项1',
completed: false
}, {
id: 2,
content: '待办事项2',
completed: false
}]
}
该如何实现将表单数据传入到state中呢?
(假设 store 为全局变量,并引入了 jQuery )
于是可以这样写:
$('#btn').on('click',function(){
let content = $('#todoInput').val()
let action = addTodo(content) //执行Action Creator获得action
store.dispatch(action) //改变state的唯一方法。
}
state状态就能有“新增事项2”了。
但是这里有个问题:
为什么在 store.dispatch(action) 之后, Redux会明确知道是提取 action.payload,并写到对应的state.todos数组中,而不是直接将整个action对象传进去呢?
带着这个疑问,我们引入了 Reducer概念
四、Reducer
4.1 Reducer是什么?
在上面例子中,store.dispatch(action)后,就会触发 reducer执行,reducer的实质是一个同步的 纯函数。
它根据 action.type 来更新state并返回 nextState,最后将返回值 完全覆盖原来的 state,更新state。
4.2 Reducer分析action,更新state
在上面待办事项的例子中,Reducer大致如下:
const initState={
counter:0,
todos:[]
}
function reducer(state,action){
state = state || initState;
switch(action.type){
case 'ADD_TODO':
var nextState = _.cloneDeep(state) //lodash的深克隆
//将action.payload放进nextState.todos里
nextState.todos.push(action.payload)
}
default:
//若无修改,必须返回原state,否则就undefined
return state
}
五、Redux总结
思想顺序:
Action Creator => action => store.dispatch(action) => reducer(state, action) => 原 state state = nextState
一个Redux文档的Todo示例:https://www.redux.org.cn/docs/basics/ExampleTodoList.html
Redux框架应当解决的问题?
Shawn McKay写的《重新思考 Redux》中提到了这样一个公式:
工具质量 = 工具节省的时间/使用工具消耗的时间
而有时候我们会发现,使用原生Redux可能会降低工作效率。
虽然Redux的数据管理思想是正确的,但是 为了更有效率的使用redux,我们需要使用基于redux的框架。
※ 从6个角度看Redux框架需要解决什么问题
简化初始化
简化 Reducers
支持 async/await
将 action + reducer 改为两种 action
不再显示申明 action type
Reducer 直接作为 ActionCreator
精读《重新思考 Redux》:https://juejin.im/post/5af8dff9f265da0b9f40616b#heading-2