Skip to content

Latest commit

 

History

History
336 lines (234 loc) · 17.3 KB

09. Лайфсайкл-методы и классовые компоненты по Марксу.md

File metadata and controls

336 lines (234 loc) · 17.3 KB

Лайфсайкл-методы и классовые компоненты по Марксу

Возвращаемся к стейтам и погружаемся немного в ООП чтобы поработать с ними и лайфсайкл-методами

Лайфсайкл-методы

Окей, поняли что интерфейс от простого макета отличает интерактивность: человек может с ним взаимодействовать и фронтэнд должен обрабатывать эти события.

Но что делать, если нам в Реакте нужны события, которые не очень зависят от действий людей? Например, с компонентом что-то произошло и это нужно отследить? Как это сделать?

Для этого есть лайфсайкл-методы компонента: те методы, которые вызываются во время его жизни. Этих всадников апокалипсисов аж целых восемь и из их названий понятно, когда они вызываются:

Маунтинг и анмаутинг

Маунтинг (mounting) это момент, когда компонент, скажем так, собирается. Для этих состояний есть три стейта:

Апдейты

(список расположен по порядку вызова лайфсайклов)

Ошибки

Реакт бросит ошибку, если он не сможет построить интерфейс (например, вы ошиблись в типах и пытаетесь сделать .map() по объекту), но как эту ошибку отловить? Лайфсайклом.

Что с ней потом делать? Можно в стейт поставить error: true и пользователю показать плашку «Сорян, произошла ошибка», а саму ошибку поймать в какой-нибудь сервис типа Sentry или Bugsnag.

// src/Movies/index.js
import React from 'react';
import bugsnag from 'bugsnag';

import Movies from './List';
import { Alert } from '../ui';

class extends React.Component {
  componentDidCatch(error, info) {
    bugsnag.notify(error);
  }

  render() {
    return (
      <section>
        <Alert>
          Произошла ошибка, мы уже отправили сообщение разработчикам
        </Alert>

        <Movies data={data} />
      </section>
    )
  }
}

Кстати, у Багснега есть даже специальный компонент для Error Boundaries — это концепт работы ошибок, который появился в Реакте 16.

Но что это за синтаксис странный? Почему мы написали функцию componentDidCatch() {} без function, да ещё и обернули в class?

Классы 👍🏻

В позапрошлом уроке мы говорили про стейты, но там мы работали с псевдокодом — да, я действительно не показал реальный код, потому что для этого было рано. Сейчас — самое время.

Окей, вот мы работаем с компонентом и всё понятно, когда речь идёт про вёрстку: возвращай через return Джсх, Реакт построит интерфейс из всех компонентов.

Но что делать со стейтами? Или лайфсайклами? Если функции это просто вызываемый кусок кода, то куда это всё впихнуть? Где хранить стейт? Как подписаться на лайфсайклы?

Для этого есть классы — специальные объекты, в которых могут быть методы и поля. Пример?

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  calcTotal() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square); // Rectangle { width: 10, height: 10 }
console.log(square.calcTotal()); // 100

Надо разбираться!


Классы пришли из объекто-ориентированного программирования — подхода, со временем показавшего свою несостоятельность перед функциональным программированием.

Но не верьте мне на слово, а то потом на собеседованиях будете отвечать «ООП говно потому что Родионов так говорит», что, по сути, карго-культ слепой. Попробуйте оба подхода и вернитесь к ФП. Красноречивее всего, конечно, этот слайд из выступления Functional Programming Design Patterns.

Этот слайд говорит о том, что пока люди в ОО придумывают новые паттерны программирования, люди в ФП просто пишут код на функциях и не устраивают баталии «этот код нужно писать на фактори паттерне, а не на визитор!» на код-ревью.

Окей, но у нас есть классы в Реакте как способ инкапсуляции компонента и нам нужно с этим что-то делать. Придётся изучать (хотя совсем недавно вышел @reactions/component и слава Райану, можно уйти от классов).

constructor в нашем примере это по сути такой же лайфсайкл-метод, но не Реакта, а просто класса. В него аргументом приходит то, что вы передали при инициализации класса через new MySuperClass(...). При инициализации создаётся экземпляр класса. Тяжко? Понимаю.

Следующее незнакомое нам это this. this это специальный объект, в котором есть данные, которые принадлежат этому скоупу. Пример с функциями:

(function parent() {
  this.x = 1;
  console.log(this.x); // 1

  (function child() {
    console.log(this.x); // 1
    this.x = 2;
    console.log(this.x); // 2
  })();

  console.log(this.x);
})();

Функции обёрнуты в ()() чтобы их сразу же вызвать — вторыми скобками мы вызываем как будто myFunc(), но сначала нужно обернуть в скобки, чтобы не было function {}() — интерпретатор не поймёт что мы от него хотим и откуда взялись ().


Но почему у вложенной функции на первом консольлоге всё равно доступен this родительской?

Это кложа (closure, на русском обычно говорят "замыкание") — функция со всеми внешними данными, что ей доступны. Второй пример на кложу:

const x = 1;

(function() {
  const y = 2;

  console.log(x); // 1

  (function() {
    const z = 3;

    console.log(y); // 2
    console.log(z); // 3
  })();

  console.log(z); // ReferenceError: z is not defined
})();

