Redux为什么而生
JQ时代的野蛮生长给前端生态带来进步(模块化,交互设计,响应式),同时也带来了跟多问题,这里我们foucs在以下两个问题:
1、前端代码难以复用(包括不同的语言)
2、页面的交互逻辑过于复杂
由此逐渐催生出现在的主流前端组件化框架,也是所谓的MVVM,为了解决这两个问题,大家给出了virtual DOM的概念跟CSS in JS的概念,将html,css,js代码统一到具有计算能力的js中,将曾经分离的页面结构/样式/交互逻辑三者统一并抽象出组件的概念,达到整体复用的目的。但是UI的动态的,数据与视图的双向绑定技术使得组件能够独立维护自己的状态数据,使组件成为了真正意义上的独立UI单元。可是看似完美的解决方案依旧带来了新的问题:
双向绑定的方式来将Model的数据展现到View。当Model中的数据发生变化时,一个或多个View会发生变化;当View接受了用户输入时,Model中的数据则会发生变化。在实际的应用中,当一个Model中的数据发生变化时,也有可能另一个相关的Model中的数据会被同步更新。这样,很容易出现的一个现象就是连锁更新(Cascading Update),Model可以更新Model,Model可以更新View,View也可以更新Model。你很难去推断一个界面的变化究竟是由哪个局部的功能代码引起。
设计理念
UI视图中混乱的数据交叉引用像机房中的复杂线路一样让人头大,多属性对想数据的强耦合破坏了组件的独立性。因此我们不得不发明新的解决方案。facebook给出Flux:将数据流向编程单向,引入Store、Action、Action Creators和Dispatcher等概念来管理数据流
- Store,它是数据操作的唯一地方,当数据发生变化时,它可以使用emit方法向其它地方发送名为'change'的广播,告知它们store已经发生了变化。
- Action
它是用来描述一个行为的对象,每个action里都包含了某个行为的相关信息。
// Action
{
actionName: 'ADD_COMMENT',
data: {
'id': '1',
'comment': 'Haha'
}
}
-
Flux流程图
Flux流程图
-
Dispatcher
它是一个信息分发中心,它是action和store的连接中心。它可以使用dispatch方法执行一个action,并且可以用register方法注册回调,在回调中处理store中的数据。 -
View
视图层监听了'change'事件,一旦change事件被触发,视图层就会调用setState方法来更新相应的UI State。
Redux是Flux的一种实现
Redux于2015年由Dan Abramov和Andrew Clark共同撰写,旨在简化和简化Flux引入的许多概念。
- Redux和Flux类似
- 它们都强调单向数据流的重要性
- 都通过type字段的Action来修改数据
- Redux的不同之处
- Redux只有一个store(使用单一数据树)
- Redux去掉了dispatcher的概念
- store只负责储存数据,不需要根据action来修改判断如何修改数据,这个职责交给了reducer
唯一状态树store
Store — 数据存储中心,同时连接着Actions和Views(React Components)。
- Store需要负责接收Views传来的Action
- 每一个Store实例都拥有dispatch方法,Views只需要通过调用该方法,并传入action对象作为形参,Store自然就就可以收到Action
- store.dispatch({type: 'INCREASE'});
- 然后,根据Action.type和Action.payload对Store里的数据进行修改
- 数据修改逻辑写在Reducer(一个纯函数)里,Store实例在创建的时候,就会被传递这样一个reducer作为形参,这样Store就可以通过Reducer的返回值更新内部数据了
- 最后,Store还需要通知Views,数据有改变,Views便去获取最新的Store数据,通过setState进行重新渲染组件(re-render)。
- 每一个Store实例都提供一个subscribe方法,Views只需要调用该方法注册一个回调(内含setState操作),之后在每次dispatch(action)时,该回调都会被触发,从而实现重新渲染;对于最新的Store数据,可以通过Store实例提供的另一个方法getState来获取
函数式与不可变性——Reducer
- Reducers是一个纯函数,接受state和action两个参数
- 不会在原state数据上做修改,Redux认为状态是不可变的
- 如果需要修改state数据,则编辑state数据副本,修改之后替换原数据
const counter = (state = { counter: 0 }, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
return {
...state,
counter: state.counter + 1
}
default:
return state
}
}
弱化唯一数据源带来的数据结构问题——combineReducers
将针对不同类型的数据比如:comment跟post定制,他们具有不同的数据更新规则(Action),所以可以定制不同的reducer。然后使用redux提供的combineReducers接口自下而上拼接起来形成rootReducer,那么rootReducer的返回值就是整个object tree。
import { combineReducers } from 'redux';
// 叶子reducer
function aReducer(state = 1, action) {/*...*/}
function cReducer(state = true, action) {/*...*/}
function aReducer(state = [2, 3], action) {/*...*/}
const eReducer = combineReducers({
e: eReducer
});
const bReducer = combineReducers({
c: cReducer,
d: dReducer
});
// 根reducer
const rootReducer = combineReducers({
a: aReducer,
b: bReducer
});
中间件——Middlerwares
中间件讲究的是对数据的流式处理,比较优秀的特性是:链式组合,由于每一个中间件都可以是独立的,因此可以形成一个小的生态圈。
在Redux中,Middlerwares要处理的对象则是:Action。
每个中间件可以针对Action的特征,可以采取不同的操作,既可以选择传递给下一个中间件,如:next(action),也可以选择跳过某些中间件,如:dispatch(action),或者更直接了当的结束传递,如:return。
标准的action应该是一个plain object,但是对于中间件而言,action还可以是函数,也可以是promise对象,或者一个带有特殊含义字段的对象,但不管怎样,因为中间件会对特定类型action做一定的转换,所以最后传给reducer的action一定是标准的plain object。
- [redux-thunk]里的action可以是一个函数,用来发起异步请求。
- [redux-promise]里的action可以是一个promise对象,用来更优雅的进行异步操作。
- [redux-logger]里的action就是一个标准的plain object,用来记录action和nextState的。
中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。
// 中间件的用法:扩展store
const enhancer = applyMiddleware(
thunk,
logger
);
const store = createStore(rootReducer, initialState, enhancer);
配合使用react-redux
- Provider组件
Provider存在的意义在于:想通过context的方式将唯一的数据源store传递给任意想访问的子孙组件。
- connect方法
让Component与Store进行关联,即Store的数据变化可以及时通知Views重新渲染。
Dojo
Counter
使用组件的state实现
class Counter extends Component {
//初始化state的值
constructor(props) {
super(props);
this.state = {counter: 0};
}
//这里采用了调用setState方法的两种方式,注意差别
handleIncrement() {
this.setState({
counter: this.state.counter + 1
});
}
//这里的两个Handle方法,一个是箭头函数,一个是普通函数(使用的时候要绑定上下文this)
handleDecrement = () => {
this.setState(preState => ({
counter: preState.counter -1
}))
};
render() {
return (
<div>
<h1>{this.state.counter}</h1>
<button onClick={this.handleIncrement.bind(this)}>+</button>
<button onClick={this.handleDecrement}>-
</button>
</div>
)
}
}
使用redux实现
- 定义reducer,初始化数据 + 定义数据变化规则
const initialState = 0;
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state
}
};
export default counterReducer
- 拿着reducer创造出数据中心sotre
import {applyMiddleware, createStore} from 'redux'
import logger from 'redux-logger'
import counterReducer from '../reducers/counterReducer'
const store = createStore(counterReducer, applyMiddleware(logger));
export default store
- 使用redux提供的Provider将store跟整个App绑定,这样子组件就可以通过store.getState()取到store中的值
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './containers/App/App'
import {Provider} from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));
- 将store与Counter组件使用connect接口链接;store中的state值映射为组件的props达到数据动态绑定的效果:dispatch触发数据变化之后组件重绘
import React, {Component} from 'react'
import store from '../../../store/index'
import {connect} from 'react-redux'
class Counter extends Component {
render() {
return (
<div>
<h1>{this.props.counter}</h1>
<button onClick={() => {
store.dispatch({type: 'INCREMENT'})
}}>+
</button>
<button onClick={() => store.dispatch({type: 'DECREMENT'})}>-</button>
</div>
)
}
}
const mapStateToProps = state => ({
counter: state.counter
});
export default connect(mapStateToProps)(Counter)
Post—Comment
Todo List
https://codesandbox.io/s/github/reactjs/redux/tree/master/examples/todos
网友评论