React.js для продолжающих

Содержание

Use HOCs For Cross-Cutting Concerns

Components are the primary unit of code reuse in React. However, you’ll find that some patterns aren’t a straightforward fit for traditional components.


For example, say you have a component that subscribes to an external data source to render a list of comments:

Later, you write a component for subscribing to a single blog post, which follows a similar pattern:

and aren’t identical — they call different methods on , and they render different output. But much of their implementation is the same:

  • On mount, add a change listener to .
  • Inside the listener, call whenever the data source changes.
  • On unmount, remove the change listener.

You can imagine that in a large app, this same pattern of subscribing to and calling will occur over and over again. We want an abstraction that allows us to define this logic in a single place and share it across many components. This is where higher-order components excel.

We can write a function that creates components, like and , that subscribe to . The function will accept as one of its arguments a child component that receives the subscribed data as a prop. Let’s call the function :

The first parameter is the wrapped component. The second parameter retrieves the data we’re interested in, given a and the current props.

When and are rendered, and will be passed a prop with the most current data retrieved from :

Note that a HOC doesn’t modify the input component, nor does it use inheritance to copy its behavior. Rather, a HOC composes the original component by wrapping it in a container component. A HOC is a pure function with zero side-effects.

And that’s it! The wrapped component receives all the props of the container, along with a new prop, , which it uses to render its output. The HOC isn’t concerned with how or why the data is used, and the wrapped component isn’t concerned with where the data came from.

Because is a normal function, you can add as many or as few arguments as you like. For example, you may want to make the name of the prop configurable, to further isolate the HOC from the wrapped component. Or you could accept an argument that configures , or one that configures the data source. These are all possible because the HOC has full control over how the component is defined.

Like components, the contract between and the wrapped component is entirely props-based. This makes it easy to swap one HOC for a different one, as long as they provide the same props to the wrapped component. This may be useful if you change data-fetching libraries, for example.

Прямые манипуляции с DOM

Такого рода ошибка встречается особенно часто среди разработчиков, которые только пересели с jQuery.

Писали ли вы такой код?

В чем же проблема?

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

Что же такого плохого в прямых манипуляциях DOM?

Любое веб-приложение на самом деле строится на состоянии и его управлении. Есть прямая зависимость между сложностью ПО и состоянием, которое его описывает. Если ваше приложение совмещает состояния DOM и React, то сложность его поддержки вырастет очень быстро.

Возможное решение

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

从 Class 迁移到 Hook

生命周期方法要如何对应到 Hook?

  • :函数组件不需要构造函数。你可以通过调用 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 。
  • :改为 安排一次更新。
  • :详见 .
  • :这是函数组件体本身。
  • , , : 可以表达所有这些(包括 的场景)的组合。
  • , 以及 :目前还没有这些方法的 Hook 等价写法,但很快会被添加。

有类似实例变量的东西吗?

有! Hook 不仅可以用于 DOM refs。「ref」 对象是一个 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。

你可以在 内部对其进行写入:

如果我们只是想设定一个循环定时器,我们不会需要这个 ref( 可以是在 effect 本地的),但如果我们想要在一个事件处理器中清除这个循环定时器的话这就很有用了:

从概念上讲,你可以认为 refs 就像是一个 class 的实例变量。除非你正在做 ,否则避免在渲染期间设置 refs —— 这可能会导致意外的行为。相反的,通常你应该在事件处理器和 effects 中修改 refs。

我应该使用单个还是多个 state 变量?

如果你之前用过 class,你或许会试图总是在一次 调用中传入一个包含了所有 state 的对象。如果你愿意的话你可以这么做。这里有一个跟踪鼠标移动的组件的例子。我们在本地 state 中记录它的位置和尺寸:

现在假设我们想要编写一些逻辑以便在用户移动鼠标时改变 和 。注意到我们是如何必须手动把这些字段合并到之前的 state 对象的:

这是因为当我们更新一个 state 变量,我们会 替换 它的值。这和 class 中的 不一样,后者会把更新后的字段 合并 入对象中。

如果你错过自动合并,你可以写一个自定义的 Hook 来合并对象 state 的更新。然而,我们推荐把 state 切分成多个 state 变量,每个变量包含的不同值会在同时发生变化。

