Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
内容目录
Redux?
- JavaScript单页应用开发日趋复杂 --> state more --> state在什么时候,由于什么原因,如何变化已然不受控制
- 分开变化和异步 --> React试图在视图层禁止异步和直接操作DOM来解决这个问题 --> React依旧把处理state中数据的问题留给了我们
- Redux由Flux演变而来,但受Elm的启发,避开了Flux的复杂性
- 你可能并不需要Redux
核心概念
三大原则
- 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。
先前技术
- Redux 可以被看作 Flux 的一种实现吗? 是,也可以说 不是。
- 和 Flux 一样,Redux 规定,将模型的更新逻辑全部集中于一个特定的层(Flux 里的 store,Redux 里的 reducer)
- Flux 和 Redux 都不允许程序直接修改数据,而是用一个叫作 “action” 的普通对象来对更改进行描述。
- 而不同于 Flux ,Redux 并没有 dispatcher 的概念(原因是它依赖纯函数来替代事件处理器)。
- 和 Flux 的另一个重要区别,是 Redux 设想你永远不会变动你的数据。
- Elm 是一种函数式编程语言,更新遵循 (state, action) => state 的规则
基础
action:事件响应
reducer:纯函数,数据改变
store:生成真正的store数据树
示例
app.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route } from 'react-router-dom';
import { asyncComponent } from 'AsyncComponent';
import HeaderMenu from './components/heard';
import SiderMenu from './components/sider';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducer';
import './css/main.css';
const demo1 = asyncComponent(() => import(/* webpackChunkName: 'demo1' */ './demo1'));
const demo2 = asyncComponent(() => import(/* webpackChunkName: 'demo2' */ './demo2'));
let store = createStore(reducer, applyMiddleware(thunk));
const router = (
<Provider store={store}>
<HashRouter>
<div>
<HeaderMenu />
<div className="ant-layout ant-layout-has-sider layout">
<SiderMenu />
<Route exact path="/" component={demo1} />
<Route exact path="/demo/demo1" component={demo1} />
<Route exact path="/demo/demo2" component={demo2} />
</div>
</div>
</HashRouter>
</Provider>
);
ReactDOM.render(router, document.getElementById('content'));
复制代码
index.js:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Layout, Breadcrumb, Button } from 'antd';
import 'date-util';
import ModalTip from 'modalTip';
import { changeNeedCode } from './action';
const { Content } = Layout;
/**
* demo1
*/
const mapStateToProps = (state, ownProps) => {
return {
demo1Store: state.demo1Store
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
changeNeedCode: bindActionCreators(changeNeedCode, dispatch)
};
};
class demo1 extends React.Component {
componentDidMount() {
let that = this;
let changeNeedCode = that.props.changeNeedCode;
changeNeedCode('zhanghao');
}
render() {
let that = this;
return (
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb style={{ margin: '12px 0' }}>
<Breadcrumb.Item>demo</Breadcrumb.Item>
<Breadcrumb.Item>demo1</Breadcrumb.Item>
</Breadcrumb>
<Content style={{ background: '#fff', padding: 24, margin: 0, minHeight: 280 }}>
<div>
<div>{new Date().format('yyyy-MM-dd hh:mm:ss')}</div>
<div>{that.props.demo1Store.welcomeText}</div>
<div>是否需要验证码{that.props.demo1Store.needCode}</div>
<div>
<Button onClick={() => that._showModalTip('info')}>Info</Button>
<Button onClick={() => that._showModalTip('success')}>Success</Button>
<Button onClick={() => that._showModalTip('error')}>Error</Button>
<Button onClick={() => that._showModalTip('warning')}>Warning</Button>
</div>
</div>
</Content>
</Layout>
);
}
_showModalTip(type) {
switch (type) {
case 'info':
ModalTip.infoTip('info');
break;
case 'success':
ModalTip.successTip('success');
break;
case 'error':
ModalTip.errorTip('error');
break;
case 'warning':
ModalTip.warningTip('warning');
break;
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(demo1);
复制代码
action.js:
import fetch from 'fetch/fetch';
import ModalTip from 'modalTip';
let checkNeedCode = nickName => {
let params = {};
params.nickName = nickName;
params.t = new Date().getTime();
return fetch
.post('/oauth/checkLogin', params)
.then(res => {
return res.needCode;
})
.catch(e => {
ModalTip.warningTip(e.message);
});
};
exports.changeNeedCode = nickName => async dispatch => {
let needCode = await checkNeedCode(nickName);
dispatch({ type: 'change_needCode', needCode: needCode });
};
复制代码
reducer.js:
import { combineReducers } from 'redux';
const demo1Store = (state = { welcomeText: '', needCode: '' }, action = {}) => {
switch (action.type) {
case 'change_welcomeText':
return { ...state, welcomeText: action.welcomeText };
case 'change_needCode':
return { ...state, needCode: action.needCode };
default:
return { ...state };
}
};
const demo2Store = (state = { columnName: '', newsTitle: '', selectText: '选择器1', dUserCode: '' }, action = {}) => {
switch (action.type) {
case 'change_columnName':
return { ...state, columnName: action.columnName };
case 'change_newsTitle':
return { ...state, newsTitle: action.newsTitle };
case 'change_selectText':
return { ...state, selectText: action.selectText };
case 'change_dUserCode':
return { ...state, dUserCode: action.dUserCode };
default:
return { ...state };
}
};
export default combineReducers({
demo1Store: demo1Store,
demo2Store: demo2Store
});
复制代码
Reduxm
为了更好的使用Redux,进行二次封装
内容目录
redux存在的问题
- 一份store树,离开页面再次进入,数据不会初始化
- reducer拆分造成汇总困难
- action的type管理混乱,重复问题
- 繁杂的使用规则,index页面action和store引入,纯函数reducer大量case仅仅为了改变一个值
设计思想(@修饰器)
- connectstore对ReactDom继承注入action和store,重写componentWillUnmount生命周期,离开页面自动触发store初始化
- 使用@修饰器、store对reducer提取存入reducerfactory,action对action提取存入actionfactory和actiontypefactory
- action的type跟随store定义,并隐式添加命名空间解决type重复问题、以及隐式case定义省略大量case
api
/**
* 数据注入层
* 提供createStore、getDevTools、getActionType、getAllInitData四个方法
*
* createStore方法,绑定数据到整个react路由层
* @params router(react路由), debug(是否开启调试工具)
* @return reactRouter
*
* getDevTools方法,获取调试工具视图
* @return DevTools(调试工具视图)
*
* getActionType方法,获取storeName下所有actionType
* @param storeName(数据层名称)
* @return {}(storeName下所有actionType)
*
* getAllInitData方法,获取storeName下所有初始数据
* @param storeName(数据层名称)
* @return {}(storeName下所有初始数据)
*/
import Store from './store/store';
/**
* store修饰器,处理整个store层存入数据工厂
* @params storeName(数据层名称), allActionType(改变整个数据层的actionType), allStoreLogs(改变整个数据层的打印日志级别)
* @return true
*/
const store = Store.store;
/**
* storeActionType修饰器,按名称录入actionType
* @params actionType(数据改变响应type), level(日志级别)
* @return target
*/
const storeActionType = Store.storeActionType;
/**
* storeDestroy修饰器,按名称录入是否需要销毁
* @return target
*/
const storeDestroy = Store.storeDestroy;
/**
* connectStore修饰器,连接数据,事件和reactDom
* @params storeList[](页面所需数据层名称), destroyStoreList[](离开页面销毁数据层名称)
* @return reactDom
* 由于我会继承你的ReactDom并重写componentWillUnmount生命周期
* 所以
* 在你的ReactDom想实现componentWillUnmount生命周期必须加上静态属性
* 并且上下文还是ReactDom
* 如下
* static componentWillUnmount (){
this._cons();
}
_cons(){
console.log("生命周期销毁");
}
*/
import connectStore from './connect/connectstore';
/**
* 事件注入层
*/
import Action from './action/action';
/**
* action修饰器,处理整个action层存入事件工厂
* @param actionName(事件层名称)
* @return target
*/
const action = Action.action;
/**
* actionProps修饰器,按名称录入action
* @params actionFunName(事件名称), level(日志级别)
* @return target
*/
const actionProps = Action.actionProps;
/**
* actionInjection修饰器,按名称反向注入事件到reactDom
* @param actionName(事件名称)
* @return target
*/
const actionInjection = Action.actionInjection;
export { Store, store, storeActionType, storeDestroy, connectStore, action, actionProps, actionInjection };
复制代码
使用注意点
- 由于我会继承你的ReactDom并重写componentWillUnmout生命周期所以在你的ReactDom想实现componentWillUnmount生命周期必须加上静态属性并且上下文还是ReactDom如下:
static componentWillUnmount (){
this._cons();
}
_cons(){
console.log("生命周期销毁");
}
复制代码
- 为了方便使用,Store中提供getAllInitData方法,获取storeName下所有初始数据,减少想手动初始化数据时的重复性定义。
- (dispatch, _this),action中第二个的系统级入参,提供_this,方便action内部函数互相调用。
- app.js路由文件中,如果想使用如下方式:
(r => {
r.keys().forEach(r);
})(require.context('./', true, /reducer\.js/));
(r => {
r.keys().forEach(r);
})(require.context('./', true, /action\.js/));
复制代码
来省略手动引入reducer和action的话,所有页面组件必须按如下异步方式引入
const XXX = asyncComponent(() => import(/* webpackChunkName: 'XXX' */ './XXX'));
复制代码
如不想异步引入页面组件,则必须在import { Store } from 'reduxm';和import XXX from '.XXX';之前进行如下引入:
import './XXX/reducer';
import './XXX/action';
复制代码
示例
app.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route } from 'react-router-dom';
import { asyncComponent } from 'AsyncComponent';
import HeaderMenu from './components/heard';
import SiderMenu from './components/sider';
import 'date-util';
import './css/main.css';
(r => {
r.keys().forEach(r);
})(require.context('./', true, /reducer\.js/));
(r => {
r.keys().forEach(r);
})(require.context('./', true, /action\.js/));
import { Store } from 'reduxm';
const demo1 = asyncComponent(() => import(/* webpackChunkName: 'demo1' */ './demo1'));
const demo2 = asyncComponent(() => import(/* webpackChunkName: 'demo2' */ './demo2'));
let debug = true;
const router = Store.createStore(
<HashRouter>
<div>
<HeaderMenu />
<div className="ant-layout ant-layout-has-sider layout">
<SiderMenu />
<Route exact path="/" component={demo1} />
<Route exact path="/demo/demo1" component={demo1} />
<Route exact path="/demo/demo2" component={demo2} />
{debug ? Store.getDevTools() : null}
</div>
</div>
</HashRouter>,
debug
);
ReactDOM.render(router, document.getElementById('content'));
复制代码
index.js:
import React from 'react';
import { Layout, Breadcrumb, Button } from 'antd';
import { connectStore, actionInjection } from 'reduxm';
import ModalTip from 'modalTip';
const { Content } = Layout;
/**
* demo1
*/
@connectStore(['demo1Store'], ['demo1Store'])
@actionInjection('demo1Action')
export default class demo1 extends React.Component {
componentDidMount() {
this.props.changeNeedCode('zhanghao');
this.props.changeImmutableList(this.props.demo1Store);
}
render() {
let that = this;
console.log(that.props.demo1Store.immutableList);
console.log(that.props.demo1Store.immutableList.toJS());
console.log(that.props.demo1Store.immutableInList.immutableList[0]);
console.log(that.props.demo1Store.immutableInList.immutableList[0].toJS());
return (
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb style={{ margin: '12px 0' }}>
<Breadcrumb.Item>demo</Breadcrumb.Item>
<Breadcrumb.Item>demo1</Breadcrumb.Item>
</Breadcrumb>
<Content style={{ background: '#fff', padding: 24, margin: 0, minHeight: 280 }}>
<div>
<div>{new Date().format('yyyy-MM-dd hh:mm:ss')}</div>
<div>{that.props.demo1Store.welcomeText}</div>
<div>是否需要验证码{that.props.demo1Store.needCode}</div>
<div>
<Button onClick={() => that._showModalTip('info')}>Info</Button>
<Button onClick={() => that._showModalTip('success')}>Success</Button>
<Button onClick={() => that._showModalTip('error')}>Error</Button>
<Button onClick={() => that._showModalTip('warning')}>Warning</Button>
</div>
</div>
</Content>
</Layout>
);
}
_showModalTip(type) {
switch (type) {
case 'info':
ModalTip.infoTip('info');
break;
case 'success':
ModalTip.successTip('success');
break;
case 'error':
ModalTip.errorTip('error');
break;
case 'warning':
ModalTip.warningTip('warning');
break;
}
}
}
复制代码
action.js:
import fetch from 'fetch/fetch';
import ModalTip from 'modalTip';
import { Store, action, actionProps } from 'reduxm';
import immutable from 'immutable';
const demo1Type = Store.getActionType('demo1Store');
const demo1AllInitStore = Store.getAllInitData('demo1Store');
let checkNeedCode = nickName => {
let params = {};
params.nickName = nickName;
params.t = new Date().getTime();
return fetch
.post('/oauth/checkLogin', params)
.then(res => {
return res.needCode;
})
.catch(e => {
ModalTip.warningTip(e.message);
});
};
@action('demo1Action')
class demo1Action {
@actionProps('changeNeedCode')
static changeNeedCode = nickName => async (dispatch, _this) => {
let needCode = await checkNeedCode(nickName);
dispatch({ type: demo1Type.change_needCode, needCode: needCode });
};
@actionProps('changeImmutableList', 'error')
static changeImmutableList = demo1Store => async (dispatch, _this) => {
let immutableList = demo1Store.immutableList;
let immutableInList = demo1Store.immutableInList;
immutableList = immutable.set(immutableList, 0, 4);
immutableInList.immutableList[0] = immutable.set(immutableInList.immutableList[0], 0, 10);
dispatch({
type: demo1Type.change_demo1Store,
demo1Store: {
immutableList: immutableList,
immutableInList: immutableInList
}
});
demo1AllInitStore.welcomeText = 'www---www';
console.log(demo1AllInitStore);
};
}
复制代码
reducer.js:
import { store, storeActionType, storeDestroy } from 'reduxm';
import immutable from 'immutable';
@store('demo1Store', 'change_demo1Store')
class demo1 {
@storeActionType('change_welcomeText')
@storeDestroy
static welcomeText = 'Welcome to Redux test!';
@storeActionType('change_needCode')
@storeDestroy
static needCode = 1;
@storeActionType('change_immutableList', 'waring')
@storeDestroy
static immutableList = immutable.fromJS([1, 2, 3]);
@storeActionType('change_immutableInList')
@storeDestroy
static immutableInList = {
immutableList: [immutable.fromJS([7, 8, 9])]
};
}
复制代码