Redux

redux是个数据流,并不依赖于react,可以和任何框架结合使用

Redux 三大原则

  • 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
  • State 是只读的,惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象 使用纯函数来执行修改,为了描述action如何改变state tree ,你需要编写 reducers
  • 单一数据源的设计让React的组件之间的通信更加方便,同时也便于状态的统一管理

Redux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
let initialState = {
title: {color:'red',text:'标题'},
content: {color:'green',text:'内容'}
}

function reducer(state = initialState, action){
switch (action.type) {
case UPDATE_TITLE_COLOR:
return {
...state,
title:{
...state.title,
color: action.payload
}
}
default:
return state;
break;
}
}

export default function createStore(reducer) {
let state;
let listener;
function getState(){
return state;
}
// action有格式要求,第一个必须是一个纯对象,new Object{},第二个必须要有一个type属性
function dispatch(action){
state = reducer(state,action);
// 发布
listener.forEach(item=>item());
}
// 订阅 当state状态改变的时候能自动去render页面
function subscribe(listener){
listener.push(listener);
return function (){ // 返回取消订阅方法
return listener.filter(item => !item === listener);
}
}
// 这步的目的是,初始reducer中的state是undefined的,派发这个动作,将initiState赋值给reducer中的state
// 所以以后调用 store.dispatch({type:'UPDATE_TITLE_COLOR',payload:'block'});这种时只需要传action的值即可
dispatch({type:'@@REDUX/INIT'});
return {
getState,
dispatch,
subscribe
}
}

let store = createStore(reducer);
store.getState();
store.dispatch({type:'...', payload: '...'});

// 假设这里有一个渲染的方法
renderApp(); // 第一次需要手动渲染
// 订阅 - 监听
let unSubscribe = store.subscribe(renderApp);
// 取消订阅
unSubscribe();

React + Redux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { Component } from 'react';
import { createStore } from '../redux';
import store from '../store';

export default class Counter extends Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidMount() {
// 监听() => this.setState({ value: store.getState() }) ,当reducer中状态改变时执行setStatet方法render页面
this.unsubscribe = store.subscribe(() => this.setState({ value: store.getState() }));
}
componentWillUnmount() {
// 组件卸载时取消监听事件,如果不取消,组件卸载之后this.setState中的this实例没有就会报错
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.value}</p>
<button onClick={() => store.dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => store.dispatch({ type: 'DECREMENT' })}>-</button>
</div>
)
}
}

bindActionCreators

[1] actionCreate

() => store.dispatch({ type: 'INCREMENT' }) 如何简化这个写法

1
2
3
4
5
6
7
8
9
function increment(){ // actionCreator  action的创建函数
return{ type: 'INCREMENT' };
}
function decrement(){ // actionCreator action的创建函数
return{ type: 'DECREMENT' };
}

<button onClick={() => store.dispatch(increment())}>+</button>
<button onClick={() => store.dispatch(decrement())}>-</button>

[2] bindActionCreators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { bindActionCreators } from 'redux';

function increment(){ // actionCreator action的创建函数
return{ type: 'INCREMENT' };
}
function decrement(){ // actionCreator action的创建函数
return{ type: 'DECREMENT' };
}
// bindActionCreators 绑定actionCreators actionCreators跟dispatch自动绑定在一起
increment = bindActionCreators(increment, store.dispatch);
decrement = bindActionCreators(decrement, store.dispatch);

<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>

[3] 手写 bindActionCreator

1
2
3
4
5
6
// bindAcrionCreators.js
export default function (actionCreator,dispatch) {
return function () {
return dispatch(actionCreator());
}
}

[4] 绑定多个 bindActionCreators

1
2
3
4
5
6
7
8
9
10
import { bindActionCreators } from './redux';

function increment(){ // actionCreator action的创建函数
return{ type: 'INCREMENT' };
}
function decrement(){ // actionCreator action的创建函数
return{ type: 'DECREMENT' };
}
let actions = {increment, decrement};
actions = bindActionCreators(actions, store.dispatch);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bindAcrionCreators.js

