Прежде чем мы начнём разговор о способах импорта в веб-проекты библиотеки React, покажу современные способы выполнения этой операции и использования хука
useState
:// Глобальный подход
window.React.useState()
// Использование импорта в стиле CommonJS
const React = require('react')
React.useState()
// ES-модули, импорт значения, экспортируемого по умолчанию
import React from 'react'
React.useState()
// ES-модули, именованный импорт
import {useState} from 'react'
useState()
// ES-модули, импорт пространства имён
import * as React from 'react'
React.useState()
Ниже я расскажу об истоках каждого из этих механизмов, и о том, почему я предпочитаю использовать последний из них.
Импорт React до начала времён
Я начал писать React-код во времена
React.createClass
. Вот как это делалось тогда:var React = require('react')
var Counter = React.createClass({
propTypes: {
initialCount: React.PropTypes.number.isRequired,
step: React.PropTypes.number,
},
getDefaultProps: function () {
return {step: 1}
},
getInitialState: function () {
var initialCount = this.props.hasOwnProperty('initialCount')
? this.props.initialCount
: 0
return {count: initialCount}
},
changeCount: function (change) {
this.setState(function (previousState) {
return {count: previousState.count + change}
})
}
increment: function () {
this.changeCount(this.props.step)
},
decrement: function () {
this.changeCount(-this.props.step)
},
render: function () {
return (
<div>
<div>Current Count: {this.state.count}</div>
<button onClick={this.decrement}>-</button>
<button onClick={this.increment}>+</button>
</div>
)
},
})
Тут можно видеть и
var
, и React.createClass
, и require
, и function
. Славные были времена.Классы и модули
Потом появился стандарт ES6 (и более поздние стандарты), в котором были описаны модули, классы и некоторые другие приятные синтаксические конструкции:
import React from 'react'
class Counter extends React.Component {
state = {count: this.props.initialCount ?? 0}
changeCount() {
this.setState(({count}) => ({count + change}))
}
increment = () => this.changeCount(this.props.step)
decrement = () => this.changeCount(-this.props.step)
render() {
return (
<div>
<div>Current Count: {this.state.count}</div>
<button onClick={this.decrement}>-</button>
<button onClick={this.increment}>+</button>
</div>
)
}
}
Именно в это время люди начали задаваться вопросом о том, как правильно импортировать React. Многие выбирали такой способ:
import React, {Component} from 'react'
class Counter extends Component {
state = {count: this.props.initialCount ?? 0}
changeCount() {
this.setState(({count}) => ({count + change}))
}
increment = () => this.changeCount(this.props.step)
decrement = () => this.changeCount(-this.props.step)
render() {
return (
<div>
<div>Current Count: {this.state.count}</div>
<button onClick={this.decrement}>-</button>
<button onClick={this.increment}>+</button>
</div>
)
}
}
Обычно подобные вещи не вызывают вообще никаких вопросов. Программисты делают то, что согласуется с устройством библиотеки. Но React никогда не предлагал программистам возможности ES-модулей. С точки зрения программиста библиотека выглядела либо как глобальная переменная
React
, либо как CommonJS-модуль, представленный объектом React
, в котором есть Component
и много чего ещё. Но из-за того, как именно компилируется подобный код, оба подхода, с технической точки зрения, были рабочими. Ни один из них нельзя было признать «неправильным».С другой стороны, глядя на вышеприведённый код можно задаться вопросом о том, почему нельзя переписать его так:
- import React, {Component} from 'react'
+ import {Component} from 'react'
Причина, по которой тут надо импортировать
React
, заключается в том, что (в те времена) JSX-код компилировался в расчёте на использование React:- <button onClick={this.increment}>+</button>
+ React.createElement('button', {onClick: this.increment}, '+')
Поэтому, если использовался JSX, то надо было импортировать и
React
.Через некоторое время, не очень большое, появились функциональные компоненты. Даже учитывая то, что тогда их нельзя было использовать для управления состоянием или побочными эффектами, они стали очень популярными. Я (как и многие другие) сильно привык к преобразованию кода компонентов, основанных на классах, в код функциональных компонентов, равно как и к обратной операции. Многие просто решили, что так работать проще в сравнении с использованием исключительно компонентов, основанных на классах.
Я же стремился к как можно более широкому использованию функциональных компонентов. И в этом, вероятно, кроется причина того, что я предпочитал использовать конструкции
import React from 'react'
и React.Component
, а не import React, {Component} from 'react'
. Мне не хотелось каждый раз менять выражения импорта, переходя от компонентов, основанных на классах, к функциональным компонентам, или выполняя обратную операцию. И да, я знаю, что IDE и редакторы, вроде VSCode и WebStorm, позволяют автоматизировать импорт библиотек, но мне никогда не удавалось добиться с помощью этих механизмов результата, который бы меня полностью устраивал (я постоянно сталкивался с всякими неприятностями, вроде этой).И ещё одно интересное наблюдение. Если применять TypeScript вместо Babel, то понадобится пользоваться конструкцией
import * as React from 'react'
(если только не включить allowSyntheticDefaultImports.Эпоха хуков
Потом появились хуки и мой подход к разработке компонентов снова эволюционировал:
import React from 'react'
function Counter({initialCount = 0, step}) {
const [count, setCount] = React.useState(initialCount)
const decrement = () => setCount((c) => c - step)
const increment = () => setCount((c) => c + step)
return (
<div>
<div>Current Count: {count}</div>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
)
}
Всё это привело к очередной волне вопросов о том, как правильно импортировать React.
Есть два способа использования хуков:
import React from 'react'
// ...
const [count, setCount] = React.useState(initialCount)
// или так:
import React, {useState} from 'react'
// ...
const [count, setCount] = useState(initialCount)
Надо ли пользоваться именованными импортами, или можно просто напрямую сослаться на то, что нужно, обратившись к React? Я, опять же, предпочёл поступить так, чтобы мне не приходилось обновлять команды импорта каждый раз, когда я добавляю хуки в файлы или удаляю их. И, опять же, я не доверяю возможностям редакторов кода по автоматизации импортов. В результате я выбрал конструкцию
React.useState
, а не useState
.Новый подход к трансформации JSX, будущее React и ES-модули
Когда, наконец, вышла 17 версия React, серьёзных изменений в библиотеке не произошло, но было сообщено об интересном дополнении. Речь идёт о новом способе преобразования JSX, для применения которого не нужно импортировать React. Поэтому теперь можно писать следующий код:
function App() {
return <h1>Hello World</h1>
}
Это компилируется в следующий код:
// Вставлено компилятором (самим импортировать это не нужно!)
import {jsx as _jsx} from 'react/jsx-runtime'
function App() {
return _jsx('h1', {children: 'Hello world'})
}
Теперь импорт того, что нужно, выполняется автоматически. Это очень хорошо, но это ещё и означает, что если планируется перевести некий проект на использование этой возможности, нужно будет убрать из него выражения
import React from 'react'
, которые использовались только для обеспечения поддержки JSX. К счастью, команда разработчиков React создала скрипт, автоматизирующий этот процесс. Им нужно было принять решение о том, что делать в ситуациях, когда пользуются хуками. Тут есть два варианта:import * as React from 'react'
const [count, setCount] = React.useState(initialCount)
// или так
import {useState} from 'react'
const [count, setCount] = useState(initialCount)
Оба эти подхода являются в наши дни рабочими, они правильны с технической точки зрения. Продолжат они работать и тогда, когда в React наконец появится официальная поддержка ES-модулей.
Команда React решила выбрать подход, связанный с использованием именованных импортов. Я не поддерживаю это решение по причинам, о которых говорил выше (речь идёт о необходимости постоянно менять команды импорта). Поэтому я решил пользоваться конструкцией
import * as React from 'react'
. Она, правда, длинная, набирать её долго, поэтому я написал такой сниппет:// snippets/javascript.json
{
"import React": {
"prefix": "ir",
"body": ["import * as React from 'react'\n"]
},
}
Вот твит Дэна Абрамова, касающийся текущего состояния дел в области импорта React. А именно, речь идёт о том, что конструкции
import { useState } from 'react'
и import * as React from 'react'
вполне современны, а конструкцией import React from 'react'
, так называемым «импортом по умолчанию», лучше не пользоваться, так как в будущем (возможно, в React 19 или 20) команда React откажется от поддержки этой конструкции.Итоги
Я в настоящее время пользуюсь
import * as React from 'react'
. Благодаря этому мне не нужно беспокоиться о применяемых мной командах импорта. Собственно говоря, только что вы прочли мой развёрнутый ответ на вопрос о том, как правильно импортировать React.Как вы импортируете React в свои проекты?