Взгляните на это, определяемое функциейGreeting
Компоненты:
function Greeting() {
return <p>Hello</p>;
}
React также поддерживает определение по классам:
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
(доНедавние хукиДо того, как он появился, это был единственный способ использовать функциональность (например, состояние). )
когда вы собираетесь визуализировать<Greeting />
, вам все равно, как это определено:
// Class or function — whatever.
<Greeting />
ноРеагировать на себязаключается в том, чтобы рассмотреть разницу между ними.
еслиGreeting
это функция, React должен вызывать ее так:
// Your code
function Greeting() {
return <p>Hello</p>;
}
// Inside React
const result = Greeting(props); // <p>Hello</p>
но еслиGreeting
Это класс, реагировать, необходимо использовать его первымnew
Управляйте экземпляром объекта, а затем вызывайте экземпляр объектаrender
метод.
// Your code
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
// Inside React
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
Цель обеих категорий React — получить визуализированный узел (здесь<p>Hello</p>
). Но точные шаги зависят от того, как вы определяетеGreeting
.
Так как же React распознает, является ли компонент классом или функцией?
подобнопредыдущий пост,Вам не нужно знать конкретную реализацию в React.Я тоже не знал много лет. Пожалуйста, не используйте это как вопрос для интервью. На самом деле, эта статья больше о JavaScript, чем о React.
Эта статья для любознательныхЗачемReact — это одноклассник, который работает определенным образом. Ты? Давайте копать вместе.
Это долгое путешествие, так что пристегните ремни. В этом посте мало говорится о самом React, но мы рассмотрим несколько других аспектов:new
,this
,class
,arrow function
,prototype
,__proto__
,instanceof
и их зависимости в JavaScript. К счастью, вам не нужно слишком много думать при использовании React.
(Если вам действительно нужен ответ, прокрутите вниз.)
Во-первых, нам нужно понять, почему важно по-разному обрабатывать функции и классы. Обратите внимание, как при вызове класса мы используемnew
из.
// If Greeting is a function
const result = Greeting(props); // <p>Hello</p>
// If Greeting is a class
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
Давайте получим примерное представлениеnew
Используйте его в JavaScript.
Раньше в JavaScript не было классов. Однако вы можете аппроксимировать его простой функцией.В частности, вы можете добавить функцию так же, как вы создаете класс.new
для создания функции:
// Just a function
function Person(name) {
this.name = name;
}
var fred = new Person('Fred'); // ✅ Person {name: 'Fred'}
var george = Person('George'); // 🔴 Won’t work
Вы все еще можете писать так сегодня! Попробуйте с инструментами разработчика.
если ты позвонишьPerson('Fred')
нет new
, в методеthis
будет указывать на глобальный или нулевой (например,window
илиundefined
). Таким образом, наш код может ошибаться или неосознанно даватьwindow.name
Назначение.
добавить перед вызовом методаnew
, мы говорим: "Эй, JavaScript, я знаюPerson
Просто функция, но давайте представим, что это конструктор класса.добавить один{}
объект, воляPerson
внутреннийthis
указывает на этот объект, и я могу сделать это какthis.name
присвойте ему такое значение и верните мне этот объект. "
Этоnew
Готово.
var fred = new Person('Fred'); // Same object as `this` inside `Person`
new
оператор также сохранит нас вPerson.prototype
что-то действует наfred
Объект:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
alert('Hi, I am ' + this.name);
}
var fred = new Person('Fred');
fred.sayHi();
Вот как JavaScript может имитировать классы перед их непосредственным добавлением.
такnew
Это было в JavaScript в течение некоторого времени. Тем не менее, класс приходит позже. Это позволяет нам переписать приведенный выше код ближе к тому, что мы задумали:
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
alert('Hi, I am ' + this.name);
}
}
let fred = new Person('Fred');
fred.sayHi();
Для языка и дизайна API,Зафиксируйте замысел разработчикаявляется важным.
Если вы пишете функцию, JavaScript не может догадаться, что она будет похожа наalert()
Нравится, чтобы меня называли, или что-то вродеnew Person()
Вроде как конструктор. Забудь перед функцией плюсnew
может привести к непредсказуемым последствиям.
Синтаксис класса позволяет нам сказать: «Это не просто функция — это класс и имеет конструктор».. Если вы забыли добавитьnew
, JavaScript выдает ошибку:
let fred = new Person('Fred');
// ✅ If Person is a function: works fine
// ✅ If Person is a class: works fine too
let george = Person('George'); // We forgot `new`
// 😳 If Person is a constructor-like function: confusing behavior
// 🔴 If Person is a class: fails immediately
Это помогает нам обнаруживать ошибки на ранней стадии, а не сталкиваться с некоторыми неуловимыми ошибками позже, такими какthis.name
бытьgeorge.name
сталwindow.name
.
Однако это также означает, что React должен предварять любые вызовы класса с помощьюnew
, его нельзя вызвать как обычную функцию, потому что JavaScript воспримет это как ошибку.
class Counter extends React.Component {
render() {
return <p>Hello</p>;
}
}
// 🔴 React can't just do this:
const instance = Counter(props);
Это вызовет проблемы.
Прежде чем рассматривать, как React решает эту проблему, важно помнить, что когда большинство людей используют React, они используют компилятор, такой как Babel, для компиляции новых функций, совместимых со старыми браузерами. Поэтому мы должны учитывать компилятор в дизайне.
В старой версии Babel класс можно было вызывать безnew
. Однако это исправлено - с некоторым дополнительным кодом:
function Person(name) {
// A bit simplified from Babel output:
if (!(this instanceof Person)) {
throw new TypeError("Cannot call a class as a function");
}
// Our code:
this.name = name;
}
new Person('Fred'); // ✅ Okay
Person('George'); // 🔴 Cannot call a class as a function
Вы можете увидеть эти коды в наборах, вот и все._classCallCheck
функция функции. (Вы можете уменьшить размер пакета, выбрав «свободный режим» без проверки, но собственный класс, в который вы в конечном итоге конвертируете, вызовет проблемы в реальной разработке.)
Теперь вы должны примерно понимать, что когда вы коллируете сnew
и нетnew
Разница в следующем:
new Person() |
Person() |
|
---|---|---|
class |
✅this is a Person instance |
🔴TypeError
|
function |
✅this is a Person instance |
😳this is window or undefined
|
Вот почему для React важно правильно вызывать ваши компоненты.Если ваш компонент объявлен с классом, React должен использоватьnew
назвать это.
Итак, React может просто судить, является ли он классом?
Не так просто! Даже если бы мы моглиРазличие между классом и функцией, что по-прежнему не применяется к классам, обрабатываемым такими инструментами, как Babel. Для браузеров это просто обычные функции. Действительно неудача для React.
Хорошо, возможно, React сможет использовать его для каждого вызова.new
? К сожалению, это не всегда работает.
Общая функция, принестиnew
вызывая их, чтобы получить эквивалентthis
объект экземпляра. Для функций, написанных как конструкторы (например, предыдущийPerson
),Оно работает. Но есть проблема с функциональными компонентами:
function Greeting() {
// We wouldn’t expect `this` to be any kind of instance here
return <p>Hello</p>;
}
Эта ситуация терпима. Но есть две причины убить идею.
Первая причина в том, что,new
Не может применяться к собственным стрелочным функциям (не скомпилированным с помощью Babel) при вызове сnew
выдаст ошибку:
const Greeting = () => <p>Hello</p>;
new Greeting(); // 🔴 Greeting is not a constructor
Эта ситуация преднамеренна и соответствует дизайну стрелочных функций. Основная особенность стрелочных функций в том, что они не имеют собственныхthis
значение, в то время какthis
Он определяется ближайшей к себе общей функцией.
class Friends extends React.Component {
render() {
const friends = this.props.friends;
return friends.map(friend =>
<Friend
// `this` is resolved from the `render` method
size={this.props.size}
name={friend.name}
key={friend.id}
/>
);
}
}
ИтакСтрелочные функции не имеют собственныхthis
. Но это также означает, что они не могут быть конструкторами.
const Person = (name) => {
// 🔴 This wouldn’t make sense!
this.name = name;
}
следовательно,JavaScript не позволяет вызывать функции стрелок при добавленииnew
. Если вы это сделаете, что-то пойдет не так, дайте мне знать как можно скорее. Это похоже на то, что JavaScript не позволяет безnew
класс называется.
Это здорово, но это также влияет на наши планы. React не работает из-за стрелочных функцийnew
для вызова всех компонентов. Мы можем использовать недостающиеprototype
чтобы проверить выполнимость стрелочных функций, а не просто использоватьnew
:
(() => {}).prototype // undefined
(function() {}).prototype // {constructor: f}
но этонепригодныйДля функций, скомпилированных с помощью Babel. Это может быть не так уж важно, но есть еще одна причина, по которой этот подход умирает.
мы не всегда можем использоватьnew
Другая причина заключается в том, что он не позволяет React поддерживать компоненты, возвращающие строки или другие примитивные типы данных.
function Greeting() {
return 'Hello';
}
Greeting(); // ✅ 'Hello'
new Greeting(); // 😳 Greeting {}
это снова сnew
операторстранный дизайн. Как мы видели ранее,new
Сообщает движку JavaScript создать объект, приравнивая объект к объекту внутри функции.this
, а затем объект какnew
Результат возвращается.
Однако JavaScript также позволяет использоватьnew
Функция переопределяется, возвращая некоторый объектnew
Возвращаемое значение. Предположительно, это считается полезным для такого шаблона, если мы хотим объединить экземпляры для повторного использования.
// Created lazily
var zeroVector = null;
function Vector(x, y) {
if (x === 0 && y === 0) {
if (zeroVector !== null) {
// Reuse the same instance
return zeroVector;
}
zeroVector = this;
}
this.x = x;
this.y = y;
}
var a = new Vector(1, 1);
var b = new Vector(0, 0);
var c = new Vector(0, 0); // 😲 b === c
Однако, если возвращаемое значение функциинетобъект,new
опять такиполностью игнорируетсяЭто возвращаемое значение. Если вы возвращаете строку или число, это точно так же, как и отсутствие значения.
function Answer() {
return 42;
}
Answer(); // ✅ 42
new Answer(); // 😳 Answer {}
использоватьnew
При вызове функции исходное возвращаемое значение данных (например, число или строка) не может быть прочитано, и она не может поддерживать компоненты, возвращающие строки.
Это неприемлемо, поэтому приходится идти на компромисс.
Чему мы научились? Реакция должна использоватьnew
Позвоните в класс (включая вывод бабела), но не долженnew
Вызовите общую функцию (включая вывод Babel) или стрелочную функцию, и не существует надежного метода их различения.
Если мы не можем решить общие проблемы, можем ли мы решить более конкретные проблемы??
Когда вы объявляете компонент с классом, вы можете захотеть расширитьReact.Component
встроенные методы, такие какthis.setState()
.Мы можем обнаружить только больше, чем обнаружение всех CLASReact.Component
компоненты-потомки?
Спойлер: это именно то, что делает React.
может быть, еслиGreeting
Это компонент класса, который может быть обнаружен обычным методом и пройден тест.Greeting.prototype instanceof React.Component
:
class A {}
class B extends A {}
console.log(B.prototype instanceof A); // true
Я знаю, о чем ты думаешь, что только что произошло? Чтобы ответить на этот вопрос, нам нужно понять прототип JavaScript (прототип).
Вы можете часто слышать «цепочку прототипов», а в JavaScript все объекты должны иметь «прототип». когда мы пишемfred.sayHi()
а такжеfred
нетsayHi
свойств, мы начнем сfred
посмотрите в прототипеsayHi
. Если не найдем, будем искать следующий в цепочкеprototype
——fred
Прототип прототипа и так далее.
Удивительно, что класс или функцияprototype
Атрибутыне будетУказывает на прототип для этого значения. Я не шучу.
function Person() {}
console.log(Person.prototype); // 🤪 Not Person's prototype
console.log(Person.__proto__); // 😳 Person's prototype
так__proto__.__proto__.__proto__
Сравниватьprototype.prototype.prototype
Больше похоже на "цепочку прототипов". Мне потребовались годы, чтобы понять это.
затем функция или классprototype
Каковы свойства?Он доступен для всех классов или функцийnew
объект__proto__
.
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
alert('Hi, I am ' + this.name);
}
var fred = new Person('Fred'); // Sets `fred.__proto__` to `Person.prototype`
а также__proto__
Цепочка — это то, как JavaScript находит свойства:
fred.sayHi();
// 1. Does fred have a sayHi property? No.
// 2. Does fred.__proto__ have a sayHi property? Yes. Call it!
fred.toString();
// 1. Does fred have a toString property? No.
// 2. Does fred.__proto__ have a toString property? No.
// 3. Does fred.__proto__.__proto__ have a toString property? Yes. Call it!
При кодировании, если вы не отлаживаете ошибку, связанную с цепочкой прототипов, вам редко нужно касаться__proto__
. если вы хотитеfred.__proto__
Чтобы что-то добавить, вы должны добавить это вPerson.prototype
начальство. По крайней мере, так было задумано изначально.
вначале,__proto__
Свойства даже не должны открываться браузером, потому что цепочка прототипов рассматривается как внутренняя концепция. Но некоторые браузеры добавляют__proto__
, который в конечном итоге был неохотно стандартизирован (но устарел, заменен наObject.getPrototypeOf()
).
Тем не менее, я все еще чувствую, что один называетсяprototype
Свойство не предоставляет вам прототип значения и очень запутанно(Например, посколькуfred
это не функцияfred.prototype
становится неопределенным). Я думаю, что это главная причина, по которой опытные разработчики также неправильно понимают прототипы JavaScript.
Это длинная статья, да? Я бы сказал, что мы на 80%, давайте двигаться дальше.
когда мы пишемobj.foo
, JavaScript на самом делеobj
, obj.__proto__
, obj.__proto__.__proto__
Поиск поfoo
, и так далее.
В классе вы не увидите этот механизм напрямую, ноextends
Он также реализован на основе этой классической цепочки прототипов. Вот как экземпляр React, определенный нашим классом, может получить что-то вродеsetState
Причина метода:
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
let c = new Greeting();
console.log(c.__proto__); // Greeting.prototype
console.log(c.__proto__.__proto__); // React.Component.prototype
console.log(c.__proto__.__proto__.__proto__); // Object.prototype
c.render(); // Found on c.__proto__ (Greeting.prototype)
c.setState(); // Found on c.__proto__.__proto__ (React.Component.prototype)
c.toString(); // Found on c.__proto__.__proto__.__proto__ (Object.prototype)
другими словами,Когда вы используете класс, экземпляр__proto__
Цепочка «реплицирует» структуру класса:
// `extends` chain
Greeting
→ React.Component
→ Object (implicitly)
// `__proto__` chain
new Greeting()
→ Greeting.prototype
→ React.Component.prototype
→ Object.prototype
две цепочки.
потому что__proto__
Цепочка отражает структуру класса, мы можем получить ее изGreeting.prototype
начать, с__proto__
Проверьте цепочку, чтобы убедиться, чтоGreeting
расширенныйReact.Component
:
// `__proto__` chain
new Greeting()
→ Greeting.prototype // 🕵️ We start here
→ React.Component.prototype // ✅ Found it!
→ Object.prototype
Проще говоря,X instanceof Y
Только что сделал этот поиск. следуетx.__proto__
цепь ищетY.prototype
.
Обычно это используется, чтобы определить, является ли что-то экземпляром класса:
let greeting = new Greeting();
console.log(greeting instanceof Greeting); // true
// greeting (🕵️ We start here)
// .__proto__ → Greeting.prototype (✅ Found it!)
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype
console.log(greeting instanceof React.Component); // true
// greeting (🕵️ We start here)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype (✅ Found it!)
// .__proto__ → Object.prototype
console.log(greeting instanceof Object); // true
// greeting (🕵️ We start here)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype (✅ Found it!)
console.log(greeting instanceof Banana); // false
// greeting (🕵️ We start here)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype (🙅 Did not find it!)
И его также можно использовать, чтобы определить, расширяет ли класс другой класс:
console.log(Greeting.prototype instanceof React.Component);
// greeting
// .__proto__ → Greeting.prototype (🕵️ We start here)
// .__proto__ → React.Component.prototype (✅ Found it!)
// .__proto__ → Object.prototype
Если что-то является классом или обычным функциональным компонентом React, мы можем использовать это, чтобы судить о наших мыслях.
Однако React делает это не так. 😳
Одна из проблем заключается в том, что в React проверяемые нами компоненты могут быть унаследованы отразноеРеагировать компонентыReact.Component
копировать,instanceof
Решение не работает для такого количества реплицированных компонентов React на странице. Эмпирически есть несколько причин, по которым многократное смешивание компонентов React в проекте не является хорошим выбором, и мы хотим по возможности избегать этого. (В Крючках мыможет понадобиться) реализует идею удаления дубликатов.
Существует также эвристика для обнаружения наличия на прототипеrender
метод. Однако в то времяне ясноКак будет развиваться API компонента. Каждое обнаружение увеличивает время обнаружения на единицу, и мы не хотим тратить здесь больше двух раз. и когдаrender
Когда это метод, определенный для экземпляра (например, определенный синтаксическим сахаром атрибута класса), этот метод бесполезен.
Поэтому реагироватьДобавить кК компоненту базового класса добавляется специальный флаг. React узнает, является ли компонент React классом, проверяя наличие этого флага.
Первоначально этот знак был вReact.Component
В этом базовом классе:
// Inside React
class Component {}
Component.isReactClass = {};
// We can check it like this
class Greeting extends Component {}
console.log(Greeting.isReactClass); // ✅ Yes
Однако некоторые унаследованные классы, которые нам нужно оценить,Не будетСкопируйте статические свойства (или задайте нестандартные__proto__
), поэтому этот флаг не будет иметь никакого эффекта.
Вот почему этот флаг ReactпередачаприбытьReact.Component.prototype
начальство:
// Inside React
class Component {}
Component.prototype.isReactComponent = {};
// We can check it like this
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent); // ✅ Yes
Весь контент здесь.
Вам может быть интересно, почему это объект, а не логическое значение. В реальном процессе не о чем беспокоиться. В более ранних версиях Jest (когда Jest был еще превосходным) функция автоматической блокировки была включена по умолчанию, а фиктивные данные, сгенерированные Jest, игнорировали исходный тип данных, что приводило к сбою проверки React. Спасибо, Ле. . Шутка.
isReactComponent
обнаружены в сегодняшнемВсе еще используется в React.
если у вас нет расширенияReact.Component
, React не смотрит в прототипisReactComponent
, он не будет рассматриваться как компонент класса. теперь ты знаешь почемуCannot call a class as a function
НеправильноЛучший ответиспользуетсяextends React.Component
Ну давай же. Наконец, мы также добавляемпредупреждать,когдаprototype.render
существует, ноprototype.isReactComponent
Предупреждение выдается, когда он не существует.
Можно сказать, что эта история — рекламный ход.Фактическое решение на самом деле довольно простое, но я использовал много отступлений, чтобы объяснить, почему React в конечном итоге использовал это решение и какие есть альтернативы..
По моему опыту, API библиотеки классов обычно выглядит так: чтобы сделать API простым в использовании, вам часто нужно учитывать семантику языка (и, вероятно, для многих языков также необходимо учитывать будущее направление) , производительность во время выполнения, эргономика и время компиляции, экология, схемы упаковки, предварительное предупреждение и многое другое. Конечный результат не всегда может быть самым элегантным, но он должен быть практичным.
Если окончательный API возможен,его пользователивам никогда не нужно думать о процессе вывода. Вместо этого они могут больше сосредоточиться на создании приложений.
Но если вам все еще интересно. . . Также было бы неплохо узнать, как это работает.
перевести оригиналHow Does React Tell a Class from a Function?