React進階篇2

小圖子發表於2019-02-28

1. 可共享可變狀態是萬惡之源

let objA = { name: 'zfpx' };
let objB = objA;
objB.name = '9';
console.log(objA.name);
複製程式碼

2. 什麼是 Immutable

  • Immutable Data 就是一旦建立,就不能再被更改的資料。對 Immutable 物件的任何修改或新增刪除操作都會返回一個新的 Immutable 物件
  • Immutable 實現的原理是 Persistent Data Structure(持久化資料結構),也就是使用舊資料建立新資料時,要保證舊資料同時可用且不變 同時為了避免 deepCopy 把所有節點都複製一遍帶來的效能損耗
  • Immutable 使用了 Structural Sharing(結構共享),即如果物件樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享
  • immutable-js immutablejs

3.Immutable類庫

內部實現了一套完整的 Persistent Data Structure,還有很多易用的資料型別。像 Collection、List、Map、Set、Record、Seq

3.1 Map

方法 作用
isMap 判斷是否是Map
clear 清空值
set 設定值
delete 刪除值
update 更新值
merge 合併值
setIn 設定值
deleteIn 刪除值
updateIn 更新值
mergeIn 合併值
get 獲取值
getIn 獲取值
keys key的陣列
values value的陣列
entries entry的陣列
toJS 轉成普通JS物件
toObject 轉成普通物件
toJSON 轉成JSON物件
toArray 轉成陣列
let obj1 = immutable.Map({ name: 'zfpx', age: 8 });
let obj2 = obj1.set('name', 'zfpx2');
let obj3 = obj2.update('age', x => x + 1);
let obj4 = obj3.merge({ home: '北京' });
console.log(obj1, obj2, obj3, obj4);


let obj6 = immutable.fromJS({ user: { name: 'zfpx', age: 8 }, 'k': 'v' });
let obj7 = obj6.setIn(['user', 'name'], 'zfpx2');
let obj8 = obj7.updateIn(['user', 'age'], x => x + 1);
let obj9 = obj8.mergeIn(["user"], { home: '北京' });
console.log(obj6, obj7, obj8, obj9);

console.log(obj6.get('user'));

console.log(obj6.getIn(['user', 'name']));
console.log(...obj6.keys());
console.log(...obj6.values());
console.log(...obj6.entries());

var map1 = immutable.Map({ name: 'zfpx', age: 9 });
var map2 = immutable.Map({ name: 'zfpx', age: 9 });
assert(map1 !== map2);
assert(Object.is(map1, map2) === false);
assert(immutable.is(map1, map2) === true); 
複製程式碼

3.2 List

方法 作用
isList 判斷是否是List
size 統計個數
push 新增
pop 彈出最後一個
update 更新
delete 刪除指定元素的陣列 delete(2)
insert 插入指定元素的陣列 insert(2)
clear 清空陣列 clear()
concat 合併
map 對映
filter 過濾
get 獲取
find 查詢
includes 判斷包含
last 最後一個
reduce 計算總和
count 統計個數
let immutable = require('immutable');
let arr1 = immutable.fromJS([1, 2, 3]);
console.log(arr1.size);
let arr2 = arr1.push(4);
console.log(arr2);
let arr3 = arr2.pop();
console.log(arr3);
let arr4 = arr3.update(2, x => x + 1);
console.log(arr4);
let arr5 = arr4.concat([5, 6]);
console.log(arr5);
let arr6 = arr5.map(item => item * 2);
console.log(arr6);
let arr7 = arr6.filter(item => item >= 10);
console.log(arr7);
console.log(arr7.get(0));
console.log(arr7.includes(10));
console.log(arr7.last());
let val = arr7.reduce((val, item) => val + item, 0);
console.log(val);
console.log(arr7.count());

// 0 1 2 3 4 5  => 3 4 5 => 6 8 10 => 6 8 10 => 8 10 => 18
let ret = immutable.Range(1, 6).skip(3).map((n) => n * 2).filter((n) => n % 2 == 0).take(2).reduce((a, b) => a + b, 0);
console.log(ret);
複製程式碼

4.Immutable優勢

4.1 降低複雜度

let obj = {age:8};
handle(obj);
console.log(obj.age);
複製程式碼

4.2 節省記憶體

let Immutable=require('immutable');
let p1=Immutable.fromJS({
    name: 'zfpx',
    home:{name:'beijing'}
});
let p2 = p1.set('name','zfpx2');
console.log(p1.get('home')== p2.get('home'));
複製程式碼

4.3 方便回溯 只要把每次的狀態都放在一個陣列中就可以很方便的實現撤銷重做功能

5. React效能優化

5.1 計數器

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash';
class Caculator extends Component {
    state = {
        number: 0
    }
    handleClick = () => {
        let amount = this.amount.value ? Number(this.amount.value) : 0;
        this.state.number = this.state.number + amount;
        this.setState(this.state);
    }
    shouldComponentUpdate(nextProps, prevState) {
        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')
)
複製程式碼

5.2 深度克隆

handleClick = () => {
        let amount = this.amount.value ? Number(this.amount.value) : 0;
        let state = _.cloneDeep(this.state);
        state.number = this.state.number + amount;
        this.setState(state);
}
複製程式碼

5.3 深比較

 shouldComponentUpdate(nextProps, prevState) {
        return !_.isEqual(prevState, this.state);
 }
複製程式碼

5.4 immutable

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash';
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;
        this.setState({ counter: this.state.counter.update('number', val => val + amount) });
    }
    shouldComponentUpdate(nextProps = {}, nextState = {}) {
        nextState = nextState == null ? {} : nextState;
        const thisProps = this.props || {}, thisState = this.state || {}, nextState = this.state || {};
        if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
            Object.keys(thisState).length !== Object.keys(nextState).length) {
            return true;
        }

        for (const key in nextProps) {
            if (!is(thisProps[key], nextProps[key])) {
                return true;
            }
        }

        for (const key in nextState) {
            if (thisState[key] !== nextState[key] && !is(thisState[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')
)
複製程式碼

6. 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')
)
複製程式碼

7. 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)
複製程式碼

8. 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")
);
複製程式碼

9. 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")
);
複製程式碼

相關文章