console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined

Почему y и z недоступны? Потому что это внутренние константы тех двух функций.


Окей, разобрались с кложами и this, разве что добавлю, что у каждого экземпляра класса свой this:

const square = new Rectangle(10, 10);
const rectangle = new Rectangle(100, 203);

console.log(square); // Rectangle { width: 10, height: 10 }
console.log(rectangle); // Rectangle { width: 100, height: 203 }

Наследование

Второе, что нас интересует в классах — наследование. Наследование делается через синтаксис class extends X {} и наследование означает, что методы и свойства класса X перейдут в новый. Для этого нужно в своём классе вызвать super() в конструкторе.

class X {
  calcTotal() {
    return this.height * this.width;
  }
}

class Rectangle extends X {
  constructor(height, width) {
    super(height, width);
    this.height = height;
    this.width = width;
  }
}

const square = new Rectangle(10, 10);
const rectangle = new Rectangle(100, 203);

console.log(square.calcTotal()); // 100
console.log(rectangle.calcTotal()); // 20300

Окей, вроде всё более-менее понятно, хоть и бесполезно, давайте возвращаться в лоно Реакта и писать нормально.

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

Всё это погружение в классы нужно было, чтобы познакомить вас с классовыми компонентами.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

class WelcomeClassed extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Как видите, разница в случае рендера не особо велика: ну подумаешь, обернули в класс и прописали метод render(), а пропы из аргументов переехали в this.

Кстати, вы же знаете, что каждый раз this будет разным для каждого вызова одного и того же компонента? Теперь знаете.

class Img extends React.Component {
  render() {
    console.log(this.props);

    return <img src={this.props.src} />;
  }
}

<Img src="https://i.imgur.com/wycmlAg.jpg" /> // { src: "https://i.imgur.com/wycmlAg.jpg" }
<Img src="https://i.imgur.com/82kaBeI.jpg" /> // { src: "https://i.imgur.com/82kaBeI.jpg" }

Окей, мы отнаследовались от React.Component, что нам это даёт? Правильно, работу со стейтами и лайфсайклы-методы!

Стейты

Начальный стейт компонента объявляется как поле или в конструкторе

class extends React.Component {
  // стейт объявляется как поле
  state = {}

  // либо в конструкторе, но зачем
  constructor(props) {
    super(props);

    this.state = {};
  }
}

Стейт обновляется через метод this.setState(), в который нужно передать новый стейт. Что? Где он, если мы его не объявили? Явное лучше неявного? Добро пожаловать в ООП, где так не думают!

this.setState() нам подарен экстендом React.Component. Где его можно вызывать? Везде, кроме рендера

Лайфсайклы

Лайфсайклы это методы вашего класса и они вызываются самим Реактом.

// src/Movies/List.js
import React from 'react';
import Card from './Card';
import { Loading } from '../ui';

class Movies extends React.Component {
  // начальный стейт
  state = {
    list: [],
    isLoading: false
  }

  // почему didMount вместо willMount?
  // https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
  componentDidMount() {
    // поставим что мы отправили запрос
    this.setState({ isLoading: true });

    // отправляем запрос к АПИ
    const response = fetch("/api/v1/movies");

    // ставим в стейт ответ
    this.setState({ list: response.list });
  }

  render() {
    // render() вызывается на каждом обновлении стейта или пропов
    // поэтому Реакт сам решит что ему показать
    // мы же описываем интерфейс от стейта, состояния компонента
    return (
      <section>
        {this.state.isLoading && <Loading>...</Loading>}
        {this.state.list.map(movie => {
          return <Card data={movie} />
        });
      </section>
    )
  }
}

Нужно ли вызывать класс через new Movies()? Нет, используйте как обычный компонент через Джсх или React.createElement(), а работу с функциональными или классовыми компонентами Реакт возьмёт на себя.

Итог

Урок получился сложным — это камень в огород ООП. Когда мы вернулись к Реакту, стало всё намного лучше и понятнее.

Сегодня мы разобрались с:

  • лайфсайклами,
  • Error Boundaries,
  • классами,
  • this,
  • кложами (они же замыкания),
  • наследованиями,
  • работой со всем этим в Реакте

Главный вывод: в большинстве случаев нужно использовать функциональные компоненты, а когда нужны стейты или лайфсайкл-методы — классы. Либо смотреть в сторону reactions/component.

Кстати, мы в уроке не разобрались с вопросом а когда всё же нужны стейты или лайфсайклы.

Когда нужны стейты

Когда у компонента должно быть своё состояние. Старайтесь не размазывать по двадцати компонентам то, что можно хранить в одном и спускать пропами, но и свалку не устраивайте.

Когда нужны лайфсайклы

Обычно нужны три лайфсайкла: componentDidMount(), componentWillReceiveProps(nextProps) и componentDidCatch(error, info).

  • componentDidMount() — при загрузке компонента (например, сменили страницу в роутере),
  • componentWillReceiveProps(nextProps) — когда изменились данные (например, сменили страницу в роутере, компонент остался тем же, а данные новые),
  • componentDidCatch(error, info) — когда поймали ошибку.