// 此方法只能接受一个actionCreator actionCreator是一个函数
function bindActionCreator(actionCreator,dispatch) {
return function () {
return dispatch(actionCreator());
}
}
export default function(actionCreators, dispatch){
if(typeof actionCreators == 'function'){
return bindActionCreator(actionCreators, dispatch);
}
const bundActionCreators = {};
for(let key in actionCreators){
bundActionCreators[key] = bindActionCreator(actionCreators[key],dispatch);
}
return bundActionCreators;
}

[5] bindActionCreators 传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function bindActionCreator(actionCreator,dispatch) {
return function (...args) {
return dispatch(actionCreator(...args));
}
}
export default function(actionCreators, dispatch){
if(typeof actionCreators == 'function'){
return bindActionCreator(actionCreators, dispatch);
}
const bundActionCreators = {};
for(let key in actionCreators){
bundActionCreators[key] = bindActionCreator(actionCreators[key],dispatch);
}
return bundActionCreators;
}


----

import { bindActionCreators } from './redux';

function increment(payload){
return{ type: 'INCREMENT', payload };
}
function decrement(payload){
return{ type: 'DECREMENT', payload};
}
let actions = {increment, decrement};
actions = bindActionCreators(actions, store.dispatch);

-----

<button onClick={()=>action.increment(2)}>+</button>
<button onClick={()=>action.decrement(3)}>-</button>

combineReducers

[1] 用法

1
2
3
4
5
6
7
import { combineReducers } from 'redux';
import conuter1 from './counter1';
let reducer = combineReducers({
conuter1,
conuter2
...
})

[2] 手写combineReducers

1
2
3
4
5
6
7
8
9
10
function combindReducers(reducers){
// 合并完了之后状态树 key是合并的状态树的属性名,值就是那个reducer
return function(state={},action){
let nextState = {};
for(let key in reducers){
nextState[key] = reducers[key](state[key],action);
}
return nextState;
}
}

react-redux

react组件和redux仓库进行自动关联的一个库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// index.js
import store from './store';
import {Provider} from './react-redux';
ReactDOM.render(<Provider store={store}> ... </Provider>,document.getElementById('root'));


// Counter.js
import {connect} from 'react-redux';

let mapStateToProps = state=>({value:state.counter});
let mapDispatchToProps = ;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter)

[1] 手写connect

// react-redux/Provider.js

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import Context from './Context';
export default class Provider extends React.Component{
render(){
return (
<Context.Provider value={{store: this.props.store}}>
{this.props.children}
</Context.Provider>
)
}
}

// react-redux/Context

1
2
import React from 'react';
export default React.createContext();

// react-redux/connect.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from "react";
import { bindActionCreators } from "../redux";
import Context from "./Context";
export default function(mapStateToProps, mapDispatchToProps) {
return function wrapWithConnect(WrappedComponent) {
return class extends React.Component {
static contextType = Context;
constructor(props, context) {
super(props);
this.state = mapStateToProps(context.store.getState());
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() =>
this.setState(mapStateToProps(this.context.store.getState()))
);
}
shouldComponentUpdate() {
// 判断新老状态是否一样,一样就不更新
if (this.state === mapStateToProps(this.context.store.getState())) {
return false;
}
return true;
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
let actions = bindActionCreators(
mapDispatchToProps,
this.context.store.dispatch
);
return <WrappedComponent {...this.props} {...this.state} {...actions} />;
}
};
};
}

Redux中间件

中间件派发action之后,reducer之前
原理:拦截dispatch,增强dispatch

1
2
3
4
5
6
7
8
9
// 1、缓存老的dispatch
// 2、重写dispatch方法
let oldDispatch = store.dispatch;
store.dispatch = function(action){
console.log('%c prev state','font:bold;color:gray',store.getState());
console.log('%c action', 'font:bold;color:green',action);
oldDispatch(action);
console.log('%c next state','font:bold;color:blue',store.getState());
}

可以理解为面向切面编程,AOP

redux-logger

