Больше JS, чем React: как фреймворк использует возможности языка
React практически не добавляет к нативному JS внешней абстракции. Поэтому разработчику необходимо хорошо разбираться в основах языка.
В этой статье разберем самые нужные для React концепции JavaScript.
Вхождение в React
Первое что мы видим после создания проекта с create-react-app – это классы компонентов:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import React, { Component } from ‘react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;
class App extends Component {
getGreeting() {
return ‘Добро пожаловать в React!’;
}
render() {
return (
<div className=«App»>
<img src={logo} className=«App-logo» alt=«logo» />
<h1 className=«App-title»>{this.getGreeting()}</h1>
</div>
);
}
}
export default App;
|
Здесь много концепций, которые не связаны с фреймворком напрямую: ключевое слово class, наследование, методы, интерполяция, импорт и экспорт. Кажется, без понимания нативного JavaScript в React делать нечего. Начнем с самых простых вещей и увидим, что в React больше JS, чем мы думали.
Стрелочные функции
Стрелочные функции – одно из недавних приобретений JavaScript. Они делают код намного короче и проще.
1
2
3
4
5
6
7
|
// ES6 стрелочная функция с телом
const getGreeting = () => {
return ‘Welcome to JavaScript’;
}
// ES6 стрелочная функция без тела
const getGreeting = () => ‘Welcome to JavaScript’;
|
Строковые литералы
Мы привыкли к такому синтаксису конкатенации строк в JS:
1
2
|
const framework = ‘React’;
const greeting = ‘Добро пожаловать в ‘ + framework + ‘!’;
|
А шаблоны строк позволяют использовать интерполяцию с помощью обратных кавычек и нотации ${}
:
1
2
|
const framework = ‘React’;
const greeting = `Добро пожаловать в ${framework}!`;
|
Их можно использовать также для реализации многострочности:
1
2
3
4
5
6
|
const framework = ‘React’;
const greeting = `
Добропожаловать
в
${framework}!
`;
|
Компоненты
React и JS-классы
Классы появились в языке относительно недавно, заменив собой цепочки прототипов. Один из способов определения компонентов в React основан именно на них.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Developer {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
getName() {
return this.firstname + ‘ ‘ + this.lastname;
}
}
var me = new Developer(‘Robin’, ‘Wieruch’);
console.log(me.getName());
|
Оператор new
вызывает конструктор класса, который создает новый объект, обладающий некоторыми свойствами и методами.
Оператор extends
позволяет одному классу наследовать от другого. Класс-наследник может расширять функциональность родителя собственными методами.
1
2
3
4
5
6
7
8
9
10
|
class ReactDeveloper extends Developer {
getJob() {
return ‘React Developer’;
}
}
var me = new ReactDeveloper(‘Robin’, ‘Wieruch’);
console.log(me.getName());
console.log(me.getJob());
|
Все компоненты React наследуют об базового класса Component
, импортированного из пакета React.
1
2
3
4
5
6
7
8
9
|
import React, { Component } from ‘react’;
class App extends Component {
render() {
// …
}
}
export default App;
|
Метод render
является обязательным, так как класс Component
вызывает его для отображения чего-либо в браузере. Без расширения базового компонента не получится использовать методы жизненного цикла, например, componentDidMount
, а также метод setState
, управляющий локальным состоянием.
Таким образом, использование JS-классов позволяет расширить существующий функционал базового компонента и получить доступ к API React.
Сокращенный синтаксис
Если компонент имеет собственные методы, их необходимо привязать в конструкторе. Там же создается объект состояния:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
this.onIncrement = this.onIncrement.bind(this);
this.onDecrement = this.onDecrement.bind(this);
}
onIncrement() {
// …
}
onDecrement() {
// …
}
render() {
// …
}
}
|
Если таких компонентов много, то привязка методов и вообще создание конструктора становится весьма утомительной задачей. К счастью, существует сокращенный синтаксис:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Counter extends Component {
state = {
counter: 0,
};
onIncrement = () => { ... }
onDecrement = () => { ... }
render() {
// …
}
}
|
Использование стрелочных функций JavaScript позволяет не биндить методы. А если конструктор не использует props
, его вообще можно убрать, определив состояние непосредственно как свойство класса. (Свойства класса еще не входят в стандарт JS.)
Компоненты-функции
Некоторые компоненты должны получать входные данные и просто возвращать отображаемые HTML-элементы без управления состоянием. Такие компоненты называются функциональными, они менее сложны и более удобны.
1
2
3
|
function Greeting(props) {
return <h1>{props.greeting}</h1>;
}
|
Для их создания можно использовать стрелочные функции JS.
1
|
const Greeting = (props) => <h1>{props.greeting}</h1>
|
Выражения импорта и экспорта
Любой create-react-app
проект начинается с инструкций import и export:
1
2
3
4
5
6
7
8
9
10
|
import React, { Component } from ‘react’;
import ‘./App.css’;
class App extends Component {
render() {
// …
}
}
export default App;
|
Переменные, компоненты или функции, объявленные в одном файле:
1
2
3
|
const firstname = ‘Robin’;
const lastname = ‘Wieruch’;
export { firstname, lastname };
|
можно импортировать в другой:
1
|
import { firstname, lastname } from ‘./file1.js’;
|
Весь экспорт можно получить в виде единого объекта:
1
|
import * as person from ‘./file1.js’;
|
Чтобы избежать совпадений имен при импорте используются псевдонимы.
1
|
import { firstname as username } from ‘./file1.js’;
|
Есть также вариант импорта/экспорта по умолчанию. Он используется, если экспортируется всего одна переменная или если требуется выделить главную функциональность модуля.
1
2
3
4
5
|
const robin = {
firstname: ‘Robin’,
lastname: ‘Wieruch’,
};
export default robin;
|
Импортировать дефолтный объект можно без фигурных скобок и с другим именем.
1
2
|
import developer from ‘./file1.js’;
console.log(developer); // { firstname: ‘Robin’, lastname: ‘Wieruch’ }
|
Можно сочетать дефолтный и именованный экспорт:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const firstname = ‘Robin’;
const lastname = ‘Wieruch’;
const person = {
firstname,
lastname,
};
export {
firstname,
lastname,
};
export default person;
|
и импорт:
1
|
import developer, { firstname, lastname } from ‘./file1.js’;
|
Это основные функции ES6 модулей. Они помогают организовать код, удобно поддерживать его, тестировать и повторно использовать.
JavaScript detected
Деструктуризация и spread-оператор
Если требуется получить доступ к большому количеству свойств из state
или props
, можно использовать новую возможность JavaScript – деструктуризацию.
1
2
3
4
5
6
|
// без деструктуризации
const users = this.state.users;
const counter = this.state.counter;
// деструктуризация
const { users, counter } = this.state;
|
Входящий параметр можно деструктурировать прямо в сигнатуре функции.
1
2
3
|
function Greeting({ greeting }) {
return <h1>{greeting}</h1>;
}
|
Для сохранения незадействованных при деструктурировании свойств объекта существует параметр rest
.
1
|
const { users, ...rest } = this.state;
|
С его помощью можно передать неиспользованные данные дальше по цепочке компонентов, используя spread-оператор.
Map, Reduce и Filter
Вывести в JSX-коде одну переменную или свойство объекта нетрудно, нужно просто обернуть их в фигурные скобки. Но как вывести список? В React не существует какого-то специального API или атрибута для рендеринга коллекции элементов, нужно использовать нативные возможности языка.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class App extends Component {
render() {
var users = [
{ name: ‘Robin’ },
{ name: ‘Markus’ },
];
return (
<ul>
{users.map(function (user) {
return <li>{user.name}</li>;
})}
</ul>
);
}
}
|
Можно еще сократить код, используя стрелочные функции:
1
2
3
4
5
|
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
);
|
JS-метод map
просто проходит по массиву и возвращает JSX-код для каждого элемента. Иногда вместо map
следует применять filter
или reduce
.
Тернарный оператор
В JSX нельзя напрямую использовать конструкцию if...else
, но можно воспользоваться тернарным оператором JS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import React, { Component } from ‘react’;
class App extends Component {
render() {
const users = [
{ name: ‘Robin’ },
{ name: ‘Markus’ },
];
const showUsers = false;
return (
<div>
{
showUsers ? (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
) : (
null
)
}
</div>
);
}
}
export default App;
|
Вы можете ознакомиться с другими методами условного рендеринга в React.
Функции высшего порядка
Рассмотрим пример фильтрации списка пользователей на основе значения поля ввода.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import React, { Component } from ‘react’;
class App extends Component {
state = {
query: », // значение поля
};
onChange = event => {
this.setState({ query: event.target.value });
}
render() {
const users = [
{ name: ‘Robin’ },
{ name: ‘Markus’ },
];
return (
<div>
<ul>
{users
.filter(user => this.state.query === user.name) // фильтрация
.map(user => <li>{user.name}</li>) // вывод
}
</ul>
<input
type=«text»
onChange={this.onChange}
/>
</div>
);
}
}
export default App;
|
Выделим фильтрующую функцию из компонента, чтобы было удобнее ее тестировать и передадим в метод filter
как параметр.
1
2
3
|
function doFilter(user) {
return query === user.name;
}
|
1
2
3
4
|
{users
.filter(doFilter)
.map(user => <li>{user.name}</li>)
}
|
Но doFilter
ничего не знает о свойстве query
. Чтобы все заработало, мы превратим ее в функцию высшего порядка.
1
2
3
4
5
|
function doFilter(query) {
return function (user) {
return query === user.name;
}
}
|
1
2
3
4
|
{users
.filter(doFilter(this.state.query))
.map(user => <li>{user.name}</li>)
}
|
Стрелочные функции сделают запись более краткой.
1
2
|
const doFilter = query => user =>
query === user.name;
|
Теперь doFilter
можно экспортировать из файла и тестировать изолированно от компонента.
Поняв этот принцип, вы без труда разберетесь в React-компонентах высшего порядка.
Больше JavaScript, чем React
Фреймворк React имеет тонкую прослойку аутентичного API, но все остальное – это чистый JavaScript. Давайте убедимся в этом, отрефакторив компонент высшего порядка, а заодно повторим изученное.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function withLoading(Component) {
return class WithLoading extends {
render() {
const { isLoading, ...props } = this.props;
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
}
}
};
}
|
Это компонент-обертка для отображения индикатора загрузки. Вы уже можете видеть деструктурирование и работу spread-оператора, который позволяет передать все неиспользованные свойства из объекта props.
Первым шагом рефакторинга будет превращение компонента в функциональный:
1
2
3
4
5
6
7
8
9
|
function withLoading(Component) {
return function ({ isLoading, ...props }) {
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
};
}
|
Переносим деструктуризацию в сигнатуру параметра и используем стрелочную функцию для краткости:
1
2
3
|
const withLoading = Component => ({ isLoading, ...props }) => {
// …
}
|
Добавим тернарный оператор:
1
2
3
4
|
const withLoading = Component => ({ isLoading, ...props }) =>
isLoading
? <p>Loading</p>
: <Component { ...props } />
|
Очевидно, что методы, которые использует компонент высшего порядка, принадлежат не фреймворку, а языку. Таким образом, React-приложения базируются на возможностях JS. Расширить их функциональность можно с помощью различных внешних библиотек.
Перевод статьи JavaScript fundamentals before learning React

