跳到主要内容

· 阅读需 3 分钟
youniaogu

input 限制输入

1. 问题

很多时候需要对 input 进行输入的限制,比如只允许输入数字、英文字母和中文,不允许输入特殊字符,又或者限制长度等

第一次接触的人可能会简单的在onChangereplace

handleInputChange = evt => {
const { name, value } = evt.target

this.setState({
[name]: value.replace(/[^\u4e00-\u9fa50-9a-zA-Z]/g, ""); //除数字、英文字母和中文外都替换为‘’
})
}

<input name="name" value={name} onChange={this.handleInputChange} />

这样写的话,如果涉及到输入法时会出现错误

因为输入法键入是一个过程,而 React 中每次键入都会触发onChange,过程中输入法的一些键入被replace去除后,就会导致错误的情况

2. 解决方法

Composition Events(组成事件)里面有onCompositionStart onCompositionUpdate onCompositionEnd三个事件分别代表 开始输入合成、输入合成更新、输入合成结束

onCompositionStartonCompositionEndonChange的执行顺序有两种情况:

  1. 正常情况下:onCompositionStart -> onCompositionEnd -> onChange
  2. chrome 或 IOS 版本 10.3 及以后:onCompositionStart -> onChange -> onCompositionEnd

3. 思路

formatter部分单独拿出来,当处在onCompositionStartonCompositionUpdate状态时不触发formatter

import React from "react";

isChrome = () => {
return !!window.chrome;
};
IOSVer = () => {
const match = window.navigator.userAgent.match(/\d[\d]*_\d[_\d]*/i);
if (match) {
return parseFloat(match[0].split("-").join("."));
}
return null;
};

function Input(props) {
this.onComposition = true;
this.isChrome = isChrome();
this.IOSver = IOSVer();

handleInputChange = (evt) => {
const { name, onChange, formatter } = this.props;

if (typeof onChange === "function") {
let value = evt.target.value;
if (this.onComposition && formatter) {
value = formatter(value);
}

onChange({ target: { value, name } });
}
};
const handleComposition = (evt) => {
if (evt.type === "compositionend") {
this.onComposition = true;

if (this.isChrome || (this.IOSVer && this.IOSVer >= 10.3)) {
this.handleInputChange(evt);
}
} else {
this.onComposition = false;
}
};

const { children, onChange, formatter, ...otherProps } = props;

return (
<input
{...otherProps}
onChange={this.handleInputChange}
onCompositionStart={this.handleComposition}
onCompositionUpdate={this.handleComposition}
onCompositionEnd={this.handleComposition}
/>
);
}

export default Input;

注意的点:因为formatter部分是在onChange里执行,所以第二种情况下onCompositionEnd后需要手动触发onChange

· 阅读需 11 分钟
youniaogu

如何实现 Redux

示例 demo:github

注意:为了简单明了的展示主要流程,下面示例中均会省略异常情况的处理。

redux里有四个核心的 api,下面会按顺序介绍并实现它们

  1. combineReducer
  2. compose
  3. createStore
  4. applyMiddlewares

一、combineReducer

1. combineReducer 是什么,有什么用?

因为createStore只能接收一个reducer,如果把所有的reducer都写在一起的话会很臃肿,所以需要将其分割为一个个小的reducer,分别负责一部分的 state,再合并成一个rootReducer

combineReducer的作用就是将多个reducer合并成一个

合并后返回的 reducer 在处理 state 时会进行分发,从上面接收 state 后会将对应部分的 state 分发给 reducer

不理解的话,可以看下边的执行前后代码

2. combineReducer 执行前后代码
export default combineReducer({
list: listReducers,
dict: dictReducers,
detail: detailReducers,
});

//↓↓↓等价于↓↓↓

export default function (state, action) {
return {
list: listReducers(state.list, action),
dict: dictReducers(state.dict, action),
detail: detailReducers(state.detail, action),
};
}
3. 实现 combineReducer

遍历传入的对象拿到所有的 key 值,使用 key 值拿到对应的旧状态,将旧状态和动作传入reducer,获取最新状态并返回,实现起来并不复杂。

