React-Redux Introduction

React-Redux is a library for React based on Redux package. And the core idea of React-Redux is to separate the state from the pure components, thereby achieving the purpose of centralized management.

For example, there is a react component we can call a pure component in Counter.js like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
 
export default class Counter extends React.Component {
    render(){
        const { count, onIncreaseClick } = this.props;
        return (
            <div>
                <span>current count is: {count}</span>
                <button onClick={onIncreaseClick}>Increase</button>
            </div>
        );
    }
}

If we want to use React-Redux to control state that mean the props above, we can do like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {connect} from "react-redux";
import Counter from './Counter';
 
function mapStateToProps(state)  {
    return {
        count: state.count
    };
}
 
function mapDispatchToProps(dispatch) {
    return {
        onIncreaseClick: function () {
            dispatch({type: 'increateCount'});
        }
    };
}
 
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

You see, we use connect method from react-redux library, and then pass 2 methods that are mapStateToProps and mapDispatchToProps to connect method, when run connect(), it return a method for wrapping a react component like Counter. So, through the above operation, it manages our component - Counter.

But, there are two questions that what the connect method do for our component and how to manage the react component's props.

Don't worry, wait a minute, we will study it together.

What do the Connect Method

connect's main function one is to repackage the two methods passed in that are mapStateToProps and mapDispatchToProps, we can see the main source code:

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
// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction,
// this function wraps mapToProps in a proxy function which does several things:
//
// * Detects whether the mapToProps function being called depends on props, which
// is used by selectorFactory to decide if it should reinvoke on props changes.
//
// * On first call, handles mapToProps if returns another function, and treats that
// new function as the true mapToProps for subsequent calls.
//
// * On first call, verifies the first result is a plain object, in order to warn
// the developer that their mapToProps function is not returning a valid result.
//
export function wrapMapToPropsFunc(mapToProps, methodName) {
    return function initProxySelector(dispatch, { displayName }) {
        const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
            return proxy.dependsOnOwnProps
                ? proxy.mapToProps(stateOrDispatch, ownProps)
                : proxy.mapToProps(stateOrDispatch)
        }
 
        // allow detectFactoryAndVerify to get ownProps
        proxy.dependsOnOwnProps = true
 
        proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
            proxy.mapToProps = mapToProps
            proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
            let props = proxy(stateOrDispatch, ownProps)
 
            if (typeof props === 'function') {
                proxy.mapToProps = props
                proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
                props = proxy(stateOrDispatch, ownProps)
            }
 
            return props
        }
 
        return proxy
    }
}

Another function is to wrap the component we passed in, we can call the function is High-order components(HOC).

A simple HOC such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import hoistStatics from 'hoist-non-react-statics'
import { Component, createElement } from 'react'
 
export default function wrapWithConnect(WrappedComponent) {
    class HOC extends Component {
        render() {
            const newProps = {
                id: '1'
            };
 
            return createElement(WrappedComponent, {
                ...this.props,
                ...newProps
            });
        }
    }
    return hoistStatics(HOC, WrappedComponent);
}

Why connect need the HOC?

Because it want to manage the pure react component such as Counter.