举个例子,我们可以把组件的 state 拆分为 和 两个对象,并永远以非合并的方式去替换 :

把独立的 state 变量拆分开还有另外的好处。这使得后期把一些相关的逻辑抽取到一个自定义 Hook 变得容易,比如说:

注意看我们是如何做到不改动代码就把对 这个 state 变量的 调用和相关的 effect 移动到一个自定义 Hook 的。如果所有的 state 都存在同一个对象中,想要抽取出来就比较难了。

把所有 state 都放在同一个 调用中,或是每一个字段都对应一个 调用,这两方式都能跑通。当你在这两个极端之间找到平衡,然后把相关 state 组合到几个独立的 state 变量时,组件就会更加的可读。如果 state 的逻辑开始变得复杂,我们推荐 ,或使用自定义 Hook。

我可以只在更新时运行 effect 吗?

这是个比较罕见的使用场景。如果你需要的话,你可以 手动存储一个布尔值来表示是首次渲染还是后续渲染,然后在你的 effect 中检查这个标识。(如果你发现自己经常在这么做,你可以为之创建一个自定义 Hook。)

如何获取上一轮的 props 或 state?

目前,你可以 来手动实现:

这或许有一点错综复杂,但你可以把它抽取成一个自定义 Hook:

注意看这是如何作用于 props, state,或任何其他计算出来的值的。

考虑到这是一个相对常见的使用场景,很可能在未来 React 会自带一个 Hook。

参见 .

为什么我会在我的函数中看到陈旧的 props 和 state ?

组件内部的任何函数,包括事件处理函数和 effect,都是从它被创建的那次渲染中被「看到」的。例如,考虑这样的代码:

如果你先点击「Show alert」然后增加计数器的计数,那这个 alert 会显示在你点击『Show alert』按钮时的 变量。这避免了那些因为假设 props 和 state 没有改变的代码引起问题。

如果你刻意地想要从某些异步回调中读取 最新的 state,你可以用 来保存它,修改它,并从中读取。

最后,你看到陈旧的 props 和 state 的另一个可能的原因,是你使用了「依赖数组」优化但没有正确地指定所有的依赖。举个例子,如果一个 effect 指定了 作为第二个参数,但在内部读取了 ,它会一直「看到」 的初始值。解决办法是要么移除依赖数组,要么修正它。 这里介绍了 ,而这里介绍了关于如何减少 effect 的运行而不必错误的跳过依赖的 。

我该如何实现 ?

尽管你可能 不需要它,但在一些罕见的你需要用到的场景下(比如实现一个 组件),你可以在渲染过程中更新 state 。React 会立即退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。

这里我们把 prop 上一轮的值存在一个 state 变量中以便比较:

初看这或许有点奇怪,但渲染期间的一次更新恰恰就是 一直以来的概念。

有类似 forceUpdate 的东西吗?

如果前后两次的值相同, 和 Hook 。原地修改 state 并调用 不会引起重新渲染。

通常,你不应该在 React 中修改本地 state。然而,作为一条出路,你可以用一个增长的计数器来在 state 没变的时候依然强制一次重新渲染:

可能的话尽量避免这种模式。

我该如何测量 DOM 节点?

获取 DOM 节点的位置或是大小的基本方式是使用 。每当 ref 被附加到一个另一个节点,React 就会调用 callback。这里有一个 小 demo:

在这个案例中,我们没有选择使用 ,因为当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们。使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。

注意到我们传递了 作为 的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。

In this example, the callback ref will be called only when the component mounts and unmounts, since the rendered component stays present throughout any rerenders. If you want to be notified any time a component resizes, you may want to use or a third-party Hook built on it.

如果你愿意,你可以 把这个逻辑抽取出来作为 一个可复用的 Hook:

Conclusion

That’s it. I tried to write this article for both React Native newcomers and developers who are familiar with React Native or React. In case of any questions, feel free to ask me in the comment section below.

Below is a short cheat sheet summarising the article and pointing out the principles of each life-cycle method:

constructor 

DO

  • Assign the initial state to this.state directly
  • If not using class properties syntax — prepare all class fields and bind functions that will be passed as callbacks

DON’T

  • Cause any side effects (AJAX calls, subscriptions, etc.)
  • Call setState()
  • Copy props into state (only use this pattern if you intentionally want to ignore prop updates)

