React进阶篇(2) -- Redux
前言
如果还不知道为什么要使用Redux,说明你暂时还不需要它。
三大原则
单一数据源
整个应用的 state
被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store
中。
State 是只读的
唯一改变 state 的方法就是触发 action
,action 是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers
。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始可以只有一个 reducer,随着应用变大,可以把它拆成多个小的 reducers。
小结
可以用普通对象来描述应用的 `state`;
要想更新 state 中的数据,需要发起一个 `action`。Action 就是一个普通 JavaScript 对象;
Action主要包含2个方面的属性:
type:标识属性,字符串,唯一,必要属性
xxx:数据属性,任意类型,可选属性
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。
准备
npm install --save redux
npm install --save react-redux //react绑定库
npm install --save-dev redux-devtools-extension //调试相关
官方例子
先来一个官方的例子,了解一下大致的工作情况。
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
实例分析
经过上面的介绍应该对Redux有了一定的了解。
到了这里很多人都会想,官方demo都能跑,概念也大致明白……
但是,实际项目怎么做!?
实例一:
介绍:单纯的redux的写法可以参考上面的官方写法,这里配合react-redux
使用,减少一些繁琐的操作。
相关目录:
src/
├── index.js
├── store
│ ├── action-type.js //声明判断的常量的集合便于统一管理
│ ├── actions.js //action集合,更新状态的指令
│ ├── reducers.js //reducer集合,更新状态的具体逻辑
│ └── store.js //创建 Redux store 来存放应用的状态
└── components
action-type.js
export const INCREMENT='INCREMENT';
export const DECREMENT='DECREMENT';
actions.js
import {INCREMENT,DECREMENT} from './action-types'
export const increment=(num)=>({type:INCREMENT,data:num }); //此处统一使用data方便获取,个人习惯
export const decrement=(num)=>({type:DECREMENT,data:num})
reducers.js
import {INCREMENT,DECREMENT} from "./action-types";
export function counter(state=0,action){
switch (action.type){
case INCREMENT:
return state+action.data; //此处统一使用data方便获取,个人习惯
case DECREMENT:
return state-action.data;
default:
return state;
}
}
store.js
import {createStore } from 'redux';
import {reducers} from "./reducers";
const store = createStore(
reducers,
//这里使用非侵入式调试,需npm install --save-dev redux-devtools-extension
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
index.js
import {Provider} from 'react-redux';
import store from './redux/store';
import App from './App';
ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root'));
App.jsx
import React, { Component } from 'react';
import Pro2 from './components/Pro2';
import { connect} from 'react-redux';
class App extends Component {
render() {
let {count}=this.props;
return (
<div className="App">
<p>App : click {count} times</p>
<Pro2 />
</div>
);
}
}
export default connect(
state=>({count:state}),
{}
)(App);
Pro2.jsx
import React, {Component} from 'react';
import { connect} from 'react-redux';
<!--引入更新状态的方法-->
import {increment,decrement} from '../redux/actions';
class Pro2 extends Component {
<!--更新redux中的状态-->
increment=()=>{
<!--在指定方法中传入参数-->
this.props.increment(1);
}
decrement=()=>{
this.props.decrement(2);
}
render() {
const {count}=this.props; //获取redux中的状态
return (
<div className='name'>
<p>Pro2 : click {count} times</p>
<div>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
</div>
)
}
}
export default connect( //将react与redux关联起来
state=>({count:state}), //获取redux中的状态,指定对应的接收props名字
{increment,decrement} //绑定action中更新状态的方法
)(Pro2);
效果如图:
实例二:
当有多个reducer时有些许的变化:
action-type.js,actions.js,store.js与上面相同
reducers.js
注意暴露出去的名字,在组件中通过store.getState().xxx
可以获取到对应状态
import {combineReducers} from 'redux';
import {INCREMENT,DECREMENT,UPDATEUSERINFO} from "./action-types";
const counter=(state=0,action)=>{
switch (action.type){
case INCREMENT:
return state+action.data;
case DECREMENT:
return state-action.data;
default:
return state;
}
}
// 用户初始化信息
const userInfoInit={
name:'adoctors',
headPic:'xxx',
tel:'130xxxxxxx2'
}
// 更新用户状态
const userInfo=(state=userInfoInit,action)=>{
switch (action.type) {
case UPDATEUSERINFO:
return ({...state,...action.data});
default:
return state;
}
}
export default combineReducers({
userInfo,
counter
})
组件中关联时的改变:
export default connect(
state=>({userInfo:state.userInfo}), //可以指定需要的某个状态
{changeLogin}
)(Header)
store.js
import {createStore } from 'redux';
import {reducers} from "./reducers";
const store = createStore(
reducers,
//这里使用非侵入式调试,需npm install --save-dev redux-devtools-extension
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
redux状态持久化
如果用过vuex的会知道这种状态管理遇到页面刷新时,状态会丢失,会回到初始状态;
那么redux有没有这样的问题呢?
恭喜恭喜!真!的!有!
所以redux状态的持久化其实时使用的基本需求。
npm install redux-persist
store.js
import React from 'react';
import {createStore ,applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import {persistStore, persistReducer} from 'redux-persist'; //引入固定方法
import storage from 'redux-persist/lib/storage/session'; //存储方式
import reducers from './reducers';
<!--相关配置-->
const config = {
key: 'root',
storage
};
let reducer = persistReducer(config, reducers);
let store = createStore(reducer, applyMiddleware(thunk));
let persistor = persistStore(store);
export { persistor, store }
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { persistor, store } from './store/store';
import { PersistGate } from 'redux-persist/es/integration/react'; //特定标签
import App from './containers/App';
ReactDOM.render(<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>, document.getElementById('root'));
改变redux状态时会同时生成相应的缓存:
存储的加密处理
cnpm i -S redux-persist-transform-encrypt
store.js
import createEncryptor from 'redux-persist-transform-encrypt';
const encryptor = createEncryptor({
secretKey: 'xxx',
onError: function(err) {
console.log(err)
}
})
let reducer = persistReducer({
key: 'root',
storage,
transforms: [encryptor]
},
reducers
)
let store = createStore(reducer, applyMiddleware(thunk));
参考文档:
https://www.redux.org.cn/docs/introduction/Motivation.html
https://github.com/zalmoxisus/redux-devtools-extension
https://www.npmjs.com/package/redux-persist
https://github.com/maxdeviant/redux-persist-transform-encrypt