Вопрос: Почему в JSX в проекте реакции написание onChange={this.func.bind(this)} более эффективно, чем написание non-bind func = () => {}?
Утверждение: Из-за моего ограниченного уровня я непродуманно или допустил ошибки, пожалуйста, строго укажите, мой брат очень благодарен. Это первая статья моего брата.Если вы не понимаете негласных правил, просто скажите мне. Младший брат поделится завтра, а после окончания обмена продолжит совершенствоваться.
Я случайно видел этот вопрос раньше. Говорят, что это вопрос уровня Али p5-p6. Давайте сначала рассмотрим этот вопрос. На первый взгляд, он должен изучить глубину понимания реакции. На самом деле, есть много задействованные тестовые точки: привязка, функция стрелки, React имеет различные методы привязки этого, преимущества и недостатки, подходящие сценарии, наследование классов, цепочку прототипов и т. д., поэтому он очень всеобъемлющий.
Наша тема сегодня - это тема, чтобы обобщить соответствующие точки знаний, здесь я будуСосредоточьтесь на анализе второй схемы привязки в заголовке.
Различия между пятью этими схемами привязки
Вариант один: ract.createClass
Это то, как старая версия реагирует на объявление в этой версии, без введения концепции класса, поэтому для создания класса компонентов (конструктор) таким образом Класс ES6 по сравнению с CreateClass, удалил две точки: один - это смесин, это автоматическое связывание. Первый может быть использована альтернатива HOC, последний не полностью, потому что FB считают, что грамматики и JS производят, чтобы избежать путаницы, его удалено. Используя этот метод, нам не нужно беспокоиться об этом, он будет автоматически связан с корпусом экземпляра компонентов, но это API устарено, поэтому только нужно знать.
const App = React.createClass({
handleClick() {
console.log(this)
},
render() {
return <div onClick={this.handleClick}>你好</div>
}
})
Вариант 2: использовать привязку в функции рендеринга
class Test extends Component {
handleClick() {
console.log(this)
}
render() {
return <div onClick={this.handleClick.bind(this)}></div>
}
}
Вариант 3. Используйте функции стрелок в функции рендеринга.
class Test extends Component {
handleClick() {
console.log(this)
}
render() {
return <div onClick={() => this.handleClick()}></div>
}
}
Эти две схемы лаконичны и понятны, и параметры можно передавать, но есть и потенциальные проблемы с производительностью:вызовет ненужный рендеринг
Мы часто видим эти сценарии в коде:Чтобы увидеть больше демонстрационных примеров, нажмите
class Test extends Component {
render() {
return <div>
<Input />
<button>添加<button>
<List options={this.state.options || Immutable.Map()} data={this.state.data} onSelect={this.onSelect.bind(this)} /> // 1 pureComponent
</div>
}
}
Сценарий 1. Используйте пустой объект/массив в качестве восходящего решения, чтобы избежать ошибки во время выполнения, когда параметры не имеют данных. Сценарий 2: Используйте функции стрелок, чтобы связать это.
Возможно, в некоторых сценариях, где вам не нужно заботиться о производительности, эти два способа написания не причинят особого вреда.PureComponent
для оптимизации производительности рендеринга
Здесь используется реакцияshallowEqualДля сравнения первого уровня в настоящее время мы можем сосредоточиться на этих данных (независимо от того, изменились ли данные и, следовательно, повлияли ли они на рендеринг), но параметры, которые мы игнорируем, onSelect напрямую вызовут сбой PureComponent, но мы не можем найти причина неудачи оптимизации.
И предположим, что наши основные данныеImmutable
Да, это на самом деле оптимизирует нашу производительность, связанную с различиями. Когда данные равны нулю, мы не ожидаем повторного рендеринга.Однако, когда наш компонент Test имеет обновление состояния, которое запускает повторный рендеринг Test, и рендеринг выполняется в это время, List все равно будет повторно рендериться. Причина в том,我们每次执行render,传递给子组件的options,onSelect是一个新的对象/函数。
Таким образом, когда мелкое равенство будет выполнено, оно подумает, что есть обновление, поэтому компонент списка будет обновлен.
Это место также имеет множество решений:
- Не делайте итоговую строку непосредственно в функции рендеринга или используйте тот же источник данных, на который ссылаются.
- Для функции слушателя событий мы можем сделать привязку заранее, использовать схему 4 или 5 или последний хук (useCallback, useMemo)
const onSelect = useCallback(() => {
... //和select相关的逻辑
}, []) // 第二个参数是相关的依赖,只有依赖变了,onSelect才会变,设置为空数组,表示永远不变
Вариант 4: использовать привязку в конструкторе
class Test extends Component {
constrcutor() {
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this)
}
render() {
return <Button onClick={this.handleClick}>测试</Button>
}
}
Это решение рекомендуется React. Оно связывается только один раз, когда создается экземпляр компонента, а затем передается одна и та же ссылка. Второе и третье решения не вызывают отрицательного эффекта.
Но этот способ написания намного громоздче, чем 2 и 3:
1. Если нам не нужно ничего делать в конструкторе, то для того, чтобы сделать привязку функции, нам нужно вручную объявить конструктор;Новый способ записи свойств экземпляра здесь не рассматривается, а присваивание осуществляется непосредственно на верхнем уровне. благодарный@Да хорошо 2012Правильный.
- Для некоторых сложных компонентов (слишком много методов, которые необходимо связать), нам нужно многократно писать эти методы;
- Одни не могут иметь дело с вопросами массового участия (это особенно важно, но также ограничивает свои сценарии использования).
Вариант 5: Определите методы с использованием функций стрелки (свойства класса)
Эта техника опирается наClass Properties
предложение, в настоящее времяstage-2
этапе, если нам нужно использовать эту схему, мы должны установить@babel/plugin-proposal-class-properties
class Test extends Component {
handleClick = () => {
console.log(this)
}
render() {
return <button onClick={this.handleClick}>测试</button>
}
}
Это также вторая схема привязки, упомянутая в наших вопросах интервью. Сначала перечислим преимущества:
- автоматическая привязка
- Нет проблем с производительностью рендеринга, вызванных решениями 2 и 3 (только однократное связывание, новые функции не генерируются);
- Можно упаковать, использовать
params => () => {}
Этот способ написания достигает цели передачи параметров.
Сделаем компиляцию на babel: нажмитеclass-properties(Выберите ES2016 или выше, вам нужно установить этот плагин вручнуюbabel-plugin-transform-class-properties
в сравнении с@babel/plugin-proposal-class-properties
Более интуитивный, первый метод именования — это метод babel6, второй — babel7)
В версии, скомпилированной с плагином, мы видим, что это решение состоит в том, чтобы определить атрибут изменения непосредственно в конструкторе, а затем назначить его как функцию стрелки, чтобы реализовать привязку this, которая выглядит идеально и изысканно. Однако из-за этого написания это означает, что все экземпляры компонентов, созданные этим классом компонентов, будут выделять блок памяти для хранения функции стрелки. Обычный метод, который мы определяем, на самом деле определен в объекте-прототипе и используется всеми экземплярами.Жертва заключается в том, что нам нужно использовать привязку для ручной привязки и создания новой функции.
Давайте посмотрим на полифилл для функции Bind:
if (!Function.prototype.bind) {
... // do sth
var fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
... // do sth
return fBound;
};
}
Если это в браузере, который не поддерживает привязку, фактически после компиляции это эквивалентно оператору в теле функции только что сгенерированной функции:fToBind.apply(...)
.
Посмотрим на разрыв в виде картинки:
Примечание:На рисунке площадь заштрихованного прямоугольника представляет собой память, сэкономленную эталонной функцией, а площадь заштрихованного прямоугольника представляет потребляемую память. Рисунок 1: Использование стрелочных функций для этой привязки. Только функция рендеринга определена в объекте-прототипе и используется всеми объектами-экземплярами. Другое потребление памяти зависит от экземпляра. Рисунок 2: Сделайте эту привязку в конструкторе. И рендеринг, и обработчик определены в объекте-прототипе, а сплошная линия обработчика в экземпляре представляет объем памяти, потребляемый функцией, сгенерированной привязкой.
Если тело нашей функции-обработчика само по себе маленькое, экземпляров не так много и методов привязки не так много. Разница в использовании памяти между двумя схемами невелика, но как только мы要在handler里处理复杂的逻辑
, или组件可能会产生大量的实例
, или组件有大量的需要绑定方法
, выделено первое преимущество.
Если приведенная выше схема привязки this используется только в React, нам может потребоваться только рассмотрение вышеуказанных моментов, но если мы используем вышеуказанный метод для создания некоторых классов инструментов, мы можем обратить внимание не только на это.
Когда дело доходит до классов, каждый может подумать о наследовании классов.Если нам нужно переопределить метод базового класса, запустив следующее, вы обнаружите, что это далеко не то, что вы себе представляли.
class Base {
sayHello() {
console.log('Hello')
}
sayHey = () => {
console.log('Hey')
}
}
class A extends Base {
constructor() {
super()
this.name = 'Bitch'
}
sayHey() {
console.log('Hey', this.name)
}
}
new A().sayHello() // 'Hello'
new A().sayHey() // 'Hey'
Примечание:Мы хотим напечатать «Привет», «Эй, сука», на самом деле печатается: «Привет», «Эй».
Причина очень проста.В конструкторе A мы вызываем super для выполнения конструктора Base и добавления атрибутов экземпляру A. В это время, после выполнения конструктора Base, экземпляр A уже имеет атрибут sayHey, и его значение представляет собой стрелочную функцию, печатает Hey И наш переписанный sayHey на самом деле определен в объекте-прототипе. Таким образом, окончательное выполнение — это метод sayHey, определенный в Base, но не тот же самый метод. Исходя из этого, мы также можем рассуждать о том, что нам делать, если мы сначала выполним sayHey of Base, а затем на этой основе добавим логику? Следующее решение определенно невозможно.
sayHey() {
super.sayHey() // 报错
console.log('get off!')
}
Еще одно слово: некоторые начальники считают, что производительность этого метода не очень хороша.Точка, которую он исследует, это ops/s (сколько компонентов может быть создано в секунду, чем больше, тем лучше), и окончательный вывод
Исходя из этого, мы рассмотрели большинство тестовых сайтов для этого вопроса.Если вы столкнетесь с таким вопросом в следующий раз или придумаете такой вопрос, вы также можете рассмотреть его со следующих точек зрения.
- Мнение интервьюера: 1.1 Прежде чем ответить на этот вопрос, напишите и объясните принципы двух схем.Очевидно, что интервьюер хочет сосредоточиться на втором понимании и на том, что он делает за кулисами. Затем расскажите о некоторых из их обычных плюсов и минусов. 1.2 Чтобы ответить на вопрос об эффективности, первый будет генерировать новую функцию каждый раз, когда он связывается, но код в теле функции небольшой, и самое главное — это обработчик на ссылочном прототипе, который является общим. Но в последнем он будет генерировать функцию на каждом экземпляре, и если экземпляров много, или тело функции большое, или связанных функций слишком много, то занимаемая память явно будет больше, чем у первого.
- Точка зрения интервьюера: реализация тестовой привязки, стратегия тестовой привязки реакции, преимущества и недостатки, стратегия оптимизации производительности теста, стрелочная функция теста, цепочка тестовых прототипов, наследование тестов. Раскинуть, очень широко.
Суммировать:
Так как каждая схема привязки существует, то есть причина ее существования (кроме первой уже в прошлом), но и у нее будут соответствующие недостатки.Нет абсолютного, кто лучше, а кто хуже. Когда мы ее используем, Мы можем сделать это в соответствии с реальной сценой. Ответить на этот вопрос несложно, как сделать так, чтобы интервьюер почувствовал, что вы все знаете, довольно сложно.
Во-вторых, для этой схемы привязкиЕсли вас особенно заботит производительность, пожертвуйте немного кодом и читабельностью: порекомендуйте четвертый и второй, если вы достаточно внимательны, вы также можете использовать второй и третий, но вы должны обратить внимание на то, будет ли вновь сгенерированная функция вызывать избыточный рендеринг; Если вы не хотите работать сверхурочно: Рекомендация 5 (упоминается в статье о том, как пройти справку).