render


DO

  • Return a valid Javascript value
  • The render() function should be pure

DON’T

Call setState()

componentDidMount

DO

  • Set up subscriptions
  • Network requests
  • You may setState() immediately (use this pattern with caution, because it often causes performance issues)

DON’T

Call this.setState as it will result in a re-render

componentDidUpdate

DO

  • Network requests (e.g. a network request may not be necessary if the props have not changed)
  • You may call setState() immediately in componentDidUpdate(), but note that it must be wrapped in a condition

DON’T

Call this.setState, as it will result in a re-render

shouldComponentUpdate

DO

Use to increase performance of components

DON’T

  • Cause any side effects (AJAX calls etc.)
  • Call this.setState

componentWillUnmount

DO

Remove any timers or listeners created in the lifespan of the component

DON’T

Call this.setState, start new listeners or timers

static getDerivedStateFromError()

DO

Catch errors and return them as state objects

DON’T

Cause any side effects

componentDidCatch

DO

  • Side effects are permitted
  • Log errors

DON’T

Render a fallback UI with componentDidCatch() by calling setState (use static getDerivedStateFromError() to handle fallback rendering).

Состояние и setState()

Большинство из вас скорее всего уже использовали состояния React, они были даже в примере с КВП

Однако важно понимать, что при смене состояния React запустит процесс повторного отображения компонента (если не указать иное в )

Поговорим о том, как можно изменить состояние. Единственный способ сделать это — использовать метод . Он принимает объект и объединяет его с текущим состоянием. Помимо этого есть ещё несколько вещей, которые следует знать.

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

Взглянув на эту картинку можно увидеть, что мы сначала вызываем , а затем . Новое значение переменной должно быть равно 1, однако выводится 0. Что если мы хотим получить доступ к новому состоянию после того, как действительно обновит состояние?

Тут мы плавно переходим к следующей вещи, которую нужно знать о — этот метод может принимать callback-функцию. Исправим наш код:

Отлично, всё работает, на этом можно закончить, да? Не совсем. На самом деле, в данном случае мы не используем корректно. Вместо передачи объекта мы передадим методу функцию. Так обычно делается, когда для установки нового состояния используется текущее. Если это не ваш случай, то смело продолжайте передавать объект в . Исправим код ещё раз:

Вот CodePen для этого примера.

В чём смысл передачи функции вместо объекта? Поскольку  асинхронный, полагаться на него при создании нового значения немного рискованно. Например, к моменту запуска одного  другой  мог уже обновить состояние. Передача функции в  даёт два преимущества:

  1. Это позволяет создавать статичную копию состояния, которое никогда не изменится само по себе.
  2. Это поместит вызовы  в очередь, поэтому они будут запускаться по порядку.

Посмотрим на следующий пример, в котором попытаемся увеличить счётчик на 2, используя два последовательных вызова :

Сравните с кодом ниже:

CodePen для этого кода.

На первом изображении обе функции  напрямую используют , который, как мы узнали ранее, останется со значением 0 после вызова первого . Таким образом, конечное значение равно 1, а не 2, так обе функции  устанавливают значение счётчика равным 1.

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

Вот и всё, что вам нужно знать о состояниях React.

Правильное использование состояния

Есть три детали о , про которые нужно знать.

Не изменяйте напрямую состояние

Например, это не приведёт к повторной отрисовке компонента:

Вместо этого используйте :

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

Обновления состояния могут быть асинхронными

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

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

Например, этот код может не обновить счётчик:

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

Мы использовали стрелочную функцию выше, но это также работает с обычными функциями:

Обновления состояния объединяются

Когда вы вызываете , React объединяет объект, который вы предоставляете c текущим состоянием.

Например, ваше состояние может содержать несколько независимых переменных:


Затем вы можете самостоятельно их обновлять с помощью отдельных вызовов :

Слияние происходит поверхностное, поэтому вызов оставляет нетронутым, но полностью заменяет .

HOC для сквозной функциональности

Традиционные компоненты подразумевают многократное использование, но не позволяют с лёгкостью решить некоторые проблемы.

Рассмотрим пример , который получает список комментариев из внешнего источника данных и отображает их:

