Redux 简介
React技术栈的主要适用情景是Web应用,Web应用涉及非常多的用户交互和状态数据改变。我们应该尽量控制应用中有状态组件的个数,应用数据应该尽量集中统一管理,否则后期维护成本较高。之前介绍有状态组件与无状态组件,React也推荐我们控制有状态组件的个数,在我们开发过程中编写的90%都应该是无状态组件。React本身提供给我们控制引用数据的方式只有props、state 及带有实验性质的context。React本身并没有提供应用状态管理的解决方案,在小的应用中可能感受不到状态管理是一个问题,但随着应用复杂度的增加,这个问题就会暴露得越来越明显,这个时候就可以考虑使用 Redux。
既然 React 推荐我们尽量少的编写有状态组件,为何不干脆把一个应用所有的状态数据集中到一个地方管理呢?这便是Redux的理念。Redux 把应用的数据统一储存在一个对象当中,把应用的所有的数据交互及引用状态的改变统一用固定形式的 action 动作对象来描述,并由 reducer 的方法来判断不同的动作如何改变引用状态,最后通过 store 对象来执行 action 动作获取 state 应用状态,并触发改变应用状态的事件。
实际上Redux提供的是一种应用状态的解决思路,我们可以用基本的原生JS方法来实现 Redux 的全部功能。并且我们就算引入 Redux 编写的大部分的是原生的JS函数和对象。
举个例子 ,原生JS模拟实现 Redux。
- 首先定义一个 counter 的函数,接受 state 和 action 参数。state 就是状态数据,而 action 是一个带有 type 的 js 对象。
- 通过对 type 的判断,返回新的 state,这样的函数在 redux 中被称为 redcer。
- 通过 counter 返回的默认值作为容器的初始state 值,为组件添加 increment、decrement 方法,事实上都是调用 dispatch 方法。
- 在 dispatch 方法中调用 counter 函数,将先用的应用状态和 action 方法传递。
import React, { Component } from 'react';
// Reducer
const Reducer = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}
class Counter extends Component {
// State
constructor(props) {
super(props)
this.state = Reducer(undefined,{})
}
dispatch(action) {
this.setState(prevState => Reducer(prevState, action));
}
// Action
increment = () => {
this.dispatch({ type: 'INCREMENT' });
};
decrement = () => {
this.dispatch({ type: 'DECREMENT' });
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
Action
Action 是带有type属性的js对象。{ type: 'INCREMENT' }
在Redux的应用操作中,我们要把所有改变应用状态的操作,规范为一个个 Action。在 Action 当中,我们也可以赋上要用来修改应用状态state的具体数据{ type: 'INCREMENT', num: 1 }
。上述我们可以看到两点 Action 的作用:
- 定义我们的应用可以进行操作的动作或者操作的类型
- 传递改变引用状态的数据
Action的定义要遵从一定的标准:Flux 标准 Action
{
// Action 类型
type: 'INCREMENT',
// payload 中返回我们要传递的数据,用来修改 state
paload: {
num: 1
},
// payload 数据未获取成功时返回 true
error:false,
// 一些不必要在 paload 中传递的其他数据
meta: {
success: true
}
}
为了防止我们代码中出现很多写死的数值,减少hard code,可以使用 Redux 当中 actionCreator 的方法。
const counterActionGenerator = (type, num) => (num) => {
let action = { type , num }
return action
}
const addNumber = counterActionGenerator('INCREMENT', null )
const minsNumber = counterActionGenerator('DECREMENT', null )
为了清晰的表达我们所有action动作,可以在应用的开头,或者一个独立的文件中定义所有的action type。
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
- 定义action类型的文件类似于一个说明文档,当你想要为应用添加新特性时,可以查阅应用已有的action类型,避免冲突。
- 在版本管理上也可以清晰看到action的记录的增删改查。
- 如果发现错误,在import就会发现代码报错,更快的进行调试。
Reducer
在之前函数中定义了 counter 函数,它的内部时一个switch结构,接收两个参数,state 和 action ,并返回一个新的state 。
// Reducer
const counter = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}
我们将上述的这个函数抽象为这个公式,更具业务现有的状态,更具触发的action ,返回新的状态的函数称之为 Reducer。
(previousState, action ) => newState
之所以将这样的函数称之为reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue)里的回调函数属于相同的类型。Reducer可以勉强翻译为缩减器或折叠器。
reduce 属于一个高阶函数,它将其中的回调函数reducer递归应用到数组中的所有元素上,并返回一个独立的值,这就是缩减折叠的意义所在。
var sum = [0, 1, 2, 3].reduce(function(acc, val) {
return acc + val
}, 0)
/* 注意这里当中的回调函数(prev,curr) => prev + curr
* 与我们redux当中的reducer模型 (previousState, action ) => newState 看起来是不是很相似?
*/
[0, 1, 2, 3, 4].reduce( (prev, curr) => prev + curr )
const todos = (state = [], action ) => {
switch (action.type) {
case 'ADD_TODO':
return [
//ES7对象扩展运算符写法
...state,
{
id:action:id,
text:action.text,
completed:false
}
]
// 不知道action类型的话返回默认state
default:
return state
}
}
reducer 的主体,是一个 switch 结构的运算,他只是更具传入的 state 和 action,来判断返回一个新的 state。它必须是一个纯函数,不可以修改影响输入值(需要深拷贝),并且没有副作用(异步调用、影响作用域...)。永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
React 应用当中数据发生改变,界面也要跟随相应改变,重新渲染,这这一步之间需要一个比较差异的diff操作,因此改变前后的数据都要被使用到。另外,实现一些诸如旅行的功能也需要依赖不可变数据。
Store
Store 是我们存储状态数据 state 的地方,我们通过 redux 中 createStore 的方法,传入 reducer 函数来创建 store。
- getState 获取当前状态数据
- dispath 执行action
- subscribe 订阅当前状态数据发生改变时触发的事件
原生模拟 createStore
const createStore = (reducer) => {
let state;
let listeners = [];
// 返回当前的state
const getState = () => state;
// 根据action调用reducer返回新的state并触发listener
const dispatch = (action) => {
state = reducer(state,action);
listeners.forEach(listeners => listener())
}
/* 这里的subscribe有两个功能
* 调用 subscribe(listener)会使用listeners.push(listener)注册一个listener
* 调用 subscribe 的返回函数则会注销掉listener
*/
const subscribe = (listener) => {
listeners.push(listerer);
return () => {
listeners = listerers.filter( 1 => 1 !== listener);
}
}
// 返回三个主要方法
return { getState, dispatch, subscribe }
}
Middleware
Middleware可以翻译为中间件,例如网站中某些访问限制的验证功能模块就可以当作一个middleware。Redux也可以应用middleware的概念实现一些例如日志记录调试输出等功能。
例如我们想知道数据前后发生的变化,最直接的办法 。
console.log('prev state',store.getState())
store.dispatch(action)
console.log('next state',store.getState())
手动修改dispath的方法
const store = createStore(counter)
// 将原本的dispatch方法保留并附上控制台语句
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('prev state',store.getState())
let result = next(action)
console.log('next state',store.getState())
return result
}
直接修改dispath原生方法是比较脏的处理,并且有时候我们会使用多个middleware,所以我们需要把它与dispatch的逻辑拆分开来。在实际中可以再redux中引入applyMiddleware 方法
const { createStore , applyMiddleware } = Redux;
const loggerMiddleware = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
const store = createStore(counter,
applyMiddleware(loggerMiddleware))
注意:
- 不要为了“用框架”而用框架
- 真正遇到需要这些工具来解决问题时再去实际应用
- Redux官网
- 你可能不需要用到Redux