Это 104-я оригинальная статья без воды.Если вы хотите получить больше оригинальных статей, выполните поиск в официальном аккаунте и подпишитесь на нас~ Эта статья была впервые опубликована в блоге Zhengcaiyun:Изучите неизменяемость за 15 минут
1. Что такое неизменное?
Неизменяемые данные — это данные, которые после создания не могут быть изменены. Любая модификация, добавление или удаление объекта Immutable возвращает новый объект Immutable. Основной принцип заключается в использовании Persistent Data Structure, то есть мы будем получать новую версию после каждой модификации, а старую версию можно сохранить нетронутой, то есть при использовании старых данных для создания новых данных мы должны следить за тем, чтобы старые данные доступны и неизменны. В то же время, чтобы избежать потери производительности, вызванной повторным копированием всех узлов deepCopy, Immutable использует Structural Sharing, то есть для частей, которые не были изменены в этой операции, мы можем напрямую скопировать соответствующие старые узлы, что на самом деле это разделение структуры.
2. Каковы преимущества Immutable?
2.1 Уменьшите сложность и избегайте побочных эффектов
В Javascript все объекты являются ссылочными типами.В сценарии передачи данных по ссылке будет несколько переменных, указывающих на один и тот же адрес памяти, что приведет к неконтролируемым побочным эффектам, как показано в следующем коде:
let obj1 = { name: '张三' };
let obj2 = obj1;
obj2.name = '李四';
console.log(obj1.name); // 李四
После использования неизменяемого:
import { Map } from 'immutable';
let obj1 = Map({ name: '张三'});
let obj2 = obj1;
obj2.set({name:'李四'});
console.log(obj1.get('name')); // 张三
Когда мы используем Immutable, мы уменьшаем сложность объектов Javascript и делаем наше состояние предсказуемым.
2.2 Сохранить память
Immutable использует механизм совместного использования структуры, поэтому он максимально повторно использует память.
import { Map } from 'immutable';
let obj1 = Map({
name: 'zcy',
filter: Map({age:6})
});
let obj2 = obj1.set('name','zcygov');
console.log(obj1.get('filter') === obj2.get('filter')); // true
// 上面 obj1 和 obj2 共享了没有变化的 filter 属性
2.3 Легко вернуться назад
Immutable будет создавать новый объект при каждом его изменении, а сам объект остается неизменным, тогда запись об изменениях может быть сохранена, а состояние приложения становится контролируемым и отслеживаемым, что удобно для реализации функций отмены и повтора, см. следующий пример кода:
import { Map } from 'immutable';
let historyIndex = 0;
let history = [Map({ name: 'zcy' })];
function operation(fn) {
history = history.slice(0, historyIndex + 1);
let newVersion = fn(history[historyIndex]);
// 将新版本追加到历史列表中
history.push(newVersion);
// 记录索引,historyIndex 决定我们是否有撤销和重做
historyIndex++;
}
function changeHeight(height) {
operation(function(data) {
return data.set('height', height);
});
}
// 判断是否有重做
let hasRedo = historyIndex !== history.length - 1;
// 判断是否有撤销
let hasUndo = historyIndex !== 0;
2.4 Функциональное программирование
Immutable сам по себе является концепцией функционального программирования, чистое функциональное программирование больше подходит для фронтенд-разработки, чем для объектно-ориентированного. Потому что до тех пор, пока ввод непротиворечив, вывод должен быть непротиворечивым, и компоненты, разработанные таким образом, легче отлаживать и собирать.
2.5 Богатый API
Immutable реализует полный набор Persistent Data Structure и предоставляет множество простых в использовании типов данных. картинаCollection
,List
,Map
,Set
,Record
,Seq
, а также ряд методов для управления ими, включая сортировку, фильтрацию, группировку данных, реверсирование, сглаживание, создание подмножеств и т. д. Конкретные API см.официальная документация
3. Как использовать Immutable в реакции
Мы все знаем, что обновление родительского компонента в React приведет к повторному рендерингу дочернего компонента.Когда мы передаем компоненту только один уровень свойств и состояния, мы можем использовать его напрямую. React.PureComponent, что автоматически поможет нам выполнять неглубокие сравнения, тем самым контролируя возвращаемое значение shouldComponentUpdate .
Однако при передаче более одного уровня реквизита или состояния или при передаче типов Array и Object неглубокие сравнения завершаются ошибкой. Конечно, мы также можемshouldComponentUpdate()
использовать в использованииdeepCopy
а такжеdeepCompare
чтобы избежать ненужногоrender()
,ноdeepCopy
а такжеdeepCompare
Как правило, это очень требовательно к производительности. В это время нам нужноImmutable
.
В следующем примере оптимизируется поверхностное сравнение:
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) {
for (const key in nextState) {
if (this.State[key] !== nextState[key]) {
return true;
}
}
return false;
}
}
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')
)
Также можно судить о том, равны ли значения двух состояний с помощью глубокого сравнения, Таким образом, производительность очень низкая.
shouldComponentUpdate(nextProps, prevState) {
// 通过 lodash 中 isEqual 深度比较方法判断两个值是否相同
return !_.isEqual(prevState, this.state);
}
Immutable предоставляет краткий и эффективный метод определения того, изменились ли данные, просто===
а такжеis
Сравните, чтобы узнать, нужно ли его выполнятьrender()
И эта операция имеет почти нулевую стоимость, поэтому она может значительно улучшить производительность. после редактированияshouldComponentUpdate
Такова, что:
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;
}
// 使用 immutable.is 来进行两个对象的比较
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')
)
Immutable.is
сравнивает два объектаhashCode
илиvalueOf
(для объектов JavaScript). Поскольку неизменяемый использует структуру данных Trie внутри, до тех пор, пока два объекта имеютhashCode
равны, стоимость одинакова. Такой алгоритм позволяет избежать глубоких сравнений обхода и работает очень хорошо.
После использования Immutable, как показано на рисунке ниже, при изменении состояния красного узла все узлы в дереве не будут отображаться, а будет отображаться только зеленая часть рисунка:
(Изображение взято из:Подробное объяснение Immutable и практика в React)
так что используйтеImmutable.is
Это может уменьшить повторяющийся рендеринг React и повысить производительность.
4. Использование Immutable с Redux
Вот небольшой пример численного накопления с использованием Immutable с Redux:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
import { createStore, applyMiddleware } from 'redux'
import { Provider, connect } from 'react-redux'
import immutable, { is, Map } from 'immutable';
import PureComponent from './PureComponent';
const ADD = 'ADD';
// 初始化数据时,使用 Map 保证数据不会被轻易修改
const initState = Map({ number: 0 });
function counter(state = initState, action) {
switch (action.type) {
case ADD:
// 返回数据时采用 update 更新对象数据
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')
)
Но из-за встроенного ReduxcombineReducers
и в редуктореinitialState
Все они являются нативными объектами Object, поэтому их нельзя использовать с Immutable нативно.Конечно, мы можем переписать их, переписавcombineReducers
способ достижения совместимости, как показано в следующем коде:
// 重写 redux 中的 combineReducers
function combineReducers(reducers) {
// initialState 初始化为一个 Immutable Map对象
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)
также может быть представленоredux-immutableМетод промежуточного программного обеспечения реализует комбинацию Redux и Immutable.Для приложений, использующих Redux, все ваше дерево состояний должно быть объектами Immutable.JS, и вообще нет необходимости использовать обычные объекты JavaScript.
5. На что обратить внимание при использовании Immutable
- Не смешивайте обычные объекты JS с неизменяемыми объектами (не используйте неизменяемые объекты в качестве свойств объектов Js или наоборот).
- Рассматривайте все дерево состояний Reudx как неизменяемый объект.
- Неизменяемые объекты следует использовать везде, кроме компонентов представления (для эффективности компоненты представления являются чистыми компонентами и не должны использоваться).
- Используйте меньше метода TOJS (этот метод работает очень дорогой, он будет глубоко пройден к данным в объекты JS).
- Ваш селектор всегда должен возвращать неизменяемый объект (т. е. mapStateToProps, потому что react-redux использует неглубокое сравнение, чтобы решить, следует ли переделывать или нет, а с toJs каждый раз возвращается новый объект, т. е. другая ссылка).
6. Резюме
Есть много способов оптимизировать наше приложение React на практике, например, отложенная загрузка компонентов, использование serviceWorks для кэширования состояния приложения, использование SSR и т. д., но прежде чем думать об оптимизации, лучше всего понять, как работают компоненты React, понять различия алгоритм, поймите это только после того, как концепция может быть лучше ориентирована на оптимизацию нашего приложения.
Если в статье что-то не так, поправьте меня.
Ссылка на ссылку:
Подробное объяснение Immutable и практика в React
Immutable Data Structures and JavaScript
Неизменная официальная документация
Рекомендуемое чтение
Реализуйте экспозицию переднего плана и скрытых точек с помощью пользовательских инструкций Vue.
работы с открытым исходным кодом
- Zhengcaiyun интерфейсный таблоид
адрес с открытым исходным кодомwww.zoo.team/openweekly/(На главной странице официального сайта таблоида есть группа обмена WeChat)
Карьера
ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 40 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 30% из них — инженеры полного стека, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.
Если вы хотите измениться, вас забрасывают вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года стажа работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com