Теперь мы решили реализовать новый компонент, который отслеживает изменения конкретной публикации и повторяет уже знакомый нам шаблон:

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

  • Оба компонента подписываются на оповещения от  при монтировании.
  • Оба меняют внутреннее состояние при изменении .
  • Оба отписываются от  при размонтировании.

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

Давайте реализуем функцию  — она будет создавать компоненты и подписывать их на обновления (наподобие и ). Функция будет принимать оборачиваемый компонент и через пропсы передавать ему новые данные:

Первый параметр — это оборачиваемый компонент. Второй — функция, которая извлекает нужные нам данные, она получает и текущие пропсы.

Когда и  рендерятся, они передают в  и  обновлённые данные через проп :

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

Вот и всё! Оборачиваемый компонент получает все пропсы, переданные контейнеру, а также проп

Для HOC не важно, как будут использоваться данные, а оборачиваемому компоненту не важно, откуда они берутся

Так как  — это обычная функция, то мы можем убрать или добавить любое количество аргументов. Например, мы могли бы сделать конфигурируемым название пропа и ещё больше изолировать HOC от оборачиваемого компонента. Также мы можем добавить аргумент для конфигурации или источника данных. Всё это возможно, потому что HOC полностью контролирует процесс создания компонента.

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

Convention: Maximizing Composability

Not all HOCs look the same. Sometimes they accept only a single argument, the wrapped component:

Usually, HOCs accept additional arguments. In this example from Relay, a config object is used to specify a component’s data dependencies:

The most common signature for HOCs looks like this:

What?! If you break it apart, it’s easier to see what’s going on.

In other words, is a higher-order function that returns a higher-order component!

This form may seem confusing or unnecessary, but it has a useful property. Single-argument HOCs like the one returned by the function have the signature . Functions whose output type is the same as its input type are really easy to compose together.

(This same property also allows and other enhancer-style HOCs to be used as decorators, an experimental JavaScript proposal.)

The utility function is provided by many third-party libraries including lodash (as ), Redux, and .

性能优化

在依赖列表中省略函数是否安全?

一般来说,不安全。

要记住 effect 外部的函数使用了哪些 props 和 state 很难。这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。 这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值:

如果这样之后我们依然没用到组件作用域中的任何值,就可以安全地把它指定为 :

根据你的用例,下面列举了一些其他的办法。

让我们来看看这有什么关系。

如果你指定了一个 作为 、、 或 的最后一个参数,它必须包含回调中的所有值,并参与 React 数据流。这就包括 props、state,以及任何由它们衍生而来的东西。

只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。下面这个案例有一个 Bug:

推荐的修复方案是把那个函数移动到你的 effect 内部。这样就能很容易的看出来你的 effect 使用了哪些 props 和 state,并确保它们都被声明了:

这同时也允许你通过 effect 内部的局部变量来处理无序的响应:

我们把这个函数移动到 effect 内部,这样它就不用出现在它的依赖列表中了。

如果处于某些原因你 无法 把一个函数移动到 effect 内部,还有一些其他办法:

  • 你可以尝试把那个函数移动到你的组件之外。那样一来,这个函数就肯定不会依赖任何 props 或 state,并且也不用出现在依赖列表中了。
  • 如果你所调用的方法是一个纯计算,并且可以在渲染时调用,你可以 转而在 effect 之外调用它, 并让 effect 依赖于它的返回值。
  • 万不得已的情况下,你可以 把函数加入 effect 的依赖但 把它的定义包裹 进 Hook。这就确保了它不随渲染而改变,除非 它自身 的依赖发生了改变:

注意在上面的案例中,我们 需要 让函数出现在依赖列表中。这确保了 的 prop 的变化会自动触发 的重新获取。

如果我的 effect 的依赖频繁变化,我该怎么办?

有时候,你的 effect 可能会使用一些频繁变化的值。你可能会忽略依赖列表中 state,但这通常会引起 Bug:

传入空的依赖数组 ,意味着该 hook 只在组件挂载时运行一次,并非重新渲染时。但如此会有问题,在 的回调中, 的值不会发生变化。因为当 effect 执行时,我们会创建一个闭包,并将 的值被保存在该闭包当中,且初值为 。每隔一秒,回调就会执行 ,因此, 永远不会超过 1。

