Функциональные vs классовые компоненты

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

Самый простой способ определить компонент в React - написать функцию Javascript.

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

Это просто функция которая принимает props и возвращает элемент React. Но вы также можете использовать синтаксис ES6 для написания компонентов.

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

Обе версии эквивалентны и дадут вам одинаковый результат. Вы можете спросить себя: "Когда мне использовать функцию, а когда классовый компонент?"

1. Различия между функциональными и классовыми компонентами.

Наиболее очевидное различие - это синтаксис. Функциональный компонент - это простая функция JavaScript, которая принимает props в качестве аргумента и возвращает элемент React.

Компонент класса требует, чтобы вы расширили класс React.Component и создали функцию рендеринга, которая возвращает элемент React. Для этого потребуется больше кода.

Если вы посмотрите на транслированный код Babel, вы также увидите некоторые важные отличия:

function Welcome(props) {
    return _react2.default.createElement(
        'h1',
        null,
        'Hello, '
        props.name
    );
}
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Welcome = function (_React$Component) {
  _inherits(Welcome, _React$Component);
function Welcome() {
    _classCallCheck(this, Welcome);
return _possibleConstructorReturn(this, (Welcome.__proto__ || Object.getPrototypeOf(MyComponentClass)).apply(this, arguments));
  }
_createClass(Welcome, [{
    key: "render",
    value: function render() {
      return React.createElement(
        "div",
        null,
        this.props.name
      );
    }
  }]);
return Welcome;
}(React.Component);

2. Передача props.

Передача props может сбивать с толку, но давайте посмотрим, как они написаны как в классовых, так и в функциональных компонентах. Допустим, мы передаем props имени «John», как показано ниже.

<Component name="John" />
const FunctionalComponent = ({ name }) => {
 return <h1>Hello, {name}</h1>;
};

Внутри функционального компонента мы передаем props в качестве аргумента функции. Обратите внимание, что здесь мы используем деструктуризацию. В качестве альтернативы мы можем написать компонент и без нее:

const FunctionalComponent = (props) => {
 return <h1>Hello, {props.name}</h1>;
};

В этом случае вы должны использовать props.name вместо name.

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

Поскольку это класс, вам нужно использовать this для ссылки на props. И, конечно же, мы можем использовать деструктуризацию, чтобы получить имя внутри props, используя компоненты на основе классов.

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

Использование state в функциональных компонентах:

const FunctionalComponent = () => {
 const [count, setCount] = React.useState(0);

 return (
   <div>
     <p>count: {count}</p>
     <button onClick={() => setCount(count + 1)}>Click</button>
   </div>
 );
};

Чтобы использовать state в функциональном компоненте, нам нужно использовать useState хук, который принимает initial state. Мы начинаем с 0 щелчков, поэтому initial state счетчика будет 0.

Конечно, у вас могут быть разные initial state, включая null, строку или даже объект - любой тип, который позволяет JavaScript. А с левой стороны, поскольку useState возвращает текущее состояние и функцию, которая его обновляет, мы деструктурируем массив следующим образом. Если вас немного смущают два элемента массива, вы можете рассматривать их как состояние и его установщик. В этом примере мы назвали их count и setCount, чтобы облегчить понимание связи между ними.

Использование state в классовых компонентах:

class ClassComponent extends React.Component {
 constructor(props) {
   super(props);
   this.state = {
     count: 0
   };
 }

 render() {
   return (
     <div>
       <p>count: {this.state.count} times</p>
       <button onClick={() => this.setState({ count: this.state.count + 1 })}>
         Click
       </button>
     </div>
   );
 }
}

Идея все та же, но компонент класса обрабатывает состояние немного иначе. Во-первых, нам нужно понять важность конструктора React.Component. Вот определение из официальной документации:

«Конструктор компонента React вызывается перед его монтированием. При реализации конструктора для подкласса React.Component вы должны вызывать super(props) перед любым другим оператором. В противном случае this.props будет неопределенным в конструкторе, что может привести к ошибкам ».

