React: основы

Основы React и JSX. А также: правильная работа со State, React’s patterns, функции жизненного цикла, отлов ошибок типизации.

Содержание

Установка

Устанавливаем create-react-app

Вводим в консоль / терминал: npm i -g create-react-app

Создаём ваше приложение

create-react-app name, где ‘name’ — название вашего приложения

cd name — переходим в папку с приложением, npm start — запускаем

Исправляем ошибки

Если после npm start падают ошибки типа: There might be a problem with the project dependency tree. It is likely not a bug in Create React App, but something you need to fix locally,

то одним из вариантов может быть создание файла .env со следующим содержимым:

SKIP_PREFLIGHT_CHECK=true

Это отключает проверку и позволяет работать.

Конфликтуют обычно компоненты установленные также глобально — webpack, babel и другие.

Поэтому другой вариант решения — удалить глобально установленный конфликтующий компонент.

Подключение к html

В вашем html-файле должен быть id, в который будет выводиться результат работы реакта. Например:

<div id="root"></div>

Пример простого кода

import React from 'react';  // импортируем реакт
import ReactDOM from 'react-dom';  // импортируем jsx

const el = (
  <div>
    <h1>My Todo List</h1>
    <input placeholder="search" />
    <ul>
      <li>Learn React</li>
      <li>Build Awesome App</li>
    </ul>
  </div>
);

ReactDOM.render(el,
  document.getElementById('root')); // выводит ваш реакт код в браузер, в <div id="root"></div>

Базовые правила реакта

Импорт библиотек

Вы всегда должны подключить в компонент реакт:

import React from 'react';  // импортируем реакт

а если используете в компоненте jsx-рендеринг, то дополнительно библиотеку react-dom:

import ReactDOM from 'react-dom';  // импортируем jsx

Элементы

Функции, которые возвращают React элемент, должны начинаться с большой буквы

const Header = () => {
  return <h1>Hi, world</h1>
}
ReactDOM.render(<Header />,
  document.getElementById('root'));

Обёртка

Нужно всегда возвращать только один DOM-узел верхнего уровня.

Ошибка

const Header = () => {
  return (
   <h1>Hi, world</h1>
   <p>Hi, user</p>
  )
}

Правильно

const Header = () => {
  return (
    <div>
     <h1>Hi, world</h1>
     <p>Hi, user</p>
    </div>
  )
}

Другой вариант

Чтобы не создавать лишних элементов обёртки, можно использовать React.Fragment

const Header = () => {
  return (
    <React.Fragment>
     <h1>Hi, world</h1>
     <p>Hi, user</p>
    </React.Fragment>
  )
}

JSX

Вы наверняка заметили, что код местами похож на html, но это jsx, который немного отличается от html.. Например, атрибуты пишутся camelCase’ом.

html:

<p style="margin-left: 10px;">Hi, user</p>

jsx:

<p style="marginLeft: 10px;">Hi, user</p>

Запомнить

Некоторые атрибуты пишутся по другому:

  • не «class», а «className»;
  • не «for», а «htmlFor»

Ключи элементов

Для ускорения рендеринга (отрисовки DOM) нужно присваивать уникальные ключи в тех областях элементов, которые будут изменяться

const List = () => {
  return (
  <div>
    <h1>Список участников:</h1>
    <ul>
      <li key='1'>Иванов И.И.</li>
      <li key='2'>Петров П.П.</li>
      <li key='3'>Сидоров С.С.</li>
    </ul>
  </div>
  )
}

Ключи не должны переиспользоваться. Т.е. если вы удаляется элемент с key=’1′, такой ключ не нужно присваивать другому элементу.

Структура React-проекта

  • Компоненты храним в папке components,
  • один компонент — один файл,
  • хороший компонент должен быть независимым

Свойства компонентов (props)

Можно передавать в компоненты свойства и значения.

const Header = props => {
  const {color, fontSize, name} = props;
  return (
    <h1 style={{color, fontSize}}>Hi, {name}</h1>
  )
}
ReactDOM.render(<Header color="red" fontSize= '48px' name="Ivan" />,
  document.getElementById('root')); // выведет заголовок "Hi, Ivan" красного цвета, размером 48px