指定 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。事实上,每个 在被清除前(类似于 )都会调用一次。但这并不是我们想要的。要解决这个问题,我们可以使用 。它允许我们指定 state 该 如何 改变而不用引用 当前 state:

( 函数的身份是被确保稳定的,所以可以放心的省略掉)

此时, 的回调依旧每秒调用一次,但每次 内部的回调取到的 是最新值(在回调中变量命名为 )。

在一些更加复杂的场景中(比如一个 state 依赖于另一个 state),尝试用 把 state 更新逻辑移到 effect 之外。这篇文章 提供了一个你该如何做到这一点的案例。 的 的身份永远是稳定的 —— 即使 reducer 函数是定义在组件内部并且依赖 props。

万不得已的情况下,如果你想要类似 class 中的 的功能,你可以 来保存一个可变的变量。然后你就可以对它进行读写了。举个例子:

仅当你实在找不到更好办法的时候才这么做,因为依赖于变更会使得组件更难以预测。如果有某些特定的模式无法很好地转化成这样,发起一个 issue 并配上可运行的实例代码以便,我们会尽可能帮助你。

我该如何实现 ?

你可以用 包裹一个组件来对它的 props 进行浅比较:

这不是一个 Hook 因为它的写法和 Hook 不同。 等效于 ,但它只比较 props。(你也可以通过第二个参数指定一个自定义的比较函数来比较新旧 props。如果函数返回 true,就会跳过更新。)

不比较 state,因为没有单一的 state 对象可供比较。但你也可以让子节点变为纯组件,或者 。

如何记忆计算结果?

Hook 允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果:

这行代码会调用 。但如果依赖数组 自上次赋值以来没有改变过, 会跳过二次调用,只是简单复用它上一次返回的值。

记住,传给 的函数是在渲染期间运行的。不要在其中做任何你通常不会在渲染期间做的事。举个例子,副作用属于 ,而不是 。

你可以把 作为一种性能优化的手段,但不要把它当做一种语义上的保证。未来,React 可能会选择「忘掉」一些之前记住的值并在下一次渲染时重新计算它们,比如为离屏组件释放内存。建议自己编写相关代码以便没有 也能正常工作 —— 然后把它加入性能优化。(在某些取值必须 从不 被重新计算的罕见场景,你可以 一个 ref。)

方便起见, 也允许你跳过一次子节点的昂贵的重新渲染:

注意这种方式在循环中是无效的,因为 Hook 调用 不能 被放在循环中。但你可以为列表项抽取一个单独的组件,并在其中调用 。

如何惰性创建昂贵的对象?

如果依赖数组的值相同, 允许你 。但是,这仅作为一种提示,并不 保证 计算不会重新运行。但有时候需要确保一个对象仅被创建一次。

第一个常见的使用场景是当创建初始 state 很昂贵时:

为避免重新创建被忽略的初始 state,我们可以传一个 函数 给 :


React 只会在首次渲染时调用这个函数。参见 。

你或许也会偶尔想要避免重新创建 的初始值。举个例子,或许你想确保某些命令式的 class 实例只被创建一次:

不会 像 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的:

这避免了我们在一个对象被首次真正需要之前就创建它。如果你使用 Flow 或 TypeScript,你还可以为了方便给 一个不可为 null 的类型。

Hook 会因为在渲染时创建函数而变慢吗?

不会。在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别。

除此之外,可以认为 Hook 的设计在某些方面更加高效:

  • Hook 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。
  • 符合语言习惯的代码在使用 Hook 时不需要很深的组件树嵌套。这个现象在使用高阶组件、render props、和 context 的代码库中非常普遍。组件树小了,React 的工作量也随之减少。

传统上认为,在 React 中使用内联函数对性能的影响,与每次渲染都传递新的回调会如何破坏子组件的 优化有关。Hook 从三个方面解决了这个问题。

  • Hook 允许你在重新渲染之间保持对相同的回调引用以使得 继续工作:

  • Hook 使得控制具体子节点何时更新变得更容易,减少了对纯组件的需要。
  • 最后, Hook 减少了对深层传递回调的依赖,正如下面解释的那样。

如何避免向下传递回调?