По сути, без реализации конструктора и вызова super(props) все переменные состояния, которые вы пытаетесь использовать, будут неопределенными. Итак, давайте сначала определим конструктор. Внутри конструктора вы создадите объект состояния с ключом состояния и начальным значением. А внутри JSX мы используем this.state.count для доступа к значению ключа состояния, который мы определили в конструкторе для отображения счетчика. Сеттер почти такой же, только другой синтаксис.

В качестве альтернативы вы можете написать функцию onClick. Помните, что функция setState принимает аргумент(ы) state, при необходимости props(необязательно).

onClick={() =>
  this.setState((state) => {
    return { count: state.count + 1 };
  })
}

4. Методы жизненного цикла.

Наконец, поговорим о жизненных циклах. Как вы уже знаете, жизненные циклы играют важную роль во время рендеринга. Тем из вас, кто переходит от компонентов класса к функциональным компонентам, должно быть интересно, что может заменить такие методы жизненного цикла, как componentDidMount(), componentWillUnmount() в компоненте класса. И да, есть хук, который идеально подходит для этой цели:

При монтировании (componentDidMount).

Метод жизненного цикла componentDidMount вызывается сразу после завершения первого рендеринга. Раньше был componentWillMount, который происходит до первого рендеринга, но он является устаревшим и не рекомендуется для использования в новых версиях React.

const FunctionalComponent = () => {
 React.useEffect(() => {
   console.log("Hello");
 }, []);
 return <h1>Hello, World</h1>;
};

Заменив componentDidMount, мы используем хук useEffect со вторым аргументом []. Второй аргумент хука useState обычно представляет собой массив состояний, которые меняются, и useEffect будет вызываться только при этих выбранных изменениях. Но когда это пустой массив, как в этом примере, он будет вызываться один раз при монтировании. Это прекрасная замена componentDidMount.

class ClassComponent extends React.Component {
 componentDidMount() {
   console.log("Hello");
 }

 render() {
   return <h1>Hello, World</h1>;
 }
}

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

При размонтировании (componentWillUnmount).

const FunctionalComponent = () => {
 React.useEffect(() => {
   return () => {
     console.log("Bye");
   };
 }, []);
 return <h1>Bye, World</h1>;
};

Мы также можем использовать хук useState для unmount компонента. Но будьте осторожны, синтаксис немного другой. Что вам нужно сделать, так это вернуть функцию, которая запускается при размонтировании внутри функции useEffect. Это особенно полезно, когда вам нужно очистить подписки, такие как функция clearInterval, иначе это может вызвать серьезную утечку памяти в более крупном проекте. Одним из преимуществ использования useEffect является то, что мы можем писать функции для монтирования и размонтирования в одном и том же месте.

class ClassComponent extends React.Component {
 componentWillUnmount() {
   console.log("Bye");
 }

 render() {
   return <h1>Bye, World</h1>;
 }
}

5. Заключение.

У обоих стилей есть свои плюсы и минусы, но я хотел бы сделать вывод, что в обозримом будущем функциональные компоненты вытеснят классовые в React.

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

Также следует отметить, что команда React поддерживает больше хуков React для функциональных компонентов, которые заменяют или даже улучшают компоненты класса. В дальнейшем команда React говорит, что они оптимизируют производительность функциональных компонентов, избегая ненужных проверок и выделения памяти. И как бы многообещающе это ни звучало, недавно были введены новые хуки для функциональных компонентов, таких как useState или useEffect. Команда стремится постепенно внедрять функциональные компоненты с хуками в новых версиях, что означает, что нет необходимости переписывать существующие проекты, в которых используются компоненты классов, на полную совместимость с функциональными компонентами.

Опять же, в React есть много допустимых стилей кодинга. Тем не менее, я предпочитаю использовать функциональные компоненты вместо компонентов класса по причинам, перечисленным выше.

Last updated

Was this helpful?