Передача данных как объекта

const data = { color: 'green', fontSize: '48px', name: 'Ivan'}

const Header = props => {
  const {color, fontSize, name} = props;
  return (
    <h1 style={{color, fontSize}}>Hi, {name}</h1>
  )
}
ReactDOM.render(<Header {...data} />,
  document.getElementById('root'));

Деструктурировать можно при объявлении компонента

const data = { color: 'red', fontSize: '48px', name: 'Ivan'}

const Header = ({color, fontSize, name}) => {
  return (
    <h1 style={{color, fontSize}}>Hi, {name}</h1>
  )
}
ReactDOM.render(<Header {...data} />,
  document.getElementById('root'));

Классы-компоненты

Используются, когда нужно хранить состояние (state).

Передача данных в компонент

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

const data = { color: 'blue', fontSize: '48px', name: 'Ivan'}

class Header extends Component {
  render() {
    const {color, fontSize, name} = this.props;
    return (
      <h1 style={{color, fontSize}}>Hi, {name}</h1>
    )
  }
}

ReactDOM.render(<Header {...data} />,
  document.getElementById('root'));

defaultProps

defaultProps — позволяет установить значение свойств по умолчанию

const Welcome = ({ name }) => (<h1>Hi, {name}</h1>);
Welcome.defaultPrors = { name: 'Masha' };
<Welcome /> // Hi, Masha

В классах

Class Welcome extends Component {
  static defaultProps  = { name: 'Masha' };
  ...
}

Свойство propTypes

Позволяет проверять типы данных в React-компонентах

