React практически не добавляет к нативному JS внешней абстракции. Поэтому разработчику необходимо хорошо разбираться в основах языка.
В этой статье разберем самые нужные для React концепции JavaScript.
Вхождение в React
Первое что мы видим после создания проекта с create-react-app – это классы компонентов:
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. Они делают код намного короче и проще.
// ES6 стрелочная функция с телом
const getGreeting = () => {
return 'Welcome to JavaScript';
}
// ES6 стрелочная функция без тела
const getGreeting = () => 'Welcome to JavaScript';
Строковые литералы
Мы привыкли к такому синтаксису конкатенации строк в JS:
const framework = 'React';
const greeting = 'Добро пожаловать в ' + framework + '!';
А шаблоны строк позволяют использовать интерполяцию с помощью обратных кавычек и нотации ${}
:
const framework = 'React';
const greeting = `Добро пожаловать в ${framework}!`;
Их можно использовать также для реализации многострочности:
const framework = 'React';
const greeting = `
Добро пожаловать
в
${framework}!
`;
Компоненты
React и JS-классы
Классы появились в языке относительно недавно, заменив собой цепочки прототипов. Один из способов определения компонентов в React основан именно на них.
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
позволяет одному классу наследовать от другого. Класс-наследник может расширять функциональность родителя собственными методами.
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.
import React, { Component } from 'react';
class App extends Component {
render() {
// ...
}
}
export default App;
Метод render
является обязательным, так как класс Component
вызывает его для отображения чего-либо в браузере. Без расширения базового компонента не получится использовать методы жизненного цикла, например, componentDidMount
, а также метод setState
, управляющий локальным состоянием.
Таким образом, использование JS-классов позволяет расширить существующий функционал базового компонента и получить доступ к API React.
Сокращенный синтаксис
Если компонент имеет собственные методы, их необходимо привязать в конструкторе. Там же создается объект состояния:
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() {
// ...
}
}
Если таких компонентов много, то привязка методов и вообще создание конструктора становится весьма утомительной задачей. К счастью, существует сокращенный синтаксис:
class Counter extends Component {
state = {
counter: 0,
};
onIncrement = () => { ... }
onDecrement = () => { ... }
render() {
// ...
}
}
Использование стрелочных функций JavaScript позволяет не биндить методы. А если конструктор не использует props
, его вообще можно убрать, определив состояние непосредственно как свойство класса. (Свойства класса еще не входят в стандарт JS.)
Компоненты-функции
Некоторые компоненты должны получать входные данные и просто возвращать отображаемые HTML-элементы без управления состоянием. Такие компоненты называются функциональными, они менее сложны и более удобны.
function Greeting(props) {
return <h1>{props.greeting}</h1>;
}
Для их создания можно использовать стрелочные функции JS.
const Greeting = (props) => <h1>{props.greeting}</h1>
Выражения импорта и экспорта
Любой create-react-app
проект начинается с инструкций import и export:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
// ...
}
}
export default App;
Переменные, компоненты или функции, объявленные в одном файле:
const firstname = 'Robin';
const lastname = 'Wieruch';
export { firstname, lastname };
можно импортировать в другой:
import { firstname, lastname } from './file1.js';
Весь экспорт можно получить в виде единого объекта:
import * as person from './file1.js';
Чтобы избежать совпадений имен при импорте используются псевдонимы.
import { firstname as username } from './file1.js';
Есть также вариант импорта/экспорта по умолчанию. Он используется, если экспортируется всего одна переменная или если требуется выделить главную функциональность модуля.
const robin = {
firstname: 'Robin',
lastname: 'Wieruch',
};
export default robin;
Импортировать дефолтный объект можно без фигурных скобок и с другим именем.
import developer from './file1.js';
console.log(developer); // { firstname: 'Robin', lastname: 'Wieruch' }
Можно сочетать дефолтный и именованный экспорт:
const firstname = 'Robin';
const lastname = 'Wieruch';
const person = {
firstname,
lastname,
};
export {
firstname,
lastname,
};
export default person;
и импорт:
import developer, { firstname, lastname } from './file1.js';
Это основные функции ES6 модулей. Они помогают организовать код, удобно поддерживать его, тестировать и повторно использовать.
JavaScript detected
Деструктуризация и spread-оператор
Если требуется получить доступ к большому количеству свойств из state
или props
, можно использовать новую возможность JavaScript – деструктуризацию.
// без деструктуризации
const users = this.state.users;
const counter = this.state.counter;
// деструктуризация
const { users, counter } = this.state;
Входящий параметр можно деструктурировать прямо в сигнатуре функции.
function Greeting({ greeting }) {
return <h1>{greeting}</h1>;
}
Для сохранения незадействованных при деструктурировании свойств объекта существует параметр rest
.
const { users, ...rest } = this.state;
С его помощью можно передать неиспользованные данные дальше по цепочке компонентов, используя spread-оператор.
Map, Reduce и Filter
Вывести в JSX-коде одну переменную или свойство объекта нетрудно, нужно просто обернуть их в фигурные скобки. Но как вывести список? В React не существует какого-то специального API или атрибута для рендеринга коллекции элементов, нужно использовать нативные возможности языка.
class App extends Component {
render() {
var users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<ul>
{users.map(function (user) {
return <li>{user.name}</li>;
})}
</ul>
);
}
}
Можно еще сократить код, используя стрелочные функции:
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
);
JS-метод map
просто проходит по массиву и возвращает JSX-код для каждого элемента. Иногда вместо map
следует применять filter
или reduce
.
Тернарный оператор
В JSX нельзя напрямую использовать конструкцию if...else
, но можно воспользоваться тернарным оператором JS:
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.
Функции высшего порядка
Рассмотрим пример фильтрации списка пользователей на основе значения поля ввода.
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
как параметр.
function doFilter(user) {
return query === user.name;
}
{users
.filter(doFilter)
.map(user => <li>{user.name}</li>)
}
Но doFilter
ничего не знает о свойстве query
. Чтобы все заработало, мы превратим ее в функцию высшего порядка.
function doFilter(query) {
return function (user) {
return query === user.name;
}
}
{users
.filter(doFilter(this.state.query))
.map(user => <li>{user.name}</li>)
}
Стрелочные функции сделают запись более краткой.
const doFilter = query => user =>
query === user.name;
Теперь doFilter
можно экспортировать из файла и тестировать изолированно от компонента.
Поняв этот принцип, вы без труда разберетесь в React-компонентах высшего порядка.
Больше JavaScript, чем React
Фреймворк React имеет тонкую прослойку аутентичного API, но все остальное – это чистый JavaScript. Давайте убедимся в этом, отрефакторив компонент высшего порядка, а заодно повторим изученное.
function withLoading(Component) {
return class WithLoading extends {
render() {
const { isLoading, ...props } = this.props;
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
}
}
};
}
Это компонент-обертка для отображения индикатора загрузки. Вы уже можете видеть деструктурирование и работу spread-оператора, который позволяет передать все неиспользованные свойства из объекта props.
Первым шагом рефакторинга будет превращение компонента в функциональный:
function withLoading(Component) {
return function ({ isLoading, ...props }) {
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
};
}
Переносим деструктуризацию в сигнатуру параметра и используем стрелочную функцию для краткости:
const withLoading = Component => ({ isLoading, ...props }) => {
// ...
}
Добавим тернарный оператор:
const withLoading = Component => ({ isLoading, ...props }) =>
isLoading
? <p>Loading</p>
: <Component { ...props } />
Очевидно, что методы, которые использует компонент высшего порядка, принадлежат не фреймворку, а языку. Таким образом, React-приложения базируются на возможностях JS. Расширить их функциональность можно с помощью различных внешних библиотек.
Перевод статьи JavaScript fundamentals before learning React
Другие статьи по изучению React:
- Пособие по React: всестороннее изучение
- 25 понятных туториалов для изучения React Native
- Основные концепции React, о которых стоит знать
Комментарии