function combineReducer(reducers) {
return function (state, action) {
const nextState = {};

for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}

return nextState;
};
}
4. redux 是如何实现 combineReducer?

对比下源码

function combineReducer(reducers) {
const reducerKeys = Object.keys(reducers);
const finalReducers = {};
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if (typeof reducers[key] === "function") {
finalReducers[key] = reducers[key];
}
}
const finalReducerKeys = Object.keys(finalReducers);

return function (state = {}, action) {
let hasChange = false;
const nextState = {};
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[i];
const prevStateForKey = state[key];
const nextStateForKey = reducer(prevStateForKey, action);

nextState[key] = nextStateForKey;
hasChange = hasChange || nextStateForKey !== prevStateForKey;
}
return hasChange ? nextState : state;
};
}

需要注意的点:

  • 除函数以外的值会被过滤,只处理对象里的函数。
  • 在返回函数里会比较前后状态,如果未变化则返回旧状态

二、compose

1. compose 有什么作用?

compose主要用于中间件的整合,接收多个函数传参,返回单个函数。当执行返回函数时,相当于嵌套执行传参的函数。

说起来比较绕,可以看下边的例子理解

compose(a, b, c); // a b c Function

//↓↓↓等价于↓↓↓

function Fn(...args) {
return a(b(c(...args)));
}
2. 如何实现?

思路很简单,可以用循环遍历,返回嵌套函数,也可以用reduce方法

//第一种
function compose(...fns) {
if (fns.length === 0) {
return;
}

let componseFn = (...args) => fns[0](...args);

for (let i = 1; i < fns.length; i++) {
let prevFn = componseFn;
componseFn = (...args) => prevFn(fns[i](...args));
}

return componseFn;
}

//第二种
function compose(...fns) {
if (fns.length === 0) {
return (arg) => arg;
}

if (fns.length === 1) {
return fns[0];
}

return fns.reduce((a, b) => {
return (...args) => a(b(...args));
});
}

三、createStore

createStore最多可以接收三个传参,分别是reducer, preloadedState, enhancer

reducer为必传项,preloadedStateenhancer为非必传,当只传递两个参数时,createStore会检查第二个参数的类型,如何是 function 类型的话按enhancer处理,其他情况会当作preloadedState

  • reducer:响应 action,更新 state
  • preloadState:初始化 state
  • enhancerapplyMiddlewares返回的函数,用于增强dispatch的功能

reducer方法传参里提供初始值也可以达到初始化 state 的效果,但和使用preloadState存在一些区别

preloadState初始化 state 在createStore执行时生效,而函数传参初始值必须要在触发动作并执行 reducer 函数后才会生效,但事实上,createStore在返回结果前会主动触发一个初始化动作(指 action),所以这两者都能达到初始化 state 的效果。

实际开发中初始化数据放在 reducer 里多一些,分割初始状态的同时也能直观看到对应的状态

createStore返回{dispatch<Function>, getState<Function>, subscribe<Function>}

1. 定义 createStore 的传参和输出
function createStore(reducer, enhancer) {
let currentState,
listeners = [];

function dispatch() {}
function subscribe() {}
function getState() {}

return {
dispatch,
subscribe,
getState,
};
}

确认dispatchgetStatesubscribe的作用

  • dispatch:触发一个动作,修改 state,并触发所有的监听函数
  • getState:返回当前的 state
  • subscribe:订阅监听函数,返回取消订阅方法
2. 补全方法
function createStore(reducer, enhancer) {
let currentState,
listeners = [];

function dispatch(action) {
currentState = reducer(currentState, action);

for (let i = 0; i < listeners.length; i++) {
listeners[i]();
}
}
function subscribe(handleStoreChange) {
listeners.push(handleStoreChange);

return function unsubscribe() {
const index = listeners.indexOf(handleStoreChange);

listeners.splice(index, 1);
};
}
function getState() {
return currentState;
}

dispatch({ type: "INIT" }); // 初始化

return {
dispatch,
subscribe,
getState,
};
}

四、applyMiddleware

1. 中间件

传入的中间件必须是规定的柯里化函数({ getState, dispatch }) => next => action

next 为下一个中间件,只有当最后一个中间件时才会是 dispatch

