Redux笔记
React 入门实例教程
React 渲染机制解析
Redux 入门教程
在React中使用Redux
React性能优化 – 利用React-Redux
对于react-redux的理解梳理
react-redux流程与实现分析
跟着例子一步步学习redux+react-redux
React高级篇(一)从Flux到Redux,react-redux
环境搭建
安装create-react-app:cnpm install -g create-react-app
创建react应用:create-react-app reactlearning
安装redux、react-redux等依赖:
cnpm install --save redux react-redux redux-thunk
npm install --save-dev redux-logger
redux存储和处理数据,react-redux帮助你完成数据订阅,redux-thunk可以帮你实现异步action,redux-logger是redux的日志中间件。
- babel-preset-env根据配置环境计算babel对代码填充何种等级的polyfill,已包括es2015的配置,但没有包括stage-x。官方首推。
- babel-preset-[stage-0、stage-1、stage-2、stage-3]与ES7相关的配置;
- 除此之外,Babel还可以有其他扩展,比如我们希望使用JSX语法,这时需要引入babel-preset-react处理器。
Flux
Flux框架也是一种MVC框架,不同于传统的MVC,它采用单向数据流,不允许Model和Control互相引用。Flux框架大致如下:
- Actions: 驱动Dispatcher发起改变
- Dispatcher: 负责分发动作(事件)
- Store: 储存数据,处理数据
- View: 视图部分
Dispatcher只会暴露一个函数-dispatch,接受Action为参数,发起动作。如果需要增加新功能,不需要改变或者增加接口,只需增加Action类型。
Store 一般会继承EventEmitter,实现事件监听,发布,卸载。需要将store注册到Dispatcher实例上才能够发挥作用。
Redux特点
相比Flux,Redux有如下两个特点:
- 在整个应用只提供一个Store,它是一个扁平的树形结构,一个节点状态应该只属于一个组件。
- 不允许修改数据。即不能修改老状态,只能返回一个新状态。
Redux 的设计思想很简单
(1)Web 应用是一个状态机,视图与状态是一一对应的。
(2)所有的状态,保存在一个对象里面。
Redux数据流如下:
view dispatch一个action后,通过对应reducer处理,然后更新store,最终views根据store数据的改变重新渲染界面。
不同于 Flux ,Redux 并没有 dispatcher 的概念(Store已经集成了dispatch方法,所以不需要Dispatcher)。它依赖纯函数来替代事件处理器,这个纯函数叫做Reducer。Reducer封装了处理数据的逻辑。
纯函数是函数式编程的概念,必须遵守以下一些约束:
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用
Date.now()
或者Math.random()
等不纯的方法,因为每次会得到不一样的结果
React整个的渲染机制就是在state/props发生改变的时候,重新渲染所有的节点,构造出新的虚拟Dom tree跟原来的Dom tree用Diff算法进行比较,得到需要更新的地方在批量造作在真实的Dom上,由于这样做就减少了对Dom的频繁操作,从而提升的性能。
使用redux
使用redux的方法主要是三步曲:创建store、创建action、创建reducer。而在这之后才是与业务或者组件相关的数据处理和展示。
创建Store
Store 就是保存数据的地方,整个应用只能有一个 Store。
Redux 提供createStore
这个函数,用来生成 Store。
import { createStore } from 'redux';
const store = createStore(reducer);
上面代码中,createStore
函数接受reducer函数作为参数,返回新生成的 Store 对象。
Store
对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
Store是用来管理state的单一对象。其中有三个方法:
store.getState()
:获取state。可以用该函数获取经过reducer返回的新的state;store.dispatch(action)
:发出操作,更新state。action内有操作的类型,可以触发不同的对state的更新;store.subscribe(listener)
:监听变化,当state发生更新时,就可以在这个函数的回调中监听。store.subscribe
方法返回一个函数,调用这个函数就可以解除监听。
Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。
创建action
State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action 是一个普通的 JavaScript对象。其中的type
属性是必须的,表示 Action 的名称。
const action = {
type: 'ADD',
payload: 'Learn Redux'
};
View层通过store.dispatch
触发动作:
onIncrement() {
store.dispatch(action);
}
dispatch 在 swtich 里面会识别action的 type 字段,能够识别出来的操作才会执行对 appState 的修改。
创建reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) {
// ...
return new_state;
};
reducer是一个会对不同action做出不同操作的函数。在没有任何操作情况下,我们返回初始的state。我们不直接去改变state的值,而是返回一个新的对象,保持state的唯一性。
const reducer = (state, action) => {
switch (action.type) {
case 'ADD':
return Object.assign({}, state, {
userName: action.payload
});
default:
return state;
}
}
实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch
方法会触发 Reducer 的自动执行。
由于 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 总是一个不变的对象。
reducer拆分
Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};
上面代码中,Reducer 函数被拆成了三个小函数,每一个负责生成对应的属性。
Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
import { combineReducers } from 'redux';
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
export default todoApp;
上面的代码通过combineReducers方法将三个子 Reducer 合并成一个大的函数。
这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法。
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
// 等同于
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
总之,combineReducers()做的就是产生一个整体的 Reducer 函数。该函数根据 State 的 key 去执行相应的子 Reducer,并将返回结果合并成一个大的 State 对象。
中间件的用法
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger';
const store = createStore(
reducer,
applyMiddleware(createLogger)
);
上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。
这里有两点需要注意:
(1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);
(2)中间件的次序有讲究。
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
);
上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。
react-redux
redux其实是一个通用的库,它不只针对react,还可以用到其它的像vue等库。因此react要想完美的应用redux,还需要封装一层,react-redux就是此作用。
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征。
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用
this.state
这个变量)- 所有数据都由参数(
this.props
)提供- 不使用任何 Redux 的 API
容器组件的特征恰恰相反。
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
react-redux库提供了一个react组件Provider和一个方法connect。下面的代码是react-redux项目的一般写法。
<Provider store={store} >
<div style={{height: '100%'}}>
<Handler/>
</div>
</Provider>
Provider组件相当于一个外壳,将store传入,并提供getChildContext方法,让后续的子组件可以通过context取到store对象。
connect方法返回一个函数,此函数的功能是创建一个connect组件包在WrappedComponent组件外面,connect组件复制了WrappedComponent组件的所有属性,并通过redux的subscribe方法注册监听,当store数据变化后,connect就会更新state,然后通过mapStateToProps方法选取需要的state,如果此部分state更新了,connect的render方法就会返回新的组件。
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
...
return function wrapWithConnect(WrappedComponent) {
...
}
}
React-Redux的connect其实会自动做一个对props的优化比较。过程如下:
connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state
映射到 UI 组件的参数(props
),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
function mapStateToProps(state) {
return {
value: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// Action Creator
const increaseAction = { type: 'increase' }