我们已经发现大部分人并不喜欢在组件树的每一层手动传递回调。尽管这种写法更明确,但这给人感觉像错综复杂的管道工程一样麻烦。

在大型的组件树中,我们推荐的替代方案是通过 context 用 往下传一个 函数:

内部组件树里的任何子节点都可以使用 函数来向上传递 actions 到 :

总而言之,从维护的角度来这样看更加方便(不用不断转发回调),同时也避免了回调的问题。像这样向下传递 是处理深度更新的推荐模式。

注意,你依然可以选择是要把应用的 state 作为 props 向下传递(更显明确)还是作为作为 context(对很深的更新而言更加方便)。如果你也使用 context 来向下传递 state,请使用两种不同的 context 类型 —— context 永远不会变,因此组件通过读取它就不需要重新渲染了,除非它们还需要应用的 state。

如何从 读取一个经常变化的值?

在某些罕见场景中,你可能会需要用 记住一个回调,但由于内部函数必须经常重新创建,记忆效果不是很好。如果你想要记住的函数是一个事件处理器并且在渲染期间没有被用到,你可以 来用,并手动把最后提交的值保存在它当中:

这是一个比较麻烦的模式,但这表示如果你需要的话你可以用这条出路进行优化。如果你把它抽取成一个自定义 Hook 的话会更加好受些:

无论如何,我们都 不推荐使用这种模式 ,只是为了文档的完整性而把它展示在这里。相反的,我们更倾向于 。

React Function Component: Event Handler

In the previous example you have used an onChange event handler for the input field. That’s appropriate, because you want to be notified every time the internal value of the input field has changed. In the case of other HTML form elements, you have several other React event handlers at your disposal such as onClick, onMouseDown, and onBlur.

Note: The onChange event handler is only one of the handlers for HTML form elements. For instance, a button would offer an onClick event handler to react on click events.

So far, we have used an arrow function to inline the event handler for out input field. What about extracting it as standalone function inside the component? It would become a named function then:

import React,{ useState }from'react';

constApp=()=>{

return<Headline/>;

};

constHeadline=()=>{

constgreeting, setGreeting=useState(

'Hello Function Component!'

);

consthandleChange=event=>setGreeting(event.target.value);

return(

<div>

<h1>{greeting}</h1>

<inputtype="text"value={greeting}onChange={handleChange}/>

</div>

);

};

exportdefault App;

We have used an arrow function to define the function within the component. If you have used class methods in React Class Components before, this way of defining functions inside a React Function Component is the equivalent. You could call it the «React Function Component Methods»-equivalent to class components. You can create or add as many functions inside the Functional Component as you want to act as explicit event handlers or to encapsulate other business logic.

Советы по использованию эффектов¶

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

Совет: используйте разные хуки для разных задач

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

Обратите внимание, что логика, которая устанавливает разбита между и. Логика подписки также раскидана между и

А метод включает в себя логику для обеих задач.

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

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

Объяснение: почему эффекты выполняются при каждом обновлении

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

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

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

В классовом компоненте нам бы пришлось добавить , чтобы решить эту задачу:

Не использовать надлежащим образом — это один из самых распространённых источников багов в приложениях React.

Теперь давайте рассмотрим версию этого же компонента, но уже написанного с использованием хуков:

Этого бага в данном компоненте нет. (Но мы и не изменили там ничего)

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

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

Совет: оптимизация производительности за счёт пропуска эффектов

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

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

В этом примере, мы передаём вторым аргументом. Что это вообще значит? Это значит, что если будет равен и наш компонент повторно рендерится с тем же значением = , React сравнит из предыдущего рендера и из следующего рендера. Так как, все элементы массива остались без изменений (), React пропустит этот эффект. Это и есть оптимизация данного процесса.

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

Это также работает для эффектов с этапом сброса:

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

Следующие шаги¶

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

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

На этом этапе, вы, возможно, задаётесь вопросом, как хуки работают в целом. Как React понимает, какая переменная состояния соответствует какому вызову между повторными рендерами? Как React «сопоставляет» предыдущие и следующие эффекты при каждом обновлении? На следующей странице, мы узнаем о правилах хуков, так как они являются залогом правильной работы хуков.

Непонимание Webpack