可以简单理解为:

({ getState, dispatch }) =>
(next) =>
action;

//↓↓↓等价于↓↓↓

function middleware({ getState, dispatch }) {
return function (next) {
return function (action) {
// ...do something
return next(action);
};
};
}

执行中间件需要提前传递getState、dispatch,以及绑定 next

这部分代码不多,但复杂程度却是最高的,建议提前看下源码或是了解下洋葱模型。

2. 源码(简化版)
function createStore(reducer, enhancer) {
...

if (typeof enhancer === "function") {
return enhancer(createStore)(reducer);
}

...
}

function applyMiddleware(...middlewares) {
return function(createStore) {
return function(reducer) {
const store = createStore(reducer);

let chain = [];
let dispatch = store.dispatch;

const middlewareAPI = {
dispatch: action => dispatch(action),
getState: store.getState
}
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);

return {
...store,
dispatch
};
};
};
}

createStore传入中间件时,会把自己传给enhancer,在里面进行store的创建,以及dispatch的绑定。

其中最重要最复杂的绑定部分代码,只有两条。

chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
  • 第一:使用map为每个中间绑定dispatch、getState
  • 第二:通过compose合并成一个函数,传递dispatch绑定中间件里的 next,得到新的dispatch

第一句很好理解,第二句也不难,compose上面有讲解,最后剩下一点,那就是 next 的绑定。

3. 绑定 next

中间件定义伪代码。

function middlewareA({ dispatch, getState }) {
return function (next) {
return function (action) {
console.log("Middleware A");
return next(action);
};
};
}
function middlewareB({ dispatch, getState }) {
return function (next) {
return function (action) {
console.log("Middleware B");
next(action);
};
};
}
// middlewares = [middlewareA, middlewareB]

// middlewares.map((middleware) => middleware(middlewareAPI));
function middlewareAAfterMap(next) {
//闭包里保持着{getState, dispatch}
return function (action) {
console.log("Middleware A do something");
return next(action);
console.log("Middleware A finish");
};
}
function middlewareBAfterMap(next) {
//闭包里保持着{getState, dispatch}
return function (action) {
console.log("Middleware B do something");
next(action);
console.log("Middleware B finish");
};
}
// chain = [middlewareAAfterMap, middlewareBAfterMap]

// composedFn = compose(...chain);
function composedFn(...args) {
return middlewareAAfterMap(middlewareBAfterMap(...args));
}

// newDispatch = composedFn(dispatch);
// newDispatch = middlewareAAfterMap(middlewareBAfterMap(dispatch))
function newDispatch(action) {
return middlewareAAfterMap(function (action) {
console.log("Middleware B do something");
dispatch(action);
console.log("Middleware B finish");
});
}
↓↓↓
function newDispatch(action) {
return function (action) {
console.log("Middleware A do something");
console.log("Middleware B do something");
dispatch(action);
console.log("Middleware B finish");
console.log("Middleware A finish");
};
}
4. 绑定顺序和执行顺序

applyMiddleware可以接收多个中间件传参,并从右到左依次绑定中间件,但当dispatch时,中间件的执行顺序却是从左到右(这也是为什么redux-logger要放在最后面)。

为什么执行顺序是相反的,可以通过下面的模型理解

 ———————————
| dispatch |
———————————

↓↓↓绑定中间件↓↓↓

-------------------
| redux-thunk |
| --------------- |
| | redux-logger | |
| | ——————————— | |
| | | dispatch | | |
| | ——————————— | |
| --------------- |
-------------------

绑定中间件相当于在dispatch外包一层又一层的裹上,而当执行中间件时则需要从外到内顺序执行,所以执行顺序和绑定顺序是相反的

5. 实现

光看还不够,只有实际动手后才会明白里面的细节和关键,最后是我的实现。

function applyMiddleware(...middlewares) {
return function({ getState, dispatch }) {
return function(next) {
return compose(
...middlewares.map(middleware => {
return middleware({ getState, dispatch });
})
)(next);
};
};
}


