持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
前言
哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家再次来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。今天是我们冒险的第十七天,昨天给大家介绍一下Redux的中间件,今天我们继续深入Redux中间件,看看到底有哪些作用,让我们探寻其中的奥秘。话不多说,开始我们的冒险之旅吧!
1.reselect
- 使用Redux管理React应用状态时, mapStateToProps 方法作为从 Redux Store 上获取数据过程中的重要一环,它一定不能有性能缺陷,它本身是一个函数,通过计算返回一个对象,这个计算过程通常是基于Redux Store状态树进行的,而很明显的Redux状态树越复杂,这个计算过程可能就越耗时,我们应该要能够尽可能减少这个计算过程,比如重复在相同状态下渲染组件,多次的计算过程显然是多余的,我们是否可以缓存该结果呢?这个问题的解决者就是 reselect ,它可以提高应用获取数据的性能
- reselect 的原理是,只要相关状态不变,即直接使用上一次的缓存结果
1.1 基本用法
reselect通过创建选择器(selectors),该函数接受一个state参数,然后返回我们需要在mapStateToProps方法内返回对象的某一个数据项,一个选择器的处理可以分为两个步骤
- 接受state参数,根据我们提供的映射函数数组分别进行计算,如果返回结果和上次第一步的计算结果一致,说明命中缓存,则不进行第二步计算,直接返回上次第二步的计算结果,否则继续第二步计算。第一步的结果比较,通常仅仅是===相等性检查,性能是足够的
- 根据第一步返回的结果,计算并返回最终结果
需要注意的是,传入createSelector的映射函数返回的状态应该是不可变的,因为默认缓存命中检测函数使用引用检查,如果使用JavaScript对象,仅改变该对象的某一属性,引用检测是无法检测到属性变更的,这将导致组件无法响应更新
//import { createSelector } from 'reselect'
function createSelector(selector, reducer) {
let lastState;
let value;
return function (state) {
let newState = selector(state);
if (lastState !== newState) {
value = reducer(newState);
lastState = newState;
}
return value;
}
}
const counterSelector = state => state.counter;
const getCounterSelector = createSelector(
counterSelector,
counter => {
console.log('重新计算number')
return counter.number;
}
)
let initialState = {
counter: {
number: 0
}
}
console.log(getCounterSelector(initialState));
console.log(getCounterSelector(initialState));
1.2 案例
1.2.1 src\index.js
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1';
import Counter2 from './components/Counter2';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<><Counter1 /><Counter2 /></>
</Provider>, document.getElementById('root'));
1.2.2 Counter1.js
src\components\Counter1.js
import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect'
import actions from '../store/actions/counter1';
class Counter extends React.Component {
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={this.props.add}>+</button>
<button onClick={this.props.minus}>-</button>
</div>
)
}
}
const getCounterSelector = state => state.get('counter1');
const counterSelector = createSelector(
getCounterSelector,
counter1 => {
console.log('重新计算counter1', counter1);
return counter1;
}
)
export default connect(
state => counterSelector(state),
actions
)(Counter)
1.2.3 Counter2.js
src\components\Counter2.js
import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect'
import actions from '../store/actions/counter2';
class Counter extends React.Component {
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={() => this.props.add(5)}>+</button>
<button onClick={() => this.props.minus(5)}>-</button>
</div>
)
}
}
const getCounterSelector = state => state.get('counter2');
const counterSelector = createSelector(
getCounterSelector,
counter2 => {
console.log('重新计算counter2', counter2)
return counter2;
}
)
export default connect(
state => counterSelector(state),
actions
)(Counter)
1.2.4 src\store\index.js
src\store\index.js
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
let store = applyMiddleware(promise, thunk, logger)(createStore)(reducer);
export default store;
1.2.5 reducers\index.js
src\store\reducers\index.js
//import {combineReducers} from 'redux';
import { combineReducers } from 'redux-immutable';
import counter1 from './counter1';
import counter2 from './counter2';
export default combineReducers({
counter1,
counter2
});
1.2.6 reducers\counter1.js
src\store\reducers\counter1.js
import * as types from '../action-types';
import actions from '../actions/counter';
const initialState = { number: 0 };
export default function (state = initialState, action) {
switch (action.type) {
case types.ADD1:
return { number: state.number + 1 };
case types.MINUS1:
return { number: state.number - 1 };
default:
return state;
}
};
1.2.7 reducers\counter2.js
src\store\reducers\counter2.js
import * as types from '../action-types';
import actions from '../actions/counter';
const initialState = { number: 0 };
export default function (state = initialState, action) {
switch (action.type) {
case types.ADD2:
return { number: state.number + 1 };
case types.MINUS2:
return { number: state.number - 1 };
default:
return state;
}
};
1.2.8 counter1.js
src\store\actions\counter1.js
import * as types from '../action-types';
export default {
add() {
return { type: types.ADD1 }
},
minus() {
return { type: types.MINUS1 }
}
}
1.2.9 actions\counter2.js
src\store\actions\counter2.js
import * as types from '../action-types';
export default {
add() {
return { type: types.ADD2 }
},
minus() {
return { type: types.MINUS2 }
}
}
2.undo
simple undo/redo functionality for redux state containers
import React, { Component, lazy, Suspense } from "react";
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';
import { createStore } from 'redux';
//import undoable from 'redux-undo';
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const UNDO_COUNTER = 'UNDO_COUNTER';
const REDO_COUNTER = 'REDO_COUNTER';
function reducer(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
}
function undoable(reducer, config) {
const { undoType = "@@redux-unto/UNDO", redoType = "@@redux-unto/REDO" } = config;
const initialState = {
past: [],
futer: [],
present: reducer(undefined, {})
}
return function (state = initialState, action) {
const { past, present, future } = state;
switch (action.type) {
case undoType:
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);
return {
past: newPast,
present: previous,
future: [present, ...future]
}
break;
case redoType:
const next = future[0];
const newFuture = future.slice(1);
return {
past: [...past, present],
present: next,
future: newFuture
}
break;
default:
const newPresent = reducer(present, action);
return {
past: [...past, present],
present: newPresent,
future: []
}
}
}
}
let undoableReducer = undoable(reducer, {
undoType: UNDO_COUNTER,
redoType: REDO_COUNTER
});
let store = createStore(undoableReducer);
class Counter extends Component {
constructor(props) {
super(props);
this.state = { value: store.getState() };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ value: store.getState() }));
}
componentWillUnmount() {
this.unsubscribe();
}
undo() {
store.dispatch({ type: UNDO_COUNTER });
}
redo() {
store.dispatch({ type: REDO_COUNTER });
}
add = () => {
store.dispatch({ type: INCREMENT });
}
render() {
const { value, onInrement, onDecrement } = this.props;
//{"past":[],"present":0,"future":[],"history":{"past":[],"present":0,"future":[]}}
console.log(JSON.stringify(this.state.value));
return (
<div>
<p>{this.state.value.present}</p>
<button onClick={this.add}>+</button>
<button onClick={() => store.dispatch({ type: DECREMENT })}>-</button>
<button onClick={this.undo}>undo</button>
<button onClick={this.redo}>redo</button>
</div>
)
}
}
ReactDOM.render(<Counter />, document.querySelector("#root"));
结尾
好啦,这期的前端丛林大冒险先到这里啦!这期我们了解了Redux中间件的基本使用。这期内容不太容易理解,大家一定要多读几遍,要好好啃下来嚼烂嚼透。希望大家可以好好品尝并消化,迅速升级,接下来我们才更好地过五关斩六将!好啦,我们下期再见。拜拜!