redux基础知识
项目中一直使用redux作为全局状态的管理工具,最近对其又进行了一次系统性的学习,对之前的一些理解不够的细节又有了新的认识,所以写文章来记录一下。
有同学说redux的学法繁琐还有学习的必要吗?我认为是必要的,它对react来说是很经典的状态管理工具,在它的基础上二次封装的库也层出不穷,我们的终极学习目标就是封装出一套自己的redux库,方便我们的开发。本文全部为redux官方文档加上自己在实际使用的经验。
三大基本原则
单一数据源
整个应用的state
存储在一个object tree
中,这个tree只存在于唯一一个store
中。这个store中可以存在许多分store对应各个功能模块, 但最终都需要合并为一个巨大的store。这就是redux提供的createStore()
方法传入的store可以是数组的原因。
state只读
唯一改变 state
的方法就是触发 action
,action 是一个用于描述已发生事件的普通对象。 这样确保了视图和请求都不能直接修改state, 相反他们只能表达想进行修改的意愿,也就是dispatch
一个action
。
使用纯函数进行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers
。reducers是一些纯函数,他们接收旧的state和action,返回新计算后的state。 他和store一样可以在应用变大时进行拆分,每个分reducers对应不同的功能模块。
三个基本概念
Action
定义
Action一般长这样:
{
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
没错它本质上就是一个普通的对象。其中规定必须存在一个type字段用来和reducers和一些中间件匹配,而另一个字段则由你随意发挥,就像上面 我们定义的是一个string类型的text。
那么Action是干什么的呢?打个比方来说它就起到一个运送的功能,有点像一条传送带,将特定的操作和操作的数据送到指定的地方。它是把数据从 应用传递到store的有效载荷,是store数据的唯一来源。
Action创建函数
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。 它的作用也很简单,就是生成action。
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
其中的text随便你怎么写都可以。
dispatch
现在有了action,我们怎么通知store呢?一般来说我们通过store.dispatch()
将action传递到store。对于一个Action创建函数来说, 我们使用dispatch(addTodo(text))
即可发起一次dispatch过程。或者我们可以创建一个函数来直接进行dispatch调用。
cnost boundAddTodo = text => dispatch(addTodo(text))
然后直接在应用中调用它boundAddTodo(text)
即可。
reducers
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的, 记住 actions 只是描述了有事情发生了这一事实, 并没有描述应用如何更新 state。打个比方来说这里就有点像加工机器,来处理‘传送带’action传递来的‘原料’。
在详细介绍之前,我们需要先确定好我们需要生产出什么东西也就是store。以一个Todo应用为例,我们需要一个对列表的过滤条件,一个任务列表, 由此我们大概清楚 store 的结构:
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'study react',
completed: true,
},
{
text: 'study redux',
completed: false
}
]
}
有了store后我们就可以规划好我们的传送带也就是action了,我们可以定义如下:
/*
* action 类型
*/
export const ADD_TODO = 'ADD_TODO'; // 添加一个任务
export const TOGGLE_TODO = 'TOGGLE_TODO' // 切换显示
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER' // 设置过滤词
/*
* 其它的常量
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL', // 显示全部
SHOW_COMPLETED: 'SHOW_COMPLETED', //显示已完成
SHOW_ACTIVE: 'SHOW_ACTIVE' //显示未完成
}
/*
* action 创建函数
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
好的,前期准备都做好了,现在可以开发reducer了。reducer就是一个纯函数,接收聚德state和action,返回新的state。
(preState,action)=>newState
。
之所以称之为reducer,是因为这种函数和传入Array.prototype.reduce(reducer,?initialValue)
里的回调函数属于同一类型。 保持它为纯函数很重要,永远不要在reducer中做以下的操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
我们将以指定state的初识状态作为开始。redux首次执行时,state为undefined
,此时我们可以设置并返回应用的初始state。
function todoApp(state = initialState, action) {
// 这里暂不处理任何 action,
// 仅返回传入的 state。
return state
}
然后处理SET_VISIBILITY_FILTER
。需要做的只是改变 state 中的 visibilityFilter
。
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
name: 'xxx'
}
]
})
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
default:
return state
}
}
这里有两个注意点:
- 不要修改state。使用
Object.assign()
新建了一个副本。 - 在default情况下返回旧的state。遇到位置action,一定要返回旧的state。
Store
在前面,我们学会了使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。
接下来我们就可以创建将他们联系到一起的对象。这就是createStore()
的作用,传入reducers从而创建出一个store。