function createStore(reducer, enhancer) {
let dispatch = function(action) {
...
};
const subscribe = function(handleStoreChange) {
...
};
const getState = function() {
...
};

let currentState = {};
let listeners = [];
let dispatchWithMiddleware = dispatch;

dispatch({ type: "INIT" }); // 初始化
if (typeof enhancer === "function") {
dispatchWithMiddleware = enhancer({
dispatch: action => dispatchWithMiddleware(action),
getState
})(dispatch);
}

return {
dispatch: dispatchWithMiddleware,
subscribe,
getState
};
}

写法上有区别,但核心内容上没有区别。

6. 注意

可能有些人已经注意到了奇怪的地方。

//为什么不直接传dispatch
{
dispatch, getState;
}
//而是传递匿名函数
{
dispatch: (action) => dispatchWithMiddleware(action), getState;
}

原因在于有些中间件会使用绑定的dispatch方法,比如redux-thunk,所以要保证传入的dispatch与外部dispatchWithMiddleware保持相同

dispatchWithMiddleware为引用类型,在绑定中间件后又会重新赋值,所以这里最好的解法就是“闭包”,将变量存在内存里维持起来。

所以这里传入函数,是为了触发闭包维持变量。

参考资料

Redux 从设计到源码

图解 Redux 中 middleware 的洋葱模型

· 阅读需 2 分钟
youniaogu

什么是 Reselect

react-redux 性能优化库

Reselect 解决了什么?

前提:在 react-redux 里,每当 state 变化时,为了比较前后的状态,都会调用 mapStateToProps 获取最新的结果

mapStateToProps的逻辑比较重时,将会是一个很大的开销,reselect的目的就是为了解决这些无用的开销。

Reselect 做了什么?

先来看一下reselect的用法:

import { createSelector } from 'reselect';

//createSelector(...inputSelectors | [inputSelectors], resultFunc)
@connect(createSelector(
(state, ownProps) => state.counter,
(counter) => {
return {
counter,
};
},
), {
dispatchAdd,
dispatchReduce,
});

createSelector需要两个参数,inputSelectorsresultFunc

inputSelectors可以为数组,也可以是多个函数,里面返回需要用来计算的字段,这些字段会作为resultFunc方法的传参,resultFunc 则是用来计算最后的props(功能对应之前的mapStateToProps

reselect会保存mapStateToProps的结果,当用来计算的参数未发生改变时(浅比较),resultFunc会直接返回旧的计算结果,从而避免了无用的计算开销

· 阅读需 2 分钟
youniaogu

返回函数的函数

函数里能够返回函数是 js 一个很重要的技巧,能让我们节省很多代码空间

react 里经常需要在map返回的元素里绑定事件,这种时候我们经常会使用这以技巧

handleClick = (index) => {
return () => {
console.log(index);
};
};

list.map((obj, index) => {
return (
<div key={index} onClick={this.handleClick(index)}>
index
</div>
);
});

这一技巧需要消耗资源,因为每次执行都要创建返回的函数,而且有可能使用到闭包。当列表数据特别多时,这种开销会放大,导致 render 速度下降

所以如果不是特别依赖闭包的话,可以把数据挂在 dom 元素上,通过event对象去获取,这样只需要创建一个函数

handleClick = (evt) => {
console.log(evt.currentTarget.dataset.index);
};

list.map((obj, index) => {
return (
<div data-index={index} key={index} onClick={this.handleClick}>
index
</div>
);
});

最后还可以用事件代理再进行优化

handleClick = evt => {
evt.preventDefault();
console.log(evt.target.dataset.index);
};

<div onClick={this.handleClick}>
list.map((obj, index) => {
return (
<div data-index={index} key={index}>
index
</div>
);
});
</div>

题外话

关于evt.targetevt.currentTarget的区别

  • target指的是当前触发事件的元素
  • currentTarget指的是绑定事件的元素

举个例子:

handleClick = (evt) => {
console.log({
target: evt.target,
currentTraget: evt.currentTarget,
});
};
<div class="wrapper" onClick="{this.handleClick}">
<p>dom1</p>
</div>

当点击 dom1 时,target为 p 元素,而currentTarget则是 div 元素,因为事件绑定在它上面

· 阅读需 2 分钟
youniaogu

如何定义 Action type

在写 redux 时,需要定义 action type,往往我们会直接写成字符串,而有些情况则会写成变量再传递过去

那么定义为变量有什么好处和坏处?

1. 好处:能够很好定位拼写错误

// actions.js
const LOAD_USER_LIST = "LOAD_USER_LIST";
const LOAD_USER_INFO = "LOAD_USER_INFO";

// userList.js
import {LOAD_USER_LIST, LOAD_USER_DETAIL} from './actions'
// Attempted import error: 'LOAD_USER_DETAIL' is not exported from './action'
// 这里会提示报错,LOAD_USER_DETAIL不存在

当引入不存在的action变量时应用会编译失败,能够很好的提示我们

export function loadUserInfo(id) {
return {
type: "LOAD_USER_DETAIL", //正确应该是type: "LOAD_USER_INFO",
id,
};
}

action为字符串时,并不会提示报错,但对应的reducersaga部分不会运行,而往往为了找出这种低级错误,会浪费很多时间

2. 坏处:使用时会麻烦一些,需要引入action

// sagas.js | createAction.js | reducers.js
import { LOAD_USER_LIST, LOAD_USER_INFO } from "./actions.js";

3. 思考

大项目逻辑复杂查错不易,action定义为变量更为严谨,可以规避一些低级错误

小项目要求快速开发,action定义为字符串更为直接,能够加快开发效率

很多时候,我们不能一味的模范,而是要思考为什么要这样做,其中的利与弊是什么,适合自己的才是最好的

· 阅读需 8 分钟
youniaogu

如何实现 React-redux

示例 demo:github

注意:为了简单明了的展示主要流程,下面示例中均会省略异常情况的处理。

一、什么是 react-redux?

react-redux是 redux 的中间件,通过 connectProvider这两个 api,将组件需要的状态注入进去,当 redux 里的状态发生改变时,触发相应组件的更新

二、react-redux 是怎么工作的?

先看一个简单的用例

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reducers from "./reducers";
import { createStore } from "redux";
import { Provider } from "react-redux";

const store = createStore(reducers);

ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);