Class AgeCheck extends Component {
  static defaultProps  = { age: 18 };
  static propTypes = { 
    age = (prop, propName, componentName) => {
      const value = prop[propName];
      if (typeof value === 'number' && !isNaN(value)) {
        return null;
      }
      return new TypeError(`${componentName}: ${propName} must be a number`);
}

Проверка срабатывает после defaultProps и возвращает null, если всё проверка прошла корректно, или возвращает объект Error

Библиотека prop-types

Установка

npm install --save prop-types

Импорт

import PropTypes from 'prop-types'; // ES6

Использование

class MyComponent extends React.Component {
  render() {
    // ... do things with the props
  }
}

MyComponent.propTypes = {
  // You can declare that a prop is a specific JS primitive. By default, these
  // are all optional.
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // Anything that can be rendered: numbers, strings, elements or an array
  // (or fragment) containing these types.
  optionalNode: PropTypes.node,

  // A React element.
  optionalElement: PropTypes.element,
...

Все варианты здесь: https://github.com/facebook/prop-types

Состояние компонента (state)

State — единственных источник значений. Сначала обновляется стэйт, а потом перерисовывается страница.

Создание state:

Вариант 1, в конструкторе

  constructor() {
    super();
    this.state = {
      color: 'red',
      fontSize: '36px',
      name: 'Иван'
    }

Вариант 2. Вероятный будущий синтаксис (на сентябрь 2018 ещё не утверждён окончательно)

state = {
      color: 'red',
      fontSize: '36px',
      name: 'Danila'
    }

После установки state его менять нельзя, но можно устанавливать новые данные в переменные стейта (обновлять).

Достаём данные из state

const {color, fontSize, name} = this.state; // достаём данные из стейта через деструктурирование

Устанавливаем новые значения

this.setState = {
      color: 'blue',
      name: 'Danila'
    }

Обработка событий

Обработчики пишутся camelCase’ом. Пример: `onClick`

class Header extends Component {
  clicked = () => {
      console.log(`You clicked on title`)
  }
  render() {
    return (
      <h1 onClick= { this.clicked }>Hi, world</h1>
    )
  }
}

Обновление состояния в зависимости от предыдущего

Мы хотим, чтобы по клику наша надпись менялась туда / сюда. Но дело в том, что state обновляется не мгновенно, а с небольшой задержкой. Поэтому, проверяя state, нужно убедится, что он уже обновился.

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class Header extends Component {
  state = {
      color: 'red',
      fontSize: '36px',
      name: 'Ivan'
    }
  changeHeader = () => {
    this.setState((state) => {
      let currentName = state.name;
      if (currentName === 'Ivan') {
        return {
          color: 'blue',
          name: 'Danila'
        }  
     }
     else return {
      color: 'red',
      name: 'Ivan'
     }
    });
  }
  render() {
    const {color, fontSize, name} = this.state; // достаём данные из стейта
    return (
      <h1 onClick={this.changeHeader} 
          style={{color: color, fontSize: fontSize}} >Hi, {name}</h1> 
    )
  }
}
ReactDOM.render(<Header />,
  document.getElementById('root'));

Изменение массива или объекта в стейте

Прямое изменение массива в стейте запрещено и считается очень плохой практикой, т.к. может приводить к трудно находимым ошибкам.

Неправильно

state = { arr = [ 1, 2, 3] }
this.setState = { arr[2] = 5; }

Правильно

state = { arr = [ 1, 2, 3] }
this.setState = { return {arr: [1, 2, 5] } }

 

Данные

  • Рекомендуется управлять данными централизовано,
  • если данные требуются в нескольких компонентах, их нужно хранить в родительском;
  • чтобы отправить данные вверх по иерархии используются события

Работа с формами

  • onChange() — получение текущего значения,
  • onSubmit() — «отправка» формы,
  • e.preventDefault() — отмена перезагрузки страницы

Методы жизненного цикла (lifecycle hooks)

  • componentDidMount() — компонент уже подключён, его DOM-элементы уже отображаются. Используется для инициализации (получение данных, работа с DOM).
  • componentDidUpdate() — вызывается после обновления компонента (после render() ). В нём можно запрашивать новые данных для обновлённых элементов. Важно проверить действительно ли обновился state, иначе может быть бесконечный цикл обновления.
  • componentWillUnmount() — компоненты будет удалён, но его DOM ещё на странице. Используется для очистки ресурсов (таймеры, интервалы, запросы к серверу).
  • componentDidCatch() — срабатывает, когда в компоненте, или его детях произошла ошибка. Не обрабатывает ошибки в event listeners и в асинхронном коде. Принимает два аргумента: error и info с дополнительной информацией об источнике ошибок.

Паттерны React’а

  • Функция может инкапсулировать получение данных (и сделать компонент не зависимым от источника данных).
  • Render-функция — возвращает строку или React-элемент. <Header welcome={ () => <p>Hi, Vasya</p> } />
  • Свойства-элементы. В качестве значения свойств можно передать элемент: <Header title={ <h1>Hi, Masha</h1> />. Так можно создавать элементы-контейнеры, или элементы, которые умеют выбирать что рендерить в зависимости от условия.

Ребёнок

Компоненту можно передать одно из свойств, поместив его в тело элемента. <Header>Hi, Alex</Header>.

Это свойство доступно через props.children. Поддерживает любые типы данных: элементы, функции, объекты.

Функция React.Children.map() упрощает обработку props.children:

{ React.Children.map( this.props.children, (child) => {
    // функция обработки child'а.
} }

Клонирование элементов

Реакт-элементы нельзя изменять после того, как они были созданы.

Неправильно

element.new = new; // так нельзя

Правильно

return React.cloneElement(element, new);

Contex.API

const {Provider, Consumer} = React.CreateContext();
// Provider делает доступным value внутри компонентов, которые оборачивает

const Name = () => {
  return 'Lena';
} 

<Provider value = { this.Name } >
  <Component1 />
  ...
  <ComponentN />
</Provider>

Внутри компонента значения извлекаются с помощью Consumer

<Consumer>
  {
    ({ name }) => {
      return (<h1>Hi, {name}</h1>)
    }
  }
</Consumer>

Источники

  1. Юрий Бура. Курс «React + Redux — Профессиональная Разработка» —>
  2. Документация по prop-types на github

Оцените статью
Добавить комментарий