Задний план

Отладка кода, особенно собственного, иногда может быть утомительной и утомительной. Это может стать еще более неприятным, если вы считаете, что полностью понимаете тонкости и поток самого кода. Конкретным случаем этого сценария является то, что я продолжал «случайно» сталкиваться с ошибкой, когда он не может прочитать свойство null во время моего проекта capstone. Удивительно (или нет), но мой коллега также столкнулся с теми же проблемами, что и я для его побочного проекта. Иногда код компилировался и отображался должным образом, а иногда возникала ошибка, когда он не мог прочитать свойство. Если я и мой коллега столкнулись с одними и теми же ошибками, то это, вероятно, гораздо более распространенное явление, чем ожидалось.

Повторное создание ошибки

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

В первом случае он отображает данные пользователя, чей идентификатор равен 1, в формате JSON. Для справки: эти данные были созданы с использованием Ruby on Rails с соглашениями RESTful. В этом смысле свойства, которые меня действительно волнуют, — это имя, любимая еда и хобби. Я мог бы предотвратить передачу свойств id, created_at и updated_at с помощью сериализатора, но в этом примере это было необязательно.

Во втором случае это интерфейсный код для форматирования и отображения данных, которые будут извлекаться из серверной части (иначе говоря, данные из первого описания). В строках 5–7 я создал экземпляр начального состояния пользователя как null, поскольку данные, которые будут заполнять это состояние, — это пользователь из запроса на выборку. В строках 9–17 метод жизненного цикла componentDidMount() представляет собой функцию для запуска и завершения запроса на выборку для извлечения данных из серверной части (т. е. пользовательских данных с идентификатором 1). В строках 19–28 все, что нужно сделать, это отобразить определенные части информации из ее состояния компонента.

Ошибка Ошибка!

При запуске внешнего кода как есть появляется следующая ошибка:

TypeError: Невозможно прочитать имя свойства null

Ошибка появляется в строке 22, где вызывается this.state.user.name. Отлично, теперь, когда мы воссоздали эту ошибку, пришло время сравнить мыслительный процесс до и после отладки кода.

Начальный мыслительный процесс

