前言
在两年前曾短暂的使用过react+redux进行开发,可是一入vue深似海,从此其他是路人,趁着闲的时间,赶紧撸一把redux在react的实现。下面来简单的和大家分享一下。
什么是flux?
在了解redux之前,有必要先了解一下flux, flux是Facebook用户建立客户端Web应用的前端架构,以单向数据流方式支持MVC, Flux应用有三个主要部分:Dispatcher调度,存储Store 和 视图View ,当一个用户和视图交互时,有一个分发器dispatcher用来发送动作action到数据存储中,然后更新视图,存储接受更新,适当地调节这些更新,而不是一致地依赖外部更新其数据,存储之外根本不知道它是如何管理领域数据的,这有助于实现一种清晰的分离关注。
这是flux的结构和数据流向图,不难看出一个单向数据流是Flux模式的核心。
什么是redux?
大家如果稍有了解或者曾经接触的话,一定会听过这样一句话:如果说flux是一种思想,那么redux就是其中一种实现。 当然还有其它的实现方式如MoBx等。
Redux是对flux的一种演变,也是flux思想的一种子集。设计目的在于 Redux 试图让 state 的变化变得可预测,Redux使Javascript的状态管理变得更加可预期,以一种新方式思考开发应用,这个方式是:状态从一个初始状态开始,被一系列动作序列改变,这种新方式是通往复杂Web应用的捷径。
redux的工作流程
redux的工作流程与其设计三大基本原则密不可分
- 单一数据源
- state是只读的
- 只能使用reducer来修改
首先要明确一下在react中完整的redux流程需要那几个对象 或者角色。
- 组件/视图
- state 存储中心
- action 行为
- reducer 暂且叫它处理业务的地方或者过滤器
再来看它的工作原理
上述 2,3,4的合集我们暂且将它称为store, 基本的角色有了,那么还得做一些角色之间的关联,比如组件,如果需要访问store里面的数据,那么首先就要和store做链接,只有建立连接之后那么他才有一个访问权。如果组件想要访问store里面的state,那么然后他要定义一个动作类型,包含一个名称和一些便于store识别的信息(可选),根据这个动作类型就是action, store会把他丢给reducer, reducer是store的大脑,可以访问store中的元数据,然后根据store丢过来的信息,reducer做出种种处理,最后返回一个结果。这个结果再由store传递给视图方面,来使页面更新。
如果上面的这个阐述的不够清楚的话,大家可以想象一个模拟借书的场景 把各个角色代入进去,大致是这样的,
component/view ====》 借书人小王
state====》图书馆A
action====》是欲借的书的信息(三国演义)
reducer====> 图书馆管理员(易中天)
角色暂时这样安放,那么把场景带入,
话说中国历史博大精深,小王同学从小最爱学历史,而且对三国的历史最感兴趣,于是乎小王周五放学拿着借书卡就往国图书馆奔, (ps:借书人首先要在该图书馆办一个借书卡,图书馆见卡才能放行) 走到门口,门卫一看有借书卡,来的还是个小伙子,还夸了句:“小伙子年纪轻轻就这么爱学习,好啊 真好啊。” 小王抿嘴一笑, 不置可否,径直借书去了,可是走进去,小王蒙了,这么多书,我找一本三国演义,我太难了,此时小王灵机一动忽然想起,这里头的图书馆管理员,易中天,江湖人称老易,掌管整个书馆,绝对能找到,于是找到老易,老易是一个七八十岁老头,一脸博学的文化人,看见小王也很高兴,说小伙子,这回借什么书,小王说:“易爷爷,这次想借一本三国演义”,老易说:“三国演义啊,行 等我想想在哪”,约莫4,5秒,老易径自拿出一本三国演义给了小王,小王大喜连声道谢。于是捧书而走。 埋头苦读,遂成三国通。
整个场景与上面的阐述大家可以思考一下,上面主要是讲一下概念,大家最关心的肯定是代码实现 下面分步骤来解释.
代码实现
拿简书网站的这个部分来演示一下实现
1. 定义组件
这是定义的上面那个列表的组件,那么组件写好之后,我们就开始往里头填数据,数据从何而来,自然是redux。
2.定义元数据
reducer.js 内容如下:
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
writerList: [],
});
这个writerList就是我们最后要渲染到WriterWrapper里面的数据,接下来的一切行为都是为了取到这个writerList
3.链接组件与store
WriterWrapper组件添加如下内容
import React, { PureComponent } from 'react';
import {connect} from 'react-redux'
const mapStateToProps = (state) =>{
return {
list: state.getIn(['home', 'writerList']) // 这一步取到为空 动作还未触发
}
}
const mapStateTodispatch = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapStateTodispatch)(Writer);
react-redux抛出了一个connect方法 用来将react组件和store做绑定链接,connect方法接受两个参数,一个是mapStateToProps,这个参数的作用是根据组件自身的状态选择自身需要的数据,因为这个方法它可以访问store里面所有的元数据,一旦使用错误 会造成性能上的损耗,所以要谨慎使用,另一个是mapStateTodispatch,包含一个dispatch方法,负责将视图的行为也就是action传递给reducer进行处理,最后跟上组件名称则表示组件已经和redux进行了关联,为什么用这种方式就能关联,先跳过,我们稍后再详细说明。
4.定义action 触发
到这里需要明确两件事: 1.我要拿到什么。 2通过什么方式拿 dispatch派发的action一般情况下是一个对象,但是当涉及到异步时 action也可以是一个函数,典型的例子就是上面这个writerList我们需要从服务端取得,这个时候我们可能需要定义一个函数类型的action代码如下:
import { actionCreators } from '../store/index'
const mapStateToProps = (state) =>{
return {
list: state.getIn(['home', 'writerList'])
}
}
const mapStateTodispatch = (dispatch) => {
return {
getWriterList(){
dispatch(actionCreators.getWriterList())
}
}
}
componentDidMount(){
this.props.getWriterList()
}
list就是我们最终要取得的数据,我们需要通过getWriterList() 这个方法去获取数据,因为已经做了关联, 我们可以在生命周期钩子里去调用这个函数 此时dispatch触发的是一个异步函数 actionCreators.js文件中的getWriterList()方法 为了方便管理 可以都统一拆分到一个actionCreators.js文件之中然后抛出 其代码如下:
actionCreators.js内容如下
export const getWriterList = () => {
// 借用redux-chunk可以使action处理异步 标记是返回的是否是函数
return (dispatch) => {
axios.get('/api/writerList.json').then(res=>{
const result = res.data.data;
dispatch(addWriterList(result))
}).catch(err=>{
console.log(err)
})
}
}
很明显在这里,我们发出了一个请求, 将返回的结果再次dispatch触发 此时将结果传入到一个addWriterList()方法中,代码如下:
export const ADD_WRITER_LIST = 'home/ADD_WRITER_LIST'; constants.js
actionCreators.js内容添加方法
import * as constants from './constants';
const addWriterList = (result) => {
return {
type: constants.ADD_WRITER_LIST,
writerList: fromJS(result.writerList)
}
}
这个方法最后return出一个对象 符合了非异步情况下action一般是一个object的规则 是不是好奇这个constants,其实就是定义的actionType,reducer需要根据这个type来做出相应处理。
5.reducer过滤
reducer是一个纯函数,本质上传入一定类型的数据,必然返回一定类型的结果,做具体的数据过滤尤其合适,
reducer.js内容如下
const defaultState = fromJS({
writerList: [],
});
const addWriterList = (state, action)=>{
return state.set('writerList', action.writerList)
}
export const ADD_WRITER_LIST = 'home/ADD_WRITER_LIST'; constants.js
export default (state = defaultState, action) => {
switch(action.type) {
case constants.ADD_WRITER_LIST:
return addWriterList(state, action);
default:
return state;
}
}
reducer接受两个参数 第一个是state元数据, 第二个是action视图触发的动作类型,通过这两个信息 reducer就可以返回符合期望的信息,参照所有图书和三国演义的例子。根据对应的ADD_WRITER_LIST类型 执行完addWriterList方法后 此时 defaultState 的 writerList, 已经是一个经过服务端数据填充的元数据,而因为组件与redux做了链接 所以页面会正确的渲染出目标视图,即简书网站的列表组件。
怎么做的链接?
大家可能一直有疑问为什么通过connect方法就能和组件做链接, connect背后的逻辑是这样的
1.创建一个全局store对象
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
));
export default store;
类似上面的代码大家肯定都很熟悉,可以先不管引入的reducer,只需要知道是处理具体的过滤逻辑即可,是一个庞大的模块,从上面的redux模块中 引入了createStore这个方法并执行, applyMiddleware是作为中间件处理异步,thunk可以使传入的action不仅仅是对象, 还可以是函数,执行createStore()方法后把它抛出。 那我们再来看reducer,reducer是一个庞大的模块,那么必然是有一个个小的模块去组成的,每一个小的模块,又包含 redux的各种角色。这种拆分方式有利于各个模块之间的数据管理不被污染
假如将我们刚才做的WriterWrapper组件看作一个小的store 那么它的结构现在是这样的
index.js内容如下
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
这个文件引入了 刚才所需要的所有内容 并做了抛出 我们姑且把它放到home目录下。
2 .创建一个全局reducer文件
import { combineReducers } from 'redux-immutable';
import { reducer as homeReducer } from '../pages/home/store';
const reducer = combineReducers({
home: homeReducer,
})
export default reducer;
这个文件引入了刚才抛出的那个home目录下的reducer对象,介绍一下combineReducers是为了做多个reducer的合并
3.绑定视图
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import Header from './common/header';
import Home from './pages/home';
import store from './store';
class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route path='/' exact component={Home}></Route>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
通过react-redux的provider提供商 将整个容器包裹,于是就相当于整个store和应用做了链接,这时就有了组件的connect链接,这样一个完整的redux流程就基本形成了。
写在最后
一直纠结于文章的顺序安排,有些地方顺序安排的不合理,ps:(千万别误人子弟了)大家也可以给出自己的见解,我会继续补充修改本文。
文章参考: