共计 4346 个字符,预计需要花费 11 分钟才能阅读完成。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
那什么是可以预测化,我的理解就是根据一个固定的输入,必然会得到一个固定的结果。
redux 是专门为 react 开发的,但并不是只能用于 react, 可以用于任何界面库。
动机
随着单页面应用的普及,web app 内部需要管理的状态越来越多,这些状态可能来自服务器端,用户输入的数据,用户交互数据,当前 UI 状态,本地的缓存数据等等。如何能够有条理的管理这些数据,成为前端开发中一个难题。
核心概念
三大原则
单一数据源
使用 redux 的程序,所有的 state 都存储在一个单一的数据源 store 内部,类似一个巨大的对象树。
state 是只读的
state 是只读的,能改变 state 的唯一方式是通过触发 action 来修改
使用纯函数执行修改
为了描述 action 如何改变 state tree,你需要编写 reducers。
reducers 是一些纯函数,接口当前 state 和 action。只需要根据 action,返回对应的 state。而且必须要有返回。
一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数
基础
action
顾名思义,action 就是动作,也就是通过动作来修改 state 的值。也是修改 store 的唯一途径。
action 本质上就是一个普通 js 对象,我们约定这个对象必须有一个字段 type,来表示我们的动作名称。一般我们会使用一个常量来表示 type 对应的值。
此外,我们还会把希望 state 变成什么样子的对应的值通过 action 传进来,那么这里 action 可能会类似这样子的
{
type: ‘TOGGLE_TODO’,
index: 5
}
Reducer
Action 只是描述了有事情发生了这件事实,但并没有说明要做哪些改变,这正是 reducer 需要做的事情。
Reducer 作为纯函数,内部不建议使用任何有副作用的操作,比如操作外部的变量,任何导致相同输入但输出却不一致的操作。
如果我们的 reducer 比较多,比较复杂,我们不能把所有的逻辑都放到一个 reducer 里面去处理,这个时候我们就需要拆分 reducer。
幸好,redux 提供了一个 api 就是 combineReducers Api。
store
store 是 redux 应用的唯一数据源,我们调用 createStore Api 创建 store。
脱离 react 的 redux 案例
store,reducer 基础使用
第一步搭建开发环境,这里不介绍了,参考上一篇文章 手把手教会使用 react 开发日历组件,搭建环境部分
搭建好环境切换到目录下面
npm install redux –save
把 index.tsx 修改为之下代码。
import {createStore, combineReducers, applyMiddleware} from ‘redux’
var simpleReducer = function(state = {}, action) {
return {
user: {
name: ‘redux’
}
}
}
var store = createStore(simpleReducer)
console.log(store.getState())
我们看到控制台打印出来的一个包含 user 信息的这么一个对象。
我们使用到了几个 api? createStore 创建 store,store.getState() 获取 store,也就是唯一数据源的根节点。
上文我们也讲过,action 的情况可能会比较多,redux 也提供了 combineReducers Api。如果我们有多个 reducer, 我们就可以使用起来了。
那我们创建多个 reducer 测试一下,代码如下:
import {createStore, combineReducers, applyMiddleware} from ‘redux’
function user(state = {name: ‘redux’}, action) {
switch (action.type) {
case ‘CHANGE_NAME’:
return {
…state,
name: action.name
}
}
return state
}
function project(state = {name: ‘min-react’}, action) {
switch (action.type) {
case ‘CHANGE_NAME’:
return {
…state,
name: action.name
}
}
return state
}
var rootReducer = combineReducers({
user,
project
})
var store = createStore(rootReducer)
console.log(store.getState())
如我们所预料一样,我们得到拥有两个字段的根 store。
结合 view 使用
第一步我们把 html 改造成这个样子,新增了一点标签
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>Document</title>
<style type=”text/css”>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id=”userName”></div>
<input id=”userNameInput”/><button id=”userNameButton”> 更改 userName</button>
<script src=”./dist/main.js”></script>
</body>
</html>
第二步,修改 index.tsx,如下
import {createStore, combineReducers, applyMiddleware} from ‘redux’
import {func} from ‘prop-types’
function user(state = {name: ‘redux’}, action) {
switch (action.type) {
case ‘CHANGE_USER_NAME’:
return {
…state,
name: action.name
}
}
return state
}
function project(state = {name: ‘min-react’}, action) {
switch (action.type) {
case ‘CHANGE_PROJECT_NAME’:
return {
…state,
name: action.name
}
}
return state
}
var rootReducer = combineReducers({
user,
project
})
var store = createStore(rootReducer)
function render(state = store.getState()) {
var $userName = document.getElementById(‘userName’)
$userName.innerHTML = state.user.name
}
render()
console.log(store.getState())
我们看到页面正确的显示了我们 user 的名称。下一步我们需要做的就是通过用户的操作,改变 store 的值,进而触发 view 的更新。
于是我们新增了这块代码:
store.subscribe(function() {
render()
})
// 绑定用户事件
var $userNameInput = document.getElementById(‘userNameInput’)
var userNameButton = document.getElementById(‘userNameButton’)
userNameButton.onclick = function() {
var value = $userNameInput.value
store.dispatch({
type: ‘CHANGE_USER_NAME’,
name: value
})
}
我们看到保存之后,当我们输入值之后,点击更改,页面的值随着改变。
但是控制台报了一个错误,TS2339: Property ‘value’ does not exist on type ‘HTMLElement’., 这是由于 typescript 强类型校验没通过导致的。只要加这段代码就好了
var $userNameInput = document.getElementById(‘userNameInput’) as HTMLInputElement
看到了吧,redux 就是这么简单。
其他所有上层应用,都是在此基础上开发的,所以开发一个 redux 应用的步骤就是
定义 action 和与之对应的 reducer
监听 store 的变化,提供回调函数
dispatch 一个 action,等待好运发生。
结合 react,其他 view 类库,开发步骤莫不如此。
高级应用
异步 action
我们也看到了,我们的 reducer 只能做同步应用,如果我们需要在 reducer,做一些延迟操作,可怎么办
社区已经有成熟的类库做这件事件
npm install redux-thunk –save
redux 本身已经提高了很好的扩展机制,就是中间件。这点很类似 express 的中间件。
// 引入新的类库
import {createStore, combineReducers, applyMiddleware, compose} from ‘redux’
import thunk from ‘redux-thunk’
…
//store 部分做如下修改
const finalCreateStore = compose(applyMiddleware(thunk))(createStore)
const store = finalCreateStore(rootReducer, {})
redux-thunk 的作用就是让 dispatch 方法不仅仅只接收 action 对象,还可以包含一个方法。我们可以在这个方法内部去调用异步代码
我们把 dom 事件部分做了如下改造
userNameButton.onclick = function() {
var value = $userNameInput.value
store.dispatch<any>(function(dispatch, getState) {
setTimeout(() => {
dispatch({
type: ‘CHANGE_USER_NAME’,
name: value
})
}, 2000)
})
}
可以看到页面元素确实在 2s 之后发生了变化,实际业务中啊,我们这里可以做一些异步操作。
至于 redux 原理,以及源码和中间件的源码讲解可以参照我的另外一篇文章 阅读 redux 源码