最外层Provider将整个应用包裹住

createStore返回的store通过props传递给Provider

store 并不是 redux 存储的状态,要获得存储的状态必须通过store.getState()

import React, { Component } from "react";
import { dispatchAdd, dispatchReduce } from "./actions";
import { connect } from "react-redux";

class Counter extends component {
render() {
return <div>{this.props.counter}</div>;
}
}

const mapStateToProps = function (state, ownProps) {
return {
counter: state.counter,
};
};

const mapDispatchToProps = function (dispatch, ownProps) {
return {
add: dispatch(dispatchAdd),
reduce: dispatch(dispatchReduce),
};
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

在需要 redux 的状态时,组件通过connect将状态传递给组件

connect 有四个传参

  1. mapStateToProps?: Function 将 state 注入到组件中
  2. mapDispatchToProps?: Function | Object 将 action 注入到组件中
  3. mergeProps?: Function 在前两个传参的返回和 ownProps 作为传参,返回合并后的 props
  4. options?: Object 自定义传入的 context ,是否是需要比较前后状态以及比较的方法,转发 ref 返回原组件还是包装后的组件

大部分情况下我们只使用到前两个方法(本文也只会讲述这两部分)

通过上面用例可以知道

  • Provider负责传递store
  • connect负责接收store,并将state注入到组件中

Provider是通过什么进行store传递? 答案是:Context

什么是Context

简单来说,Context是 React 提供的一种数据传递方式,Context能够自动的自上而下传递数据,不需要像props那样手动定义传递的数据,是一个既方便又危险的属性。'

如果想详细了解Context的详细用法和说明,可以去 React 官网查看文档

三、动手

目标:实现connectProvider

connect是函数,接收mapStateToPropsmapDispatchToProps,返回hoc

Provider是组件,接收store并通过Context传递给其他组件

import React, { Component } from "react";
import PropTypes from "prop-types";

const StoreContext = React.createContext();

export function connect(mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) {
return class connect extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
};
}

export class Provider extends Component {
render() {
return (
<StoreContext.Provider value={this.props.store}>
{this.props.children}
</StoreContext.Provider>
);
}
}

