Эта статья была рецензирована Бруно Мота . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
this
В отличие от языка с жесткой моделью классов, не всегда ясно, на что this
Некоторый другой код тривиально перепривязывает контекст функции, с которой вы работаете, используя ключевое слово new
Function.prototype
Это вводит целый класс запутанных сценариев, и часто вы увидите код, управляемый обратным вызовом, разбросанный по вызовам .bind(this)
Проблема
Поскольку React использует ключевое слово this
Вы, вероятно, привыкли видеть подобный код внутри компонентов React.
this.setState({ loading: true });
fetch('/').then(function loaded() {
this.setState({ loading: false });
});
Этот код приводит к TypeError
this.setState is not a function
Это связано с тем, что при вызове обратного вызова для обещания внутренний контекст функции изменяется, и this
Давайте посмотрим, как мы можем предотвратить это.
Варианты
Некоторые из этих альтернатив являются старыми методами, которые использовались в Javascript в течение многих лет, другие специфичны для React, а некоторые даже не будут работать в браузерах, но мы все равно их исследуем.
1. Псевдоним это
Этот подход существует намного дольше, чем React, и он предусматривает создание второй ссылки на this
var component = this;
component.setState({ loading: true });
fetch('/').then(function loaded() {
component.setState({ loading: false });
});
Этот подход облегчен и очень прост для понимания новичками (хотя может быть неясно, почему вы это сделали). Это дает вам визуальную гарантию того, что вы будете ссылаться на правильный контекст.
Такое ощущение, что вы работаете против семантики самого языка, но это простое решение, и оно работает хорошо.
2. Привязать это
Следующая опция, которую мы имеем, включает в себя введение правильного контекста в нашу функцию обратного вызова во время выполнения.
this.setState({ loading: true });
fetch('/').then(function loaded() {
this.setState({ loading: false });
}.bind(this));
Все функции в Javascript имеют метод связывания , который позволяет вам указать значение для this
Как только функция «связана», контекст не может быть переопределен, а это означает, что у нас есть гарантия, что this
Этот подход немного сложнее для понимания другими программистами, и если вы работаете с глубоко вложенным асинхронным кодом, то вам придется помнить о необходимости связывать каждую функцию по ходу работы.
3. Методы реагирования компонентов
React позволяет вам определять произвольные методы в ваших классах компонентов, и эти методы автоматически связываются с правильным контекстом для this
React.createClass
Это позволяет вам переместить код обратного вызова на ваш компонент.
React.createClass({ componentWillMount: function() { this.setState({ loading: true }); fetch('/').then(this.loaded); }, loaded: function loaded() { this.setState({ loading: false }); } });
, а.bind(this)
Это может быть очень элегантным решением, если вы не выполняете много работы со своим компонентом (вероятно, вам тоже не следует!). Это позволяет вам использовать именованные функции, выравнивать ваш код и забыть о правильном контексте. Фактически, если вы попытаетесь this
bind (): вы связываете метод компонента с компонентом. React делает это автоматически для вас высокопроизводительным способом, поэтому вы можете безопасно удалить этот вызов.
Важно помнить, что это автоматическое связывание не относится к классам ES2015 . Если вы используете их для объявления своих компонентов, вам придется использовать одну из других альтернатив.
4. ES2015 Стрелки
Спецификация ES2015 представляет синтаксис функции стрелки для написания функциональных выражений. Помимо того, что они являются более краткими, чем выражения регулярных функций, они также могут иметь неявный возврат и, что наиболее важно, они всегда используют значение this.setState({ loading: true });
fetch('/').then(() => {
this.setState({ loading: false });
});
(anonymous function)
Независимо от того, сколько уровней вложенности вы используете, функции стрелок всегда будут иметь правильный контекст.
К сожалению, мы потеряли способность называть нашу функцию. Это затрудняет отладку, поскольку трассировки стека, ссылающиеся на эту функцию, будут помечены как const loaded = () => {
this.setState({ loading: false });
};
// will be compiled to
var _this = this;
var loaded = function loaded() {
_this.setState({ loading: false });
};
Если вы используете компилятор, такой как Babel, для преобразования кода ES2015 в ES5, то вы обнаружите, что есть некоторые интересные качества, о которых следует знать.
- В некоторых случаях компилятор может определить имя функции, если оно было присвоено переменной.
- Компилятор использует псевдоним Этот подход для поддержания контекста.
::
5. ES2016 Bind Синтаксис
В настоящее время есть предложение по синтаксису привязки ES2016 (ES7) , который вводит map
Оператор связывания ожидает значение с левой стороны и функцию с правой стороны, этот синтаксис связывает функцию RHS, используя LHS в качестве значения для этого.
Возьмите эту реализацию function map(f) {
var mapped = new Array(this.length);
for(var i = 0; i < this.length; i++) {
mapped[i] = f(this[i], i);
}
return mapped;
}
map
В отличие от lodash, мы не обязаны передавать данные в качестве аргумента, что позволяет нам писать код, который делает [1, 2, 3]::map(x => x * 2)
// [2, 4, 6]
[].map.call(someNodeList, myFn);
// or
Array.from(someNodeList).map(myFn);
Когда-нибудь надоело использовать такой код?
someNodeList::map(myFn);
Этот оператор позволит вам использовать функцию map непосредственно на массивоподобных структурах.
this.setState({ loading: true });
fetch('/').then(this::() => {
this.setState({ loading: false });
});
Мы также можем использовать этот синтаксис в наших компонентах React.
.bind(this)
Я буду первым, кто признает, что этот синтаксис немного страшен.
Хотя об этом операторе интересно знать, в этом контексте он не особенно полезен. Он страдает от многих из тех же недостатков, что и this
Это может запутать других программистов всех способностей.
Реагирующий компонентный компонент, вероятно, не является будущим оператора связывания, но если вам интересно, взгляните на некоторые замечательные проекты, в которых он используется с большим эффектом (например, mori-ext ).
6. Специфический метод
Некоторые функции позволяют передавать явное значение для map
Одним из примеров является items.map(function(x) {
, которая принимает это значение в качестве последнего аргумента.
return <a onClick={this.clicked}>x</a>;
}, this);
this
Хотя это работает, это не согласованный интерфейс. Большинство функций не принимают этот параметр, поэтому вам, вероятно, лучше отдать предпочтение другим опциям, обсуждаемым здесь.
Вывод
Мы видели целый ряд различных способов гарантировать, что вы получите правильный контекст в своих функциях, но какой из них следует использовать?
Если производительность является проблемой, то, возможно, это будет самый быстрый подход. Хотя вы, вероятно, не заметите разницу, пока не начнете работать с десятками тысяч компонентов, и даже в этом случае существует множество узких мест, которые могут возникнуть до того, как это станет проблемой.
Если вас больше беспокоит отладка , используйте один из вариантов, который позволяет вам писать именованные функции, предпочтительно методы компонентов, поскольку они также будут решать некоторые проблемы с производительностью.
В Astral Dynamics мы нашли разумный компромисс между в основном использованием методов именованных компонентов и функций стрелок, но только когда мы пишем очень короткие встроенные функции, которые не вызовут проблем со следами стека. Это позволяет нам писать компоненты, которые понятны для отладки, не теряя при этом краткости функций стрелок, когда они действительно учитываются.
Конечно, это в основном субъективно, и вы можете обнаружить, что предпочитаете сбивать с толку коллег с помощью функций стрелок и синтаксиса привязки. В конце концов, кто не любит читать через кодовую базу, чтобы найти это?
this.setState({ loading: false });
fetch('/')
.then((loaded = this::() => {
var component = this;
return this::(() =>
this::component.setState({ loaded: false });
}).bind(React);
}.bind(null)));