可有可无的 Redux
举例:需求是吃饭,React 是做饭的工具或者吃饱的途径,也就是解法。那么 Redux 是餐饮管理办法。1个人吃饭的话,可以随心随便吃,也可以事无巨细规划的很详细。2个人天天一起吃饭就会有一些潜移默化的规则,如果太随心的话经常会有一些矛盾。如果10个人20个人,就一定需要一个行之有效的餐饮管理办法。
Redux 就是这样的一个管理办法,他不是一定需要的。即便是一个大型的 Web 大型项目,没有 Redux 的加持,也有方法非常有效的迭代开发。所以说,大多数情况,你可以不用它,只用 React 就够了。
再说了,Redux 也只是 Web 架构的一种解决方案。
为什么我们不需要 Redux
有人说过:
"如果你不知道是否需要 Redux,那就是不需要它。"
任何场景都可以完全不用Redux,特殊的说有一些场景是完全没有必要的,比如:
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
为什么我们需要 Redux
Redux 的创造者 Dan Abramov 补充了一句:
"只有遇到 React 实在解决不了的问题,你才需要 Redux 。"
前面说了那么多,但是Redux独特能优雅的解决一些问题:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
是什么
Redux 是一个应用数据流框架
Redux 对于 JavaScript 应用而言是一个可预测状态的容器。应用数据流框架,而不是传统的像 underscore.js 或者 AngularJs 那样的库或者框架。
Redux 最主要是用作应用状态的管理。简言之,Redux 用一个单独的常量状态树(对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用 actions 和 reducers)。
如果要用一句话来概括Redux,那么可以使用官网的这句话:Redux是针对JavaScript应用的可预测状态容器
。此句话虽然简单,但包含了以下几个含义:
可预测性(predictable)
: 因为Redux用了reducer与纯函数(pure function)的概念,每个新的state都会由旧的state建来一个全新的state。因而所有的状态修改都是”可预测的”。
状态容器(state container)
: state是集中在单一个对象树状结构下的单一store,store即是应用程序领域(app domain)的状态集合。
JavaScript应用
: 这说明Redux并不是单指设计给React用的,它是独立的一个函数库,可通用于各种JavaScript应用。
Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。
核心思想
- Web 应用是一个状态机,视图与状态是一一对应的。
- 所有的状态,保存在一个对象里面。
核心概念 和 API
ReduxMap.jpg概念1、Store
Store 就是保存数据的地方,你可以把它看成一个容器。
前面说了,所有的状态,保存在一个对象里面
,所以整个应用有且只能有一个 Store。
概念2、State
Store 之中有所有的数据。那么 Store 的快照就是 State。当前的 State 通过store.getState()
拿到。
Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样。
概念3、Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。
Action 就是 View 发出的通知
,表示 State 应该要发生变化了。
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
概念4、Action Creator
View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
上面代码中,addTodo函数就是一个 Action Creator。
概念5、store.dispatch()
store.dispatch()是 View 发出 Action 的唯一方法。
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。
结合 Action Creator,这段代码可以改写如下。
store.dispatch(addTodo('Learn Redux'));
概念6、Reducer,计算过程
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程
就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) {
// ...
return new_state;
};
整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。
const defaultState = 0;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
const state = reducer(1, {
type: 'ADD',
payload: 2
});
上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。
实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
import { createStore } from 'redux';
const store = createStore(reducer);
上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。
const actions = [
{ type: 'ADD', payload: 0 },
{ type: 'ADD', payload: 1 },
{ type: 'ADD', payload: 2 }
];
const total = actions.reduce(reducer, 0); // 3
上面代码中,数组actions表示依次有三个 Action,分别是加0、加1和加2。数组的reduce方法接受 Reducer 函数作为参数,就可以直接得到最终的状态3。
概念7、纯函数
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。
纯函数是函数式编程的概念,必须遵守以下一些约束。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。
概念8、store.subscribe()
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
中间件是什么
异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。
怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
目前的情形是这样的:
- Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
- View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
- Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
在redux中,thunk是redux作者给出的中间件,实现极为简单,10多行代码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
//这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:
//action(dispatch, getState, extraArgument);
//发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。
redux-saga
redux-saga:action统一、可以集中处理异步操作等优点外,redux-saga中使用声明式的Effect以及提供了更加细腻的控制流。
Umi
项目使用Umi,Umi的背后是DvaJS,dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
配合 umi 使用后,0 API
Redux for React
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。
这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。
UI 组件 容器组件
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征:
1. 只负责 UI 的呈现,不带有任何业务逻辑
2. 没有状态(即不使用this.state这个变量)
3. 所有数据都由参数(this.props)提供
4. 不使用任何 Redux 的 API
因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。
容器组件,恰好相反:
1. 负责管理数据和业务逻辑,不负责 UI 的呈现
2. 带有内部状态
3. 使用 Redux 的 API
如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
@connect
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
使用Redux工具进行调试
Redux拥有很多第三方的调试工具,可用于分析代码和修复bug。最受欢迎的是time-travelling tool,即redux-devtools-extension。设置它只需要三个步骤。
Redux Devtools很强大。你可以在action, state和diff(方法差异)之间切换。选择左侧面板上的不同action,观察状态树的变化,你还可以通过进度条来播放actions序列。
Redux-saga
redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。
这意味着应用的逻辑会存在两个地方:
- reducer负责处理action的stage更新
- sagas负责协调那些复杂或者异步的操作
网友评论