Provider.propTypes = {
children: PropTypes.element.isRequired,
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired,
}),
};

上面就是connectProvider最基础的定义

再来是connect接收Context,并通过传入的两个 map 方法将需要的statedispatch注入到组件的props

...

export function connect(mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) {
return class connect extends Component {
static contextType = StoreContext;

constructor(props, context) {
super(props, context);

this.store = props.store || context;
}

render() {
return (
<WrappedComponent
{...this.props}
{...mapStateToProps(this.store.getState(), this.props)}
{...mapDispatchToProps(this.store.dispatch, this.props)}
/>
);
}
};
};
}

...

到这一步,已经将statedispatch注入到组件,但触发action后并不会触发组件render,原因在于state变化后并没有重新渲染组件

所以这一步我们需要监听state的变化,并且在确认变化后,触发组件的render

...

export function connect(mapStateToProps, mapDispatchToProps) {
return function wrapWithComponent (WrappedComponent) {
return class Connect extends Component {
static contextType = StoreContext;

constructor(props, context) {
super(props, context);

this.store = props.store || context;
this.state = {
storeState: this.store.getState(),
};
}

componentDidMount() {
if (!this.unSubscribe) {
this.unSubscribe = this.store.subscribe(
this.handleStoreChange.bind(this)
);
}
}

componentWillUnmount() {
if (this.unSubscribe) {
this.unSubscribe();
}
this.clearCache();
}

handleStoreChange() {
if (!this.unSubscribe) {
return;
}

const prevStoreState = this.state.storeState;
const storeState = this.store.getState();

if (prevStoreState !== storeState) {
this.setState({ storeState });
}
}

clearCache() {
this.store = null;
this.unSubscribe = null;
}

render() {
const storeState = this.state.storeState;

return (
<WrappedComponent
{...this.props}
{...mapStateToProps(storeState, this.props)}
{...mapDispatchToProps(this.store.dispatch, this.props)}
/>
);
}
};
};
}

...

在订阅后,每当触发一个action,就会触发handleStoreChange方法,里面将prevStoreStatestoreState进行比较,只有触发的reducers返回原本的state时才会是 true

每当action触发state变化时,每个WrappedComponent都会render,即使改变的state组件没有用到,所以接下来需要做一些优化

...

shouldComponentUpdate(prevProps, prevState) {
return !(
shallowEqual(this.state.storeState, prevState.storeState) ||
shallowEqual(
mapStateToProps(this.state.storeState, this.props),
mapStateToProps(prevState.storeState, prevProps)
)
);
}

...

shouldComponentUpdate中,根据前后的storeStatemapStateToProps的结果来决定是否需要render

pic1

可以看到点击 add 按钮后,只触发了counterrender,达到了预期的效果(这里render两次是因为React.StrictMode,详细可以看issues

到这步简易react-redux算是完成了

思考

最后总结一下存在的一些问题:

  • mapDispatchToProps只能传递function类型

    • 实际开发中大多数都会传递action creater而不是action,所以支持object类型是挺重要的一点
  • 更新逻辑不应该在shouldComponentUpdate

    • 这一点在React Redux with Dan Abramov视频里有提到,最初设计时更新逻辑写在shouldComponentUpdate里,但在特殊情况下可能会导致最后渲染出来不是最新的state(因为本人英语不好,没能理解错误发生的情景,所以就不写出来怕误导大家,视频内 24:59~27:30)

    • 问题的根本原因在于 react 是异步渲染,对于多个setState会合并成一个去执行,而 redux 修改state却是同步,当dipatch时,redux 里的state会立即改变,异步与同步之间的差异导致了问题

    • 讲述问题的同时也介绍了新的解决方案,将更新逻辑放在render里,connect会保留React.createElement生成的element对象,在一系列判断后返回新旧element对象(如果 render 里返回旧的 element 对象,将不会重新渲染)

  • 更新逻辑里每个mapStateToProps都必须重新执行一遍

    • 当 mapStateToProps 的计算很重时,每次渲染都要话费大量时间,这是 react-redux 本身的缺陷(reselect解决了这个问题)

参考资料

React Redux with Dan Abramov

The History and Implementation of React-Redux