[Перевод] Импорт react с древнейших времен до наших дней

h4hjayo6uwn9_o075nagii3ehs4.jpeg

Прежде чем мы начнём разговор о способах импорта в веб-проекты библиотеки 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 (
      
        
Current Count: {this.state.count}
                        
    )   }, })


Тут можно видеть и 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 (
      
        
Current Count: {this.state.count}
                        
    )   } }


Именно в это время люди начали задаваться вопросом о том, как правильно импортировать 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 (
      
        
Current Count: {this.state.count}
                        
    )   } }


Обычно подобные вещи не вызывают вообще никаких вопросов. Программисты делают то, что согласуется с устройством библиотеки. Но React никогда не предлагал программистам возможности ES-модулей. С точки зрения программиста библиотека выглядела либо как глобальная переменная React, либо как CommonJS-модуль, представленный объектом React, в котором есть Component и много чего ещё. Но из-за того, как именно компилируется подобный код, оба подхода, с технической точки зрения, были рабочими. Ни один из них нельзя было признать «неправильным».

С другой стороны, глядя на вышеприведённый код можно задаться вопросом о том, почему нельзя переписать его так:

- import React, {Component} from 'react'
+ import {Component} from 'react'


Причина, по которой тут надо импортировать React, заключается в том, что (в те времена) JSX-код компилировался в расчёте на использование React:

- 
+ 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 (
    
      
Current Count: {count}
                  
  ) }


Всё это привело к очередной волне вопросов о том, как правильно импортировать 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 

Hello World

}


Это компилируется в следующий код:

// Вставлено компилятором (самим импортировать это не нужно!)
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 в свои проекты?

8xzqbhb0at3_pjylb5c4366w_t8.png

oug5kh6sjydt9llengsiebnp40w.png

© Habrahabr.ru