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