Некоторые из младших разработчиков, с которыми я работал, знали, как использовать, но не понимали, как работает Webpack. Они использовали только лишь с основной кодовой базой проекта и считали, что все остальное «работает просто потому что». Они не копали глубже, не выясняли, как именно CSS и ES6, который они пишут, трансформируется и объединяется в то, что в конечном итоге используется клиентским браузером.

Я рекомендую каждому React-разработчику выделить время и построить простой шаблонный проект. Вместо того, чтобы каждый раз полагаться на create-react-app и NextJS, разберитесь, как современные инструменты сборки JavaScript работают вместе. Это улучшит ваше понимание своей работы и, как следствие, сделает вас более эффективным разработчиком, особенно при решении проблем со сборкой.

Pure React Function Component

React Class Components offered the possibility to decide whether a component has to rerender or not. It was achieved by using the PureComponent or shouldComponentUpdate to avoid performance bottlenecks in React by preventing rerenders. Let’s take the following extended example:

import React,{ useState }from'react';

constApp=()=>{

constgreeting, setGreeting=useState('Hello React!');

constcount, setCount=useState();

consthandleIncrement=()=>

setCount(currentCount=> currentCount +1);

consthandleDecrement=()=>

setCount(currentCount=> currentCount -1);

consthandleChange=event=>setGreeting(event.target.value);

return(

<div>

<inputtype="text"onChange={handleChange}/>

<Countcount={count}/>

<buttontype="button"onClick={handleIncrement}>
        Increment
</button>

<buttontype="button"onClick={handleDecrement}>
        Decrement
</button>

</div>

);

};

constCount=({ count })=>{

  console.log('Does it (re)render?');

return<h1>{count}</h1>;

};

exportdefault App;

In this case, every time you type something in the input field, the App component updates its state, rerenders, and rerenders the Count component as well. React memo — which is one of React’s top level APIs — can be used for React Function Components to prevent a rerender when the incoming props of this component haven’t changed:

import React,{ useState, memo }from'react';

constApp=()=>{

constgreeting, setGreeting=useState('Hello React!');

constcount, setCount=useState();

consthandleIncrement=()=>

setCount(currentCount=> currentCount +1);

consthandleDecrement=()=>

setCount(currentCount=> currentCount -1);

consthandleChange=event=>setGreeting(event.target.value);

return(

<div>

<inputtype="text"onChange={handleChange}/>

<Countcount={count}/>

<buttontype="button"onClick={handleIncrement}>
        Increment
</button>

<buttontype="button"onClick={handleDecrement}>
        Decrement
</button>

</div>

);

};

const Count =memo(({ count })=>{

  console.log('Does it (re)render?');

return<h1>{count}</h1>;

});

exportdefault App;

Now, the Count component doesn’t update anymore when the user types something into the input field. Only the App component rerenders. This performance optimization shouldn’t be used as default though. I would recommend to check it out when you run into issues when the rerendering of components takes too long (e.g. rendering and updating a large list of items in a Table component).

Bileşen Nesnesinin Değişkenleri

, bu değişkeni çağıran eleman tarafından tanımlanan prop değerlerini içerir. Prop’lara giriş olması açısından fazla bilgi için Bileşenler ve Prop’lar yazısını inceleyebilirsiniz.

Bilhassa, özel bir prop’tur. Genellikle etiketin kendisi yerine JSX ifadesindeki alt etiketler tarafından tanımlanır.

State, zaman içerisinde değişim gösterebilen ve ilgili değişkene özgü olan verileri tutar. State değişkeni kullanıcı tarafından tanımlanır, ve düz bir JavaScript nesnesi olmalıdır.

Eğer state’te tanımlanan bazı değerler, render işleminde veya zamanlayıcı ID’sinin tutulması gibi veri akışına yönelik işlemlerde kullanılmıyorsa, bu değişkenler state içerisine konulmamalıdır. Bu değerler, bileşen nesnesinde değişken olarak tanımlanabilirler.

State hakkında daha fazla bilgi için State ve Lifecycle sayfasına göz atabilirsiniz.

‘i direkt olarak değiştirmeyiniz. Çünkü daha sonra yapılan çağrıları, yaptığınız değişikliklerin üzerine yazabilir. Bu nedenle ‘e immutable’mış gibi davranmalısınız.


С этим читают