- [Redux 的内幕(一)](#Redux的内幕(一))
- [一个问题引发的血案](#一个问题引发的血案)
- [重振旗鼓](#重振旗鼓)
- [基础了解](#基础了解)
- [修修边幅](#修修边幅)
- [心中有疑惑](#心中有疑惑)
- [相关链接](#相关链接)
# Redux 的内幕(一)
## 一个问题引发的血案
博主在面试的过程中,面试官问 : “看你简历,Vue 和 React 都使用过,你能说一下 [Vue 和 React 的区别嘛?](https://github.com/PDKSophia/blog.io/blob/master/React%E7%AF%87-React%E5%92%8CVue%E7%9A%84%E5%8C%BA%E5%88%AB.md)”, 然后吧唧吧唧说了一下,于是!血案发生了,当我答道 Vuex 和 Redux 的时候,面试官问了一句,为什么 Redux 总是要返回一个新的 `state` ?返回旧的 `state` 为什么不行 ?面试结果不用说,GG 了。
## 重振旗鼓
过了大半个月,自己总结面试经验的时候,把 Redux 的源码看了一遍,ojbk,看的晕头转向,然后去 github 上看了一些大哥们的解读,再自己总结一哈,写个专栏,用于自己以后的复习
## 基础了解
#### Redux 是什么?
Redux 是 JavaScript 状态容器,提供可预测化的状态管理方案, 官网里是这么介绍的 :
```javascript
// Redux is a predictable state container for JavaScript apps.
```
#### 那么它能做什么?
```javascript
// It helps you write applications that behave consistently, run in different environments (client, server, and native) and are easy to test.
// On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.
```
#### 三大原则
- 单一数据源 : 整个应用的 `state` 都存储在一颗 state tree 中,并且只存在与唯一一个 store 中
- state 是只读的 : 唯一改变 state 的方法只能通过触发 `action`,然后通过 action 的 `type` 进而分发 dispatch 。不能直接改变应用的状态
- 状态修改均由纯函数完成 : 为了描述 action 如何改变 state tree,需要编写 `reducers`
## 修修边幅
这里我们先来了解一下 store、middleware、action、reducer 等知识
### store
这里的 `store` 是由 Redux 提供的 createStore(reducers, preloadedState, enhancer) 方法生成。从函数签名看出,要想生成 store,必须要传入 reducers,同时也可以传入第二个可选参数初始化状态(preloadedState)。第三个参数一般为中间件 `applyMiddleware(thunkMiddleware)`,看看代码,比较直观
```javascript
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk'; // 这里用到了redux-thunk
const store = createStore(
reducers,
state,
applyMiddleware(thunkMiddleware) // applyMiddleware首先接收thunkMiddleware作为参数,两者组合成为一个新的函数(enhance)
);
```
redux 中最核心的 API 就是 —— `createStore`, 通过 createStore 方法创建的 store 是一个对象,它本身包含 4 个方法 :
- getState() : 获取 store 中当前的状态。
- dispatch(action) : 分发一个 action,并返回这个 action,这是唯一能改变 store 中数据的
方式。
- subscribe(listener) : 注册一个监听者,它在 store 发生变化时被调用。
- replaceReducer(nextReducer) : 更新当前 store 里的 reducer,一般只会在开发模式中调用该方法。
### middleware
下图中表达的是 Redux 中一个简单的同步数据流动场景,点击 button 后,在回调中分发一个 action, reducer 收到 action 后,更新 state 并通知 view 重新渲染。
单向数据流,看着没什么问题。 但是,如果需要打印每一个 action 信息来调试,就得去改 dispatch 或者 reducer 实现,使其具有 打印日志的功能。
又比如,点击 button 后,需要先去服务端请求数据,只有等数据返回后,才能重新渲染 view,此时我们希望 dispatch 或 reducer 拥有异步请求的功能。再比如,需要异步请求数据返回后,打印一条日志,再请求数据,再打印日志,再渲染。
面对多样的业务场景,单纯地修改 dispatch 或 reducer 的代码显然不具有普适性,Redux 借鉴了 Node.js Koa 里 middleware 的思想,Redux 中 reducer 更关心的是数据的转化逻辑,所以 **middleware 就是为了增强 dispatch 而出现的。**
### Action
```javascript
// 引用官网的介绍
// Actions are payloads of information that send data from your application to your store.
// They are the only source of information for the store
// You send them to the store using store.dispatch().
```
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。简单来说,Action 就是一种消息类型,他告诉 Redux 是时候该做什么了,并带着相应的数据传到 Redux 内部。
Action 就是一个简单的对象,其中必须要有一个 type 属性,用来标志动作类型(reducer 以此判断要执行的逻辑),其他属性用户可以自定义。如:
```javascript
const START_FETCH_API = 'START_FETCH_API';
```
```javascript
{
type: START_FETCH_API,
data: {
id: itemId,
value: 'I am Value'
}
}
```
### Action Creator
看看官网中的介绍 : Action Creator are exactly that—functions that create actions. It's easy to conflate the terms “action” and “action creator”, so do your best to use the proper term。 也就是说 : Redux 中的 Action Creator 只是简单的返回一个 Action
```javascript
function fetchStartRequestApi(jsondata) {
return {
type: START_FETCH_API,
data: jsondata
};
}
```
我们知道,Redux 由 Flux 演变而来,在传统的 Flux 中, Action Creators 被调用之后经常会触发一个 dispatch。比如:
```javascript
function fetchStartRequestApiDispatch(jsondata) {
const action = {
type: START_FETCH_API,
data: jsondata
};
dispatch(action);
}
```
但是,在 Redux 中,我们只需要把 Action Creators 返回的结果传给 dispatch() ,就完成了发起一个 dispatch 的过程,甚至于 创建一个 `被绑定的 Action Creators` 来自动 dispatch
```javascript
// example 1
dispatch(fetchStartRequestApi(jsondata));
// example 2
const boundFetchStartRequestApiDispatch = jsondata =>
dispatch(fetchStartRequestApi(jsondata));
```
这里有人就要昏厥了,dispatch() 是个啥?其实前面就讲过了,通过 createStore() 创建的 `store` 对象,他有一个方法 : dispatch(action),store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下,我们都会使用 react-redux 提供的 connect() 帮助器来调用。bindActionCreators() 可以自动把多个 action 创建函数绑定到 dispatch() 方法上。
### Reducers
```javascript
// 引用官网的介绍
// Reducers specify how the application's state changes in response to actions sent to the store.
// Remember that actions only describe what happened, but don't describe how the application's state changes
```
上边也说过了,Reducers 必须是一个纯函数,它根据 action 处理 state 的更新,如果没有更新或遇到未知 action,则返回旧 state;否则返回一个新 state 对象。**注意:不能修改旧 state,必须先拷贝一份 state,再进行修改,也可以使用 Object.assign 函数生成新的 state。**具体为什么,我们读源码的时候就知道啦~
永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random();
下边上个例子代码,帮助消化,发送请求,获取音乐列表
```javascript
// action.js
/*
* action 类型
*/
export const START_FETCH_API = 'START_FETCH_API';
export const STOP_FETCH_API = 'STOP_FETCH_API';
export const RECEIVE_DATA_LIST = 'RECEIVE_DATA_LIST';
export const SET_OTHER_FILTERS = 'SET_OTHER_FILTERS';
/*
* 其它的常量
*/
export const otherFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_ACTIVE: 'SHOW_ACTIVE'
};
/*
* action 创建函数
*/
export function startFetchApi() {
return {
type: START_FETCH_API
};
}
export function stopFetchApi() {
return {
type: STOP_FETCH_API
};
}
export function receiveApi(jsondata) {
return {
type: RECEIVE_DATA_LIST,
data: jsondata
};
}
export function setOtherFilters(filter) {
return {
type: SET_OTHER_FILTERS,
data: filter
};
}
// 异步
export const fetchMusicListApi = music_id => dispatch => {
dispatch(startFetchApi());
fetch({
url: url,
method: 'POST',
data: {
music_id: music_id
}
})
.then(res => {
dispatch(stopFetchApi());
dispatch(receiveApi());
})
.catch(err => {
console.log(err);
});
};
```
```javascript
// reducers.js
// 引入 action.js
import { otherFilters } from './action';
// 初始 state
const initialState = {
otherFilters: otherFilters.SHOW_ALL,
list: [],
isFetching: false
};
function reducers(state, action) {
switch (action.type) {
case SET_OTHER_FILTERS:
return Object.assign({}, state, {
otherFilters: action.payload.data
});
case START_FETCH_API:
return Object.assign({}, state, {
isFetching: true
});
case STOP_FETCH_API:
return Object.assign({}, state, {
isFetching: false
});
case RECEIVE_DATA_LIST:
return Object.assign({}, state, {
list: [...action.payload.data]
});
default:
return state;
}
}
```
### 注意
1. 不要修改 state。 使用 `Object.assign()` 新建了一个副本。不能这样使用 Object.assign(state, { otherFilters: action.payload.data }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象
2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。
## 心中有疑惑
- bindActionCreators() 是如何自动帮我把 action 绑定到 dispatch 上的?
- 什么是纯函数?
- 为什么 reducer 必须是纯函数?
- 为什么只能通过 action 来修改 state,直接修改有什么问题?
- bindActionCreators() 是如何自动帮我把 action 绑定到 dispatch 上的?
- 为什么 reducers 在 default 的情况下,一定要返回旧的 state?
- ...
# 相关链接
- Redux 中文文档 : https://cn.redux.js.org/docs/basics/Actions.html
- Redux 英文文档 : https://redux.js.org/basics/actions