Мое первое впечатление об этой ошибке, когда я впервые столкнулся с ней во время моего проекта Capstone, было то, что ошибка была непреднамеренной ошибкой, как в случае ошибки с ошибкой (мета, верно?). Это было связано с тем, что время от времени появлялась ошибка «случайно», препятствуя компиляции и рендерингу моего кода, тогда как в большинстве случаев код компилировался и рендерился как «ожидаемый». Следующие шаги были моими мыслями о том, почему я думал, что ошибка вообще не должна появляться:

  1. Я инициализировал состояние как null, поскольку состояние будет определяться из запроса на выборку (строки 5–7).
  2. Запустите и завершите запрос на выборку, когда компонент смонтируется. Как только запрос на выборку завершен, я могу выполнить setState(), чтобы обновить начальное состояние, которое в этот момент будет нулевым, до полученной информации (строки 9–17.
  3. Теперь, когда у состояния есть вся необходимая информация, теперь я могу выбирать конкретную информацию из состояния и отображать ее соответствующим образом (строки 19–28).

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

Время отладки

Ошибка гласит, что «не удается прочитать свойство «имя» равное нулю». Это появляется в строке 22, поэтому, когда код читает this.state.user.name, он указывает, что this.state.user равно null, исходному состоянию компонента. В качестве быстрой проверки работоспособности я поместил console.log между рендерингом и возвратом (строка 20), чтобы увидеть, в каком состоянии было состояние. На скриншоте ниже показано то, что показано:

В консоли состояние компонента или this.state действительно равно нулю. Но вот что интересно: console.log появляется дважды, что означает, что было два разных рендеринга.

Первый рендеринг console.log происходит при первой загрузке страницы. Это до вызова componentDidMount(). Таким образом, во время этого первого прохода ожидается, что состояние будет нулевым, поскольку начальное состояние было инициализировано как нулевое.

Второй рендеринг console.log происходит после вызова функции setState() в componentDidMount(). Хотя console.log говорит, что состояние равно null, это типично, потому что setState() не сразу обновляет компонент. Более подробное объяснение см. в setState() документации. Чтение документации setState() определенно даст вам более целостное объяснение и понимание этой функции. Это также помогло мне избавиться от привычного представления о том, что код выполняется в линейной последовательности, а не в динамической, нелинейной последовательности.

Хотя я пошел по образовательной касательной, это все равно не помогает смягчить эту ошибку. Основная причина ошибки по-прежнему заключается в том, что состояние не обновляется до вызова функции render(). Это означает, что запрос на выборку не завершен до рендеринга this.state.user.

Ужас асинхронных действий

После размышлений над моим первоначальным мыслительным процессом и нескольких часов стука головой о стол я понял, что основная причина проблемы оказалась прямо передо мной. Это также восходит к основам Javascript. Запрос на выборку – это асинхронный запрос, что означает, что почти или совсем невозможно определить, когда этот запрос будет выполнен. В моем первоначальном мыслительном процессе я продолжал подсознательно думать, что рендеринг будет ждать, пока запрос на выборку не будет завершен, что НЕработает асинхронными действиями.

Поскольку невозможно определить, когда запрос будет выполнен, как мне подойти к отладке этой ошибки? У меня нет контроля над асинхронными действиями, то есть я не могу ускорить выполнение запроса на выборку. Также не помогает то, что у данных всего шесть атрибутов, поэтому нет смысла пытаться уменьшить количество самих данных в запросе на выборку (это тоже не сработает в целях масштабируемости). Однако я действительно контролирую то, что отображается.

Новый мыслительный процесс

До сих пор я знаю, что ошибка появляется, потому что this.state.user все еще равно нулю, когда код попадает в функцию render(). Поскольку запрос на выборку иsetState() являются асинхронными, я не знаю, когда состояние будет обновлено, но Я знаю, что когда он соответствует, он повторно отрисовывает компонент, и в этом случае this.state.user больше не будет нулевым. this.state.user не будет нулевым — безопасное предположение, потому что this.state не является глубоко вложенным объектом.

Похоже, основная проблема сейчас заключается в том, что код должен отображать что-то, что не основано на this.state.user, в течение асинхронного периода времени. Это должно быть рендеринг this.state.user.name или что-то еще. То одно, то другое… Хм… Звучит как задание для оператора if/else! Или, точнее, тернарный оператор!

Например, новый код может выглядеть примерно так: this.state.user ? this.state.user.name : "HEY, I'M STILL WAITING FOR THE ASYNCHRONOUS ACTIONS TO FINISH!" . С логической точки зрения это пока имеет смысл, потому что если this.state.user соответствует действительности, это означает, что this.state был обновлен и может отображать необходимую информацию. Если this.state.user является ложным, это означает, что асинхронные действия (т. е. запрос на выборку и this.stateState()) не завершены, что затем отобразит "HEY, I'M STILL WAITING FOR THE ASYNCHRONOUS ACTIONS TO FINISH!".

Давайте проверим эту теорию в строке 23 и превратим ее в тернарный оператор. Суть ниже показывает это изменение.

Ниже приведен скриншот после реализации тернарного оператора.

И похоже тернарный оператор удался! Ошибка переместилась с this.state.user.name на this.state.user.favorite_food. Давайте реализуем тернарные операторы везде, где вызывается this.state.user.

Эта суть выше отображается без каких-либо ошибок!

ПРИМЕЧАНИЕ. Если вы видите мерцание при запуске этого кода в браузере, это нормально. Это связано с тем, что при первом рендеринге он будет рендерить «ЭЙ, Я ВСЕ ЖДУ ЗАВЕРШЕНИЯ АСИНХРОННЫХ ДЕЙСТВИЙ», и когда состояние обновится, компонент будет повторно рендериться, что является причиной мерцания, с полученная информация из запроса на выборку.

Ключевые вынос

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

Ссылки на гитхаб

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

Серверная часть: https://github.com/guosamuel/rendering_with_asychronous_actions_back_end

Интерфейс: https://github.com/guosamuel/rendering_with_asychronous_actions_front_end