By using the Redux library, when the state(componets' props) changed, each new component that is wrapped by Connect will compare its own props, whether it is worth updating.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function makeSelectorStateful(sourceSelector, store) {
    // wrap the selector in an object that tracks its results between runs.
    const selector = {
        run: function runComponentSelector(props) {
            try {
                const nextProps = sourceSelector(store.getState(), props)
                if (nextProps !== selector.props || selector.error) {
                    selector.shouldComponentUpdate = true
                    selector.props = nextProps
                    selector.error = null
                }
            catch (error) {
                selector.shouldComponentUpdate = true
                selector.error = error
            }
        }
    };
    return selector
}

If it should update, then this component wrapped of connect will run the setState wrapped with Connect to re-render the real component.

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
initSubscription() {
    if (!shouldHandleStateChanges) return
 
    // parentSub's source should match where store came from: props vs. context. A component
    // connected to the store via props shouldn't use subscription from context, or vice versa.
    const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
    this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
 
    // `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
    // the middle of the notification loop, where `this.subscription` will then be null. An
    // extra null check every change can be avoided by copying the method onto `this` and then
    // replacing it with a no-op on unmount. This can probably be avoided if Subscription's
    // listeners logic is changed to not call listeners that have been unsubscribed in the
    // middle of the notification loop.
    this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
 
onStateChange() {
    this.selector.run(this.props)
 
    if (!this.selector.shouldComponentUpdate) {
        this.notifyNestedSubs()
    else {
        this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
        this.setState(dummyState)// dummyState always equal empty object
    }
}

Obviously, if we use the React-Redux framework to manage pure react components, we don't have to execute the shouldUpdateComponent method in those pure react components.

But how to centrally monitor the state of the state changed?

That's what Redux is responsible for, the HOC of Connect just integrate Redux.

What is Redux

The main idea of Redux is that the web application is a state machine, and the view and state are one-to-one and all states are stored in an object.

we can see a simple Redux is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const createStore = (reducer, initState = {}) => {
    let state = initState;
    let listeners = [];
 
    const getState = () => state;
 
    const dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
    };
 
    const subscribe = (listener) => {
        listeners.push(listener);
        return () => {
            listeners = listeners.filter(l => l !== listener);
        }
    };
 
    dispatch({});
 
    return { getState, dispatch, subscribe };
};

And in React-Redux, it support a Provider component, that we can pass the store ( =createStore() ) to Provider component in top level so that each Component wrapped by connect component can visit the store that created by the Redux, such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Counter from './Counter';
 
function reducerDemo (state, action) {
    switch (action.type) {
        case 'increase':
            return {count: state.count + 1};
        default:
            return state;
    }
}
let store = createStore(reducerDemo, {count: 1});
 
ReactDOM.render(
    <Provider store={store}>
        <Counter/>
    </Provider>,
    document.getElementById('root')
);

Provider component just do one thing that use the context function, pass the store created by Redux to each Component wrapped with Connect.

We can look at the official document on the introduction of Context:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.

Context more details see here https://reactjs.org/docs/context.html (the latest version).

Note: after 16.3 version, React change the Context usage and the Provider of React-Redux use Context before 16.3 version so you can get it click here.

We get the React-Redux how to manage the whole UI project above and get how to update the component wrapped by connect – each component wrapped by connect subscribe Store, when store triggered by store.dispatch, store will run all method subscribed, then each component will compare itself props with store.getState(), if it judge its props changed, it will trigger its setState method, this is a point that JavaScript's Object is a reference type, even if its children changed, its reference is not changed, React-Redux will think it don't change, so we will improve this.

In normally use, we can easily use Spread (...) or Object.assign to copy a Object, but they are shallow copy and if we encapsulate a method for deep copy, we will deep equal in shouldUpdateComponent.

So we should optimize these  what Redux is responsible for.

Immutable Introduction

We can get its main idea from its name that it make the Object immutable.

In React-Redux project, if we don't use the library such as Immutable, our code like this:

1
2
3
4
5
6
7
8
9
10
11
export function selectTreeItem (selectedTreeItem = {}, action) {
  switch (action.type) {
    case SELECT_CONFIG_TREE_SERVICE:
      let newState = Object.assign({}, selectedTreeItem);
      newState.service = action.serviceName;
      delete newState.key;
      return newState;
    default:
      return selectedTreeItem;
  }
}

So cumbersome code above !

How about optimizing these codes used Immutable:

1
2
3
4
5
6
7
8
9
10
export function selectTreeItem (selectedTreeItem = Map(), action) {
  switch (action.type) {
    case SELECT_CONFIG_TREE_SERVICE:
      return selectedTreeItem.set('service', action.serviceName).delete('key');
    default:
      return selectedTreeItem;
  }
}

Obviously, it doesn't have to worry about references and is more readable.

Another advantage that performance improvement.

Since immutable internally uses the Trie data structure for storage, the values are the same as long as the hashCodes of the two objects are equal. Such an algorithm avoids deep traversal comparisons and performs very well. This is very useful for our performance optimization during the specific rendering process.

And It also has a shortcoming that is very contagious and is used throughout the project we recommended. Because somewhere we use Immutable somewhere, somewhere we use plain Object, we need operate it with plain Object by Immutable.toJS() that is bad action for performance.

How to Integrate Immutable with React-Redux

First we should use redux-immutable library instead of Immutable library in React-Redux project.

And then createStore from Redux need 2 params: reduces and initialState, so we should convert them to Immutable type, such as:

1
2
3
4
5
6
7
8
9
10
11
12
import Immutable from 'immutable';
import { combineReducers } from 'redux-immutable';
 
const rootReducer = combineReducers(
    {
        routing: routerReducer,
        uiStates: uiStatesReducer
    }
);
 
const initialState = Immutable.Map();
const store = createStore(rootReducer, initialState);

If you don't pass initialState, redux-immutable will also help you build a global Map as the global state with the initial value of each child reducer when the store is initialized. Of course, this requires that each of your child reducer's default initial values be immutable.

Next, you will find that the access to the react-router-redux is also modified because the routerReducer is not compatible with immutable, so you must customize the reducer:

1
2
3
4
5
6
7
8
9
10
11
12
import Immutable from "immutable";
import { LOCATION_CHANGE } from 'react-router-redux';
 
const initialState = Immutable.fromJS({
    locationBeforeTransitions: null
});
const routerReducer = (state = initialState, action) => {
    if (action.type === LOCATION_CHANGE) {
        return state.set('locationBeforeTransitions', action.payload);
    }
    return state;
};

In addition, let the react-router-redux access the routing information that is mounted on the global state:

1
2
3
4
5
6
7
import {hashHistory} from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
const history = syncHistoryWithStore(hashHistory, store, {
    selectLocationState (state) {
        return state.get('routing').toObject();
    }
});

Finally, we change our pure components' props validate by 'react-immutable-proptypes', such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
 
class App extends Component {
    render() {
        return <div></div>
    }
}
 
App.propTypes = {
    children: PropTypes.node,
    errors: ImmutablePropTypes.list,
    currentUser: ImmutablePropTypes.map
};
 
export default App;

And change the container wrapped the pure component, such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { connect } from 'react-redux';
import App from './App';
 
function mapStateToProps (state) {
    return {
        currentUser: state.get('currentUser'),
        errors: state.get('errors')
    }
}
 
function mapDispatchToProps (dispatch) {
    return {
        actions: ()  => dispatch({})
    }
}
 
export default connect(mapStateToProps, mapDispatchToProps)(App);

That's all, thanks.

Other article about Immutable+Rudux

Using Immutable in React + React-Redux的更多相关文章

  1. react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)

    公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webp ...

  2. immutable.js 在React、Redux中的实践以及常用API简介

    immutable.js 在React.Redux中的实践以及常用API简介 学习下 这个immutable Data 是什么鬼,有什么优点,好处等等 mark :  https://yq.aliyu ...

  3. [React] react+redux+router+webpack+antd环境搭建一版

    好久之前搭建的一个react执行环境,受历史影响是webpack3.10.0和webpack-dev-server2.7.1的环境,新项目准备用webpack4重新弄弄了,旧的记录就合并发布了(在没有 ...

  4. [React] 15 - Redux: practice IM

    本篇属于私人笔记. client 引导部分 一.assets: 音频,图片,字体 ├── assets │ ├── audios │ ├── fonts │ └── images 二.main&quo ...

  5. React、Redux 和 Bootstrap

    使用 React.Redux 和 Bootstrap 实现 Alert 今天,我们来学习使用 React.Redux 和 Bootstrap 实现Alert. 例子 这个例子实现了弹出不同类型信息的功 ...

  6. 实例讲解react+react-router+redux

    前言 总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应 ...

  7. 基于 React.js + Redux + Bootstrap 的 Ruby China 示例 (转)

    一直学 REACT + METEOR 但路由部分有点问题,参考一下:基于 React.js + Redux + Bootstrap 的 Ruby China 示例 http://react-china ...

  8. 基于react+react-router+redux+socket.io+koa开发一个聊天室

    最近练手开发了一个项目,是一个聊天室应用.项目虽不大,但是使用到了react, react-router, redux, socket.io,后端开发使用了koa,算是一个比较综合性的案例,很多概念和 ...

  9. 最新的chart 聊天功能( webpack2 + react + router + redux + scss + nodejs + express + mysql + es6/7)

    请表明转载链接: 我是一个喜欢捣腾的人,没事总喜欢学点新东西,可能现在用不到,但是不保证下一刻用不到. 我一直从事的是依赖angular.js 的web开发,但是我怎么能一直用它呢?看看最近火的一塌糊 ...

  10. 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。

    前言 前几天,我在博文[前端]一步一步使用webpack+react+scss脚手架重构项目 中搭建了一个react开发环境.然而在实际的开发过程中,或者是在对源码的理解中,感受到react中用的最多 ...

随机推荐

  1. lsblk命令详解

    基础命令学习目录首页 lsblk 默认是树形方式显示: $lsblk NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTsda      8:0    0   2. ...

  2. “秒杀”问题的数据库和SQL设计【转载】

    “秒杀”问题的数据库和SQL设计 APRIL 21ST, 2015 问题的来源 完全不考虑一致性的方案 表结构 方案 存在的问题 保证单用户不会重复购买 解决超卖问题 方案 优化 提高性能了 鱼与熊掌 ...

  3. 大华摄像头WEB页面集成

    对于海康.大华的摄像头web页面内的集成方式,根据浏览器类型,通常是采用以下形式: IE内核:调用ocx控件 例如: <object width="100%" height= ...

  4. 5233杨光--Linux第二次实验

    实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou,密码shiyanlou 若不小心登出后,直接刷新页面即可 2. 环境使用 完成实验后可以点击桌面上方的“实验截图”保存并分享实 ...

  5. 私人助手(Alpha)版使用说明

    私人助手使用说明 私人助手这款软件是通过添加事件提醒,提醒你在合适的时间做该做的事,可以选择有多种提醒模式. 目前实现了对事件的添加和提醒功能,软件现在的情况如下: 1.添加事件 2.删除事件 3.事 ...

  6. java 面试 -- 4

    Java面试知识点总结   本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺(阅读本文需要有 ...

  7. DFS--障碍在指定时间会消失

    哈利被困在了一个魔法花园里.魔法花园是一个 N*M 的矩形,在其中有着许多植物, 这些植物会在时刻 K 的倍数消失. 哈利每单位时间都会选择上.下.左.右四 个方向的其中一个进行移动. #includ ...

  8. HDU 1015 Jury Compromise 01背包

    题目链接: http://poj.org/problem?id=1015 Jury Compromise Time Limit: 1000MSMemory Limit: 65536K 问题描述 In ...

  9. Software Defined Networking(Week 1)

    前言 课程名称:软件定义网络 课程地址 Coursera上新的一期还没开课,所以是YouTube. Instructor:Nick Feamster Get Started 对于本次课程,主要的新内容 ...

  10. Unity3D游戏开发——编程实现游戏管理器

    本篇简介 本篇介绍了如何将上一篇的设计模式思想运用到实际的开发过程中. 脚本文件 (1)IGameManager:这个接口存在声明了一个属性(一个拥有getter函数的变量,属性的类型是Manager ...