1
2
3
4
5
6
7
8
9
10
11
12
// getState获取仓库状态
// dispatch用来重新派发动作(这个dispatch就是改造后的最终的dispatch,不是原来的dispatch)
export default function ({getState, dispatch}){
return function(next){ // 代表下一个中间件next
return function(action){ // 动作action
console.log('%c prev state','font:bold;color:gray',getState());
console.log('%c action', 'font:bold;color:green',action);
next(action);
console.log('%c next state','font:bold;color:blue',getState());
}
}
}

中间件的原理和koa是一样的

redux-thunk

1
2
3
4
5
6
7
8
9
10
export default function ({getState, dispatch}){
return function(next){ // 代表下一个中间件next
return function(action){ // 动作action
if(typeof action === 'function'){
return action(dispatch, getState);
}
next(action);
}
}
}

redux-promise

1
2
3
4
5
6
7
8
9
10
export default function ({getState, dispatch}){
return function(next){ // 代表下一个中间件next
return function(action){ // 动作action
if(action.then && typeof action.then === 'function'){
return action.then(dispatch);
}
next(action);∂
}
}
}

compose

[拓展]

1
2
3
4
5
6
7
8
let result = add3(add2(add1('ceshi')));
// 等同于
let result = compose(add3, add2, add1)('ceshi');
function compose(...fns){
if(fns.length === 0) return args=>args;
if(fns.length === 1) return fns[0];
return fns.reduce((a,b) => (...args) => (a(b(...args))));
}

applyMiddleware

1
2
3
// let store = createStore(reducer);
let store = applyMiddleware(promise, thunk, logger)(createStore)(reducer);
export default store;

【applyMiddleware 实现】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function applyMiddleware(...middlewares){
// 返回一个方法 传入 (createStore)
return function (createStore){
// 返回一个方法 传入 (reducer)
return function (reduce){
let store = createStore(reducer);
let dispatch;
let middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
middlewares = middlewares.map(middleware => middleware(middlewareAPI));
// store.dispatch传给logger的next参数,结果再传给thunk的next,结果再传给promise的next参数,最终返回pormise的dispatch方法
dispatch = compose(...middlewares)(store.dispatch);
return {
...store,
dispatch
}
}
}
}

redux-persist

redux数据缓存在内存中,页面一刷新就初始化了,可以使用redux-persist持久化缓存数据

1
npm install redux-persist -D

官网的用法 https://github.com/rt2zz/redux-persist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = { // 持久化,持久到哪里?到localStore ajax接口里 localStore.setItem('root:')
key: 'root', // 持久化的key
storage // 存储的位置,默认值
}
// 持久化reducer
const persistedReducer = persistReducer(persistConfig, reducer);
let store = applyMiddleware(promise, thunk, logger)(createStore)(persistedReducer);
// 持久化store
let persistor = persistStore(store);
export {
persistor,
store
}

页面中引入

1
2
3
4
5
6
7
8
9
10
11
import { PersistGate } from 'redux-persist/integration/react'

const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
);
};

redux-saga

npm install redux react-redux redux-saga -D

在 redux-saga 的世界里,Sagas 都用 Generator 函数实现。我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(mySaga)

// render the application

派发动作都是同步的,要异步就使用saga

明天验证:yield call 不但可以调用一个返回promise的函数,还可以调用另一个saga
thunk比较

fork

表示开启了一个新的子进程去处理这个请求,
如果调用了fork则代码不会阻塞在此,而是会向下执行。

1
2
3
4
5
6
// let result = yield call(login, username, password); // 这个请求是阻塞的,只有请求login方法返回之后才能执行下面的代码
const task = yield fork(login, username, password); // 开辟的子进程,所以这里也不能直接拿到值了,逻辑可以放到login方法中写
// 这里task代表新的子任务本身,而非login方法返回值

// 取消任务
yield cancel(task);

[中文官网]https://redux-saga-in-chinese.js.org/
http://www.zhufengpeixun.cn/2020/html/63.4.redux-saga.html

参考文章:

http://www.zhufengpeixun.cn/2020/html/63.1.redux.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html