Эта статья была изначально опубликована в моем блоге.

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

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

Я работаю с React каждый день и постоянно нахожу решения сложных проблем. Я могу это сделать, имея хорошие ментальные модели вокруг React. В этой статье я объясню те ментальные модели, которые помогают мне решать проблемы и преодолевать сложности.

Работаете ли вы с React годами или только начинаете, наличие полезной ментальной модели, на мой взгляд, является самым быстрым способом почувствовать себя уверенно, работая с ней.

Что такое ментальная модель?

Ментальная модель - это то, как мы представляем себе работу системы. Мы создаем его, понимая различные части системы и ее взаимосвязи, и это важно, потому что помогает нам понять мир и помогает нам решать проблемы.

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

Это неполная ментальная модель, но она достаточно хороша, чтобы я мог работать с ней, чтобы решать проблемы и улучшать ее, если мне когда-нибудь понадобится, и в этом суть: ментальные модели призваны помочь нам решать проблемы и понимать мир.

Почему так важны ментальные модели?

Когда я начал создавать веб-сайты в 2014 году, мне было трудно понять, как все это работает. Создать свой блог с помощью WordPress было легко, но я понятия не имел о хостинге, серверах, DNS, сертификатах и ​​многом другом.

Когда я читал статьи и пробовал что-то (и несколько раз ломал конфигурацию сервера), я начал разбираться в системе, чтобы получить представление о том, как все это работает, пока, в конце концов, она не «щелкнула», и я почувствовал себя комфортно с ней работать. Мой разум построил мысленную модель вокруг этой системы, которую я мог использовать для работы с ней.

Если бы кто-то объяснил это, передав мне свою ментальную модель, я бы понял это намного быстрее. Здесь я объясню (и покажу) ментальные модели, которые я использую с React. Это поможет вам лучше понять React и сделает вас лучшим разработчиком.

Реагировать на ментальные модели

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

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

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

Он работает до конца

Начнем с моделирования основных строительных блоков JavaScript и React: functions.

  • Компонент React - это просто функция
  • Компоненты, содержащие другие компоненты, являются функциями, вызывающими другие функции
  • Props - это аргументы функции

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

Давайте рассмотрим каждую часть по отдельности.

Компонент - это функция, возвращающая JSX

React используется с JSX - JavaScript XML - способом писать то, что кажется HTML, со всеми возможностями JavaScript. JSX предлагает отличную прикладную ментальную модель для использования вложенных функций интуитивно понятным способом.

Давайте проигнорируем компоненты класса и сосредоточимся на гораздо более общих функциональных компонентах. Функциональный компонент - это функция, которая ведет себя точно так же, как любая другая функция JavaScript. Компоненты React всегда возвращают JSX, который затем выполняется и превращается в HTML.

Вот как выглядит простой JSX:

const Li = props => <li {...props}>{props.children}</li>;
export const RickRoll = () => (
  <div>
    <div className='wrapper'>
      <ul>
        <Li color={'red'}>Never give you up</Li>
      </ul>
    </div>
  </div>
);

который компилируется в чистый JavaScript с помощью Babel:

const Li = props => React.createElement('li', props, props.children);
export const RickRoll = () =>
  React.createElement(
    'div',
    null,
    React.createElement(
      'div',
      {
        className: 'wrapper',
      },
      React.createElement(
        'ul',
        null,
        React.createElement(
          Li,
          {
            color: 'red',
          },
          'Never give you up',
        ),
      ),
    ),
  );

Если вам сложно следовать этому коду, значит, вы не одиноки и поймете, почему команда React решила использовать JSX.

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

Важной особенностью React является то, что у компонента может быть много дочерних элементов, но только один родитель. Это меня немного сбивало с толку, пока я не понял, что это та же логика, что и в HTML, где каждый элемент должен находиться внутри других элементов и иметь много дочерних элементов. Вы можете заметить это в приведенном выше коде, где есть только один родительский элемент div, содержащий все дочерние элементы.

Свойства компонента совпадают с аргументами функции

При использовании функции мы можем использовать аргументы для обмена информацией с этой функцией. Для компонентов React мы называем эти аргументы props (забавная история, я долгое время не осознавал, что props - это сокращение от properties).

Под капотом свойства props ведут себя в точности как аргументы функций, разница в том, что мы взаимодействуем с ними через более приятный интерфейс JSX, и что React предоставляет дополнительные функции таким свойствам, как children.

Создание ментальной модели вокруг функций

Используя эти знания, давайте создадим мысленную модель для интуитивного понимания функций!

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

function sum(a, b) {
  return a + b;
}
console.log(sum(10, 20)); // 30
function logSum(a, b) {
  console.log(a + b); // 30
}

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

Когда компонент выполняется, он запускает любую имеющуюся у него логику, если таковая имеется, и оценивает свой JSX. Любые теги станут HTML, и любой компонент будет выполнен, и процесс будет повторяться до тех пор, пока не будет достигнут последний компонент в цепочке дочерних элементов.

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

Но ментальная модель блока, представляющего компонент, не будет полной без понимания того, как он может взаимодействовать с другими блоками.

Как думать о закрытии

Замыкания - это основная концепция JavaScript. Они обеспечивают сложную функциональность на языке, их очень важно понимать, чтобы иметь хорошую ментальную модель на основе React.

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

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

В то время как само закрытие представляет собой коробку, любое закрытие будет внутри более крупных коробок, причем самое внешнее поле будет объектом Window.

Но что такое закрытие?

Замыкание - это особенность функций. Если вы используете функцию, вы используете закрытие.

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

Говоря с точки зрения моей ментальной модели: я представляю функции как блоки внутри блоков, и каждое меньшее поле может видеть информацию внешнего блока или родителя, но большое окно не может видеть информацию меньшего. Это самое простое и точное объяснение замыканий, которое я могу дать.

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

Замыкания в React

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

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

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

const Parent = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      The count is {count}
      <div>
        <ChildButtons onClick={setCount} count={count} />
      </div>
    </div>
  );
};
const ChildButtons = props => (
  <div>
    <button onClick={() => props.onClick(props.count + 1)}>
      Increase count
    </button>
    <button onClick={() => props.onClick(props.count - 1)}>
      Decrease count
    </button>
  </div>
);

Когда событие onClick происходит на button, он выполнит функцию, полученную от реквизита props.onClick, и обновит значение с помощью props.count.

Идея здесь заключается в том, как мы обновляем состояние родителя с помощью дочернего элемента, в данном случае функции props.onClick. Причина, по которой это работает, заключается в том, что функция была объявлена ​​ в пределах Parent компонента, в пределах его закрытия, поэтому у нее будет доступ к информации родителя. После того, как эта функция вызывается в дочернем элементе, она все еще находится в том же закрытии.

Это может быть трудно понять, поэтому я представляю это как «туннель» между замыканиями. У каждого есть своя область действия, но мы можем создать односторонний коммуникационный туннель, который соединяет оба.

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

Подгонка состояния React к нашей ментальной модели

Философия React проста: он определяет когда и как визуализировать элементы, а разработчики контролируют что отображать. Государство - это наш инструмент, чтобы решить, что к чему.

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

В моей ментальной модели state похоже на особое свойство внутри коробки. Он не зависит от всего, что в нем происходит. Он получит значение по умолчанию при первом рендеринге и всегда будет соответствовать последнему значению.

Каждая переменная и функция создается при каждом рендеринге, что означает, что их значения также совершенно новые. Даже если значение переменной никогда не меняется, оно каждый раз пересчитывается и переназначается. В случае с state этого не происходит, он изменяется только тогда, когда есть запрос на его изменение через событие set state.

Состояние следует простому правилу: всякий раз, когда оно изменяется, оно будет повторно отображать компонент и его дочерние элементы. Реквизит следует той же логике. Если свойство изменится, компонент будет повторно отрисован. Хотя мы можем управлять state, изменяя его, свойства более статичны и обычно меняются в ответ на изменение состояния.

Ментальная модель визуализации: понимание магии React

Я считаю рендеринг самой запутанной частью React, потому что во время рендеринга происходит много вещей, которые иногда неочевидны, глядя на код. Вот почему помогает четкая ментальная модель.

Я представляю себе рендеринг с моими воображаемыми блоками двояко: первый рендеринг вызывает появление бокса, то есть когда состояние инициализируется. Вторая часть - это когда он перерисовывает, то есть коробка перерабатывается, большая ее часть совершенно новая, но некоторые важные элементы остаются, а именно состояние.

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

const Thumbnail = props => (
  <div>
    {props.withIcon && <AmazingIcon />}
    <img src={props.imgUrl} alt={props.alt} />
  </div>
);

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

Однако при повторных рендерингах состояние не меняется, его значение сохраняется. Вот почему коробка каждый раз «перерабатывается», а не создается заново. Внутренне React отслеживает каждое поле и следит за тем, чтобы его состояние всегда было согласованным. Вот как React знает, когда обновлять компонент.

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

Полная ментальная модель реакции: соединяем все вместе.

Теперь, когда я объяснил все различные части головоломки по отдельности, давайте соберем все вместе. Вот полная ментальная модель, которую я использую для компонентов React, прямо переведенная с того, как я их представляю в словах.

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

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

Коробки полупроницаемые, то есть они никогда ничего не просачиваются наружу, но могут использовать информацию извне, как если бы она принадлежала им. Я использую эту модель, чтобы представить, как работают замыкания в JavaScript.

В React способ обмена информацией между компонентами называется props. . Когда та же идея применяется к функциям, они называются arguments. Обе работают одинаково, но с другим синтаксисом.

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

Я представляю этот направленный обмен информацией в виде прямоугольников внутри прямоугольников. С самым внутренним блоком, имеющим доступ к родительским данным.

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

Последующие отрисовки, или re-renders, снова выполняют весь код в компоненте, пересчитывая переменные, воссоздавая функции и т. Д. Все, кроме state, совершенно новое на каждом рендере. Значение состояния сохраняется во время рендеринга и обновляется только с помощью set метода.

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

Когда коробка перерабатывается, все коробки внутри нее, ее дочерние элементы, также перерабатываются. Это может произойти из-за изменения состояния компонента или изменения свойства.

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

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

React не так уж сложен, если вы начнете понимать основные принципы, лежащие в его основе, и создать некоторые ментальные модели того, как работает ваш код.

Я надеюсь, что эта статья была для вас полезна и ее было так же приятно читать, как и писать! Я понял, что понимаю React интуитивно, и выразить это понимание словами было непросто.

Некоторые пояснения, приведенные в этой статье, являются упрощениями, например, при каждом рендеринге не выполняется повторное выполнение большего количества вещей, таких как хуки useEffect, useCallback и useMemo. Моя полная ментальная модель сложнее, чем я мог бы объяснить в одной статье, поэтому следите за обновлениями частей 2 и 3.

Часть 2 будет посвящена углубленной модели API React, обсуждая useState useEffect и жизненный цикл компонента. Часть 3 будет сосредоточена на высокоуровневых функциях, таких как контекст, и кратком изложении точной и полной ментальной модели, которую я использую для React.

Вы можете обсудить эту статью в Twitter и подписаться на меня в Twitter.