# 一、redux
# 1】使用场景
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
- 代码结构
- 组件之间通信(所有组件不限于有关系的组件)
对于大型的复杂应用来说,这两方面恰恰是最关键的。因此,只用 React 没法写大型应用。
为了解决这个问题,2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
"只有遇到 React 实在解决不了的问题,你才需要 Redux 。"
①简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
②从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到(全局使用)
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
发生上面情况时,如果不使用 Redux 或者其他状态管理工具,不按照一定规律处理状态的读写,代码很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化。
# 2】设计思想
Redux 的设计思想很简单,就两句话。
(1)Web 应用是一个状态机,视图与状态是一一对应的。
(2)所有的状态,保存在一个对象里面。
请务必记住这两句话,下面就是详细解释。
Redux是一个可预测性(我们给一个固定的输入,那么必定可以得到一个相应的结果)的状态管理容器。
可以集中管理react中多个组件的状态。
# 三大原则:
1.单一数据源:整个react
中的state
状态都会被统一管理到store
仓库中。(单一状态树)。store
中的数据结构往往是个给应用使用的深度嵌套的对象。
2.state是只读的:我们不能够直接改变state状态而是要通过触发redux
中的特定方法进行修改。(store.dispatch()
是 View
发出 Action
的唯一方法。)
- store 对象本身只有4个简单的方法作为 API:
store.dispatch(action)
store.subscribe(listener)
store.getState()
replaceReducer(nextReducer)
dispatch()
方法发送一个对象给 Redux ,这个对象就是action
。action
可以说是一个带着type
属性和其他数据的载体(用来更新state
),然后再提交到reducer
更新state
。 注意type
属性的值,设计action
对象时你要给它命名赋值。
3.使用纯函数来执行修改操作:
正如之前说的,Redux不允许应用直接修改
state
。它要求使用载action
来描述state
的变化,通过发送action
到store
来改变state
。为了描述action
如何改变state tree
,你需要编写reducers
。(这一切都是在部署store
,等待用户触发去action
,然后再去reducer
改变state
)Reducer 只是一些纯函数,它接收两个参数:先前的
state
和action
,并返回新的state
:// Reducer Functionvar someReducer = function(state, action) { ... return state;}
在设计 Reducer 应当注意按照纯函数来设计,纯函数有以下几个特征:
- 不使用外界网络或数据库调用(是dispatch去调用)
- 返回的值完全取决于其参数的值
- 调用具有相同的参数集的一个纯函数始终返回相同的值
总之,一个函数在程序执行的过程中只会根据输入参数给出运算结果,我们把这类函数称为“纯函数”。纯函数由于不依赖外部变量,使得给定函数输入其返回结果永远不变。
- 比如整数的加法函数,它接收两个整数值并返回一个整数值,对于给定的两个整数值,它的返回值永远是相同的整数值。
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
# 如果还不明白我们看一下这个最基本的使用过程:
首先,我们通过 Redux.createStore()
创建一个 store ,并把所有的 reducers 都作为其参数。
我们看下这个只有一个 reducer 的示例:
// 注意这儿使用 .push() 并不
// 是好的方法。只是这样比较易于展示这
// 个示例。我将会在下一节解释原因。
// Reducer 函数
var userReducer = function(state, action) {
if (state === undefined) {
state = [];
}
if (action.type === 'ADD_USER') {
state.push(action.user);
}
return state;
}
// 建立一个 store 并把 reducer 传递进去
var store = Redux.createStore(userReducer);
// 发送我们的第一个 action 来改变 state
store.dispatch({
type: 'ADD_USER',
user: {name: 'Dan'}
});
简短的总结下这段代码发生了什么:
- store 被建立了,并传递了一个 reducer。
- reducer 规定应用程序的初始 state 是一个空数组。
- 我们发送了一个 action,其
type
是ADD_USER
,携带的数据是user: {name: 'Dan'}
,意为添加一个名为 Dan 的新用户 - reducer 向 state 添加了新用户,并返回了更改后的 state,然后更新了 store
在这个示例中 reducer 实际上被调用了两次,一次是 store 被创建时,一次是使用 dispatch 发送 action 时。
当 store 被创建时,Redux 立即调用 reducer 并获取到 reducer 返回的初始 state。第一次调用 reducer 时 state 的值为 undefined
,于是 reducer 返回了一个空的数组作为 store 的初始 state。
Reducer 在每次传递 action 时都会被调用。Action 只是描述了有事情发生了这一事实,reducer 指明应用如何更新 state。
示例中传递 action 时第二次调用了 Reducer,Reducer 通过 action 携带的 type 属性的值 ADD_USER
来判断如何处理 state。
可以把 Reducer 想想成一个漏斗,它接收先前的 state 和 action,输出新的 state 来更新 store:
# 3】基本使用
# ①npm 安装redux插件
npm install --save redux
# ②创建store.js
在redux文件夹下创建store.js文件:
- 我们首先要使用
createStore
所以我们需要导入相关内容。 - 调用
createStore(fn)
方法:createStore
函数接受另一个函数作为参数,返回新生成的 Store 对象。(这个参数就是我们需要传入的是reducer
,意思是把reducer加入到store仓库中。reducer回调函数是通过action来计算新的state)
import { createStore } from 'redux';
import { data } from './reducer';
export var store = createStore(data)
# ③创建reducer.js
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。(使用reducer来通过action改变state)
在src下创建redux文件夹,在这个文件夹下新建reducer.js文件:
- 所以下一步我们需要设置reducer函数,向里面传入两个固定参数,state和action
var obj = [{
age: 18
}]
export function data(state = obj[0].age, action) {
switch (action.type) {
case 'add':
return state + action.data;
case 'del':
return state - action.data
default:
return state;
}
}
# ④创建action.js
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。Action 是一个对象。其中的type
属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。payload是携带的参数(这里是一个字符串)
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
在src下创建redux文件夹,在这个文件夹下新建action.js文件:
- 设置action对象,一个action行为必须含有一个type参数,其它属性可以自己定义。
- 真正调用api获取数据的是
dispatch
,reducer是纯函数做的是传入相应action.type
通过对应的写好的action.type
对比做运算改变state
的操作。
- 真正调用api获取数据的是
export function add(num) {
return {
type: 'add', data: num
}
}
export function del(num) {
return {
type: 'del', data: num
}
}
# ⑤在组件中使用redux
- 在view视图模板触发中需要使用
store.dispatch(action)
去分发action
,然后才能去对应reducer
中的找到对应的action.type
计算出新的state
。(注意在我们createStore(reducer)
的时候就传入了reducer
,也就是已经注册好了store
,到时候只需要dispatch
分发的时候传入相应的action
,就可以找到reducer
中对应的action方法去修改state
) - 这一切都是在
redux
的store
仓库中进行操作的,所以我们肯定还要导入store
仓库才能使用。把store
仓库中的state
使用store.getState()
全部取出,然后把这个状态加入到组件当中。 - 在
reducer
中我们需要传入初始的state
(或者旧状态)和用户行为action
,然后返回的是通过不同的传入action
行为改变后的state
状态。
import React, { Component } from 'react'
import { store } from '../redux/store';
import * as action from '../redux/action';
export default class Home extends Component {
state = {
num: store.getState()
}
componentDidMount() {
store.subscribe(() => {
this.setState({ num: store.getState() })
})
}
render() {
return (
<div>
Home---{this.state.num}
<button onClick={() => { store.dispatch(action.add(1)) }}>点我+1</button>
<button onClick={() => { store.dispatch(action.del(1)) }}>点我-1</button>
</div>
)
}
}
# 4】工作流程
Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
从组件入手分析:Redux 提供了一个叫 store
的统一仓储库,组件通过 dispatch
将 state
直接传入 store
,不用通过其他的组件。并且组件通过 subscribe
从 store
获取到 state
的改变。store
就像一个管理 state
改变的“中间人”,组件之间的信息传递不必直接在彼此间进行,所有的 state
变化都通过 store 这唯一数据源。使用了 Redux ,所有的组件都可以从 store 中获取到所需的 state, 他们也能从 store 获取到 state 的改变。这比组件之间互相传递数据清晰明朗的多。
control层:store=createStore(reducer(state,action))
view层:store.dispatch(action.fun(..))
model层:state={xxx:store.getState()}
完全和vuex
一模一样:reducer就是mutation。但是react需要组件订阅才能接收到state的更新。