【前端丛林】React这样服用,效果更佳(10)

74 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

前言

哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家再次来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。今天是我们冒险的第十天,今天继续给大家介绍一下React中的Immutable,让我们深入Immutable探寻其中的奥秘,介绍一下React中的性能优化。话不多说,开始我们的冒险之旅吧!

1.React性能优化

1.1 计数器

每次调用setState的时候组件都会刷新

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Counter extends Component {
    state = { counter: { number: 0 } }
    handleClick = () => {
        let amount = this.amount.value ? Number(this.amount.value) : 0;
        this.state.counter.number = this.state.counter.number + amount;
        this.setState(this.state);
    }
    shouldComponentUpdate(nextProps, nextState) {
        return true;
    }
    render() {
        console.log('render');
        return (
            <div>
                <p>{this.state.number}</p>
                <input ref={input => this.amount = input} />
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
ReactDOM.render(
    <Caculator />,
    document.getElementById('root')
)

1.2 深度克隆+浅比较

  • 可以通过浅比较判断是否需要刷新组件
  • 浅比较要求每次修改的时候都通过深度克隆每次都产生一个新对象
import _ from 'lodash';
handleClick = () => {
    let amount = this.amount.value ? Number(this.amount.value) : 0;
    let state = _.cloneDeep(this.state);
    state.counter.number = this.state.counter.number + amount;
    this.setState(state);
}
shouldComponentUpdate(nextProps, nextState) {
    for (const key in nextState) {
        if (this.State[key] !== nextState[key]) {
            return true;
        }
    }
    return false;
}

1.3 深比较

  • 可以通过深度比较的方式判断两个状态的值是否相等
  • 这样做的话性能非常低
shouldComponentUpdate(nextProps, prevState) {
  return !_.isEqual(prevState, this.state);
}

1.4 immutable

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { is, Map } from 'immutable';
class Caculator extends Component {
    state = {
        counter: Map({ number: 0 })
    }
    handleClick = () => {
        let amount = this.amount.value ? Number(this.amount.value) : 0;
        let counter = this.state.counter.update('number', val => val + amount);
        this.setState({ counter });
    }
    shouldComponentUpdate(nextProps = {}, nextState = {}) {
        if (Object.keys(this.state).length !== Object.keys(nextState).length) {
            return true;
        }
        for (const key in nextState) {
            if (!is(this.state[key], nextState[key])) {
                return true;
            }
        }
        return false;
    }
    render() {
        return (
            <div>
                <p>{this.state.counter.get('number')}</p>
                <input ref={input => this.amount = input} />
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
ReactDOM.render(
    <Caculator />,
    document.getElementById('root')
)

2.redux+immutable手工实现

我们试着用redux+immutable手工实现一下~

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider, connect } from 'react-redux'
import immutable, { is, Map } from 'immutable';
import PureComponent from './PureComponent';
const ADD = 'ADD';
const initState = Map({ number: 0 });
function counter(state = initState, action) {
    switch (action.type) {
        case ADD:
            return state.update('number', (value) => value + action.payload);
        default:
            return state
    }
}
const store = createStore(counter);
class Caculator extends PureComponent {
    render() {
        return (
            <div>
                <p>{this.props.number}</p>
                <input ref={input => this.amount = input} />
                <button onClick={() => this.props.add(this.amount.value ? Number(this.amount.value) : 0)}>+</button>
            </div>
        )
    }
}
let actions = {
    add(payload) {
        return { type: ADD, payload }
    }
}
const ConnectedCaculator = connect(
    state => ({ number: state.get('number') }),
    actions
)(Caculator)
ReactDOM.render(
    <Provider store={store}><ConnectedCaculator /></Provider>,
    document.getElementById('root')
)

3.redux-immutable中间件

我们试着用redux-immutable中间件来体验一下~

import { combineReducers } from 'redux-immutable';
function combineReducers(reducers) {
    return function (state = Map(), action) {
        let newState = Map();
        for (let key in reducers) {
            newState = newState.set(key, reducers[key](state.get(key), action));
        }
        return newState;
    }
}
let reducers = combineReducers({
    counter
});
const ConnectedCaculator = connect(
    state => {
        return ({ number: state.getIn(['counter', 'number']) })
    },
    actions
)(Caculator)

4.react-router-redux使用

我们试着用react-router-redux来体验一下~

import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { combineReducers } from 'redux-immutable';
import createHistory from "history/createBrowserHistory";
import { Route } from "react-router";
import { Map } from 'immutable';
import {
    ConnectedRouter,
    routerMiddleware,
    push,
    LOCATION_CHANGE
} from "react-router-redux";
const initialRouterState = Map({
    location: null,
    action: null
});
export function routerReducer(state = initialRouterState, { type, payload = {} } = {}) {
    if (type === LOCATION_CHANGE) {
        const location = payload.location || payload;
        const action = payload.action;
        return state
            .set('location', location)
            .set('action', action);
    }
    return state;
}
const history = createHistory();
const middleware = routerMiddleware(history);
const store = createStore(
    combineReducers({
        router: routerReducer
    }),
    applyMiddleware(middleware)
);
window.push = push;
window.store = store;
let Home = () => <div>Home</div>
let About = () => <div>About</div>
let Topics = () => <div>Topics</div>
ReactDOM.render(
    <Provider store={store}>
        <ConnectedRouter history={history}>
            <div>
                <Route exact path="/" component={Home} />
                <Route path="/about" component={About} />
                <Route path="/topics" component={Topics} />
            </div>
        </ConnectedRouter>
    </Provider>,
    document.getElementById("root")
);

5.react-router-redux实现

我们试着用react-router-redux来实现一下~

import React, { Component } from "react";
import ReactDOM from "react-dom";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import createHistory from "history/createBrowserHistory";
import { Router, Route } from "react-router";
import { Link } from "react-router-dom";
import PropTypes from 'prop-types';
// import {
// ConnectedRouter,
// routerReducer,
// routerMiddleware,
// push
// } from "react-router-redux";
const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD';
const LOCATION_CHANGE = 'LOCATION_CHANGE';
var initialRouteState = {
    location: null
}
class ConnectedRouter extends Component {
    static contextTypes = {
        store: PropTypes.object
    };
    handleLocationChange = (location) => {
        this.store.dispatch({
            type: LOCATION_CHANGE,
            payload: location
        });
    }
    componentWillMount() {
        this.store = this.context.store;
        this.history = this.props.history;
        history.listen(this.handleLocationChange);
    }
    render() {
        return <Router {...this.props} />
    };
}
function routerReducer(state = initialRouteState, action) {
    let { type, payload } = action;
    if (type === LOCATION_CHANGE) {
        return { ...state, location: payload };
    }
    return state;
}
function routerMiddleware(history) {
    return function () {
        return function (next) {
            return function (action) {
                if (action.type !== CALL_HISTORY_METHOD) {
                    return next(action);
                }
                var _action$payload = action.payload,
                    method = _action$payload.method,
                    args = _action$payload.args;
                history[method].apply(history, args);
            };
        };
    };
}
//push
function push(...args) {
    return {
        type: CALL_HISTORY_METHOD,
        payload: { method: 'push', args: args }
    };
}
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory();
// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history);
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
    combineReducers({
        router: routerReducer
    }),
    applyMiddleware(middleware)
);
window.push = push;
window.store = store;
// Now you can dispatch navigation actions from anywhere!
// store.dispatch(push('/foo'))
let Home = () => <div>Home</div>
let About = () => <div>About</div>
ReactDOM.render(
    <Provider store={store}>
        {/* ConnectedRouter will use the store from Provider automatically */}
        <ConnectedRouter history={history}>
            <div>
                <Link to="/">Home</Link>
                <Link to="/about">About</Link>
                <Route exact path="/" component={Home} />
                <Route path="/about" component={About} />
            </div>
        </ConnectedRouter>
    </Provider>,
    document.getElementById("root")
);

结尾

好啦,这期的前端丛林大冒险先到这里啦!这期我们深入了解了Immutable,也介绍了一下React的性能优化,相信让大家对Immutable有了更深入的了解,但是这期内容比较干不容易理解,大家一定要好好啃下来嚼烂嚼透。希望大家可以好好品尝并消化,迅速升级,接下来我们才更好地过五关斩六将!好啦,我们下期再见。拜拜!