Так же естественно, как дышать: React hooks + RxJS

внешний интерфейс React.js RxJS

На React Conf в прошлом месяце основная команда React впервые представила публике зацепки. Сначала я увидел такую ​​странную вещь и очень сопротивлялся этому. Дэн сказал, что это в JavaScript слишком мрачно, и людям, пришедшим с других языков писать React, будет очень неудобно. Однако хуки — это тоже некая черная магия по своей сути, нужно понимать ее суть и еще нужно очень досконально разбираться в различных замыканиях и скоупах JS.

Однако после нескольких дней работы с хуками идея показалась мне довольно интересной. Во-первых, я рекомендую вступительную речь на React Conf:React Today and Tomorrow and 90% Cleaner React With Hooks, стоит посмотреть.

Наша команда всегда любила RxJS, но его использование в сочетании с React всегда было немного громоздким. на прошлой неделе

@ тоже волкРешил проверить воду на крючках апи. В результате весь день я слышал, как люди вокруг меня кричали: «Это действительно ароматно».

rxjs-hooks

Итак, насколько вкусно писать код RxJS с хуками? Давайте взглянем на этот проект с открытым исходным кодом, который радовал мою маму и открывался снова и снова:LeetCode-OpenSource/rxjs-hooks

rxjs-hooks имеет полные тестовые примеры со 100% покрытием тестами. На данный момент есть только два крючка:useObservableа такжеuseEventCallback. Или объясните прямо на примере, давайте сначала вспомним, как создать, подписаться и уничтожить поток в React Component. Это выглядит так:

import React from 'react';
import { interval } from 'rxjs';
import { tap } from 'rxjs/operators';

class Timer extends React.Component {
  state = {
    val: 0,
  };

  subscription = new Subscription();

  componentDidMount() {
    const sub = interval(1000).pipe(
	  tap((val) => this.setState({ val }))
	)
    this.subscription.add(sub)
  }

  componentWillUnmount() {
    this.subscription.unsubscribe()
  }

  render() {
    return <h1>{this.state.val}</h1>
  }
}

Подписка вручную, управление циклами объявлений вручную и создание моста к функции рендеринга (UI) через состояние в React. Затем после использования rxjs-хуков:

import React from 'react';
import { interval } from 'rxjs';
import { useObservable } from 'rxjs-hooks';

function Timer() {
  const val = useObservable(() => interval(1000), 0);

  return <h1>{val}</h1>
}

Никаких ручных подписок, не нужно беспокоиться об управлении жизненным циклом. просто нужен одинМенее 1 КБ зависимостей, вы можете с радостью использовать RxJS в мире React.

Подробный API

В этом разделе кратко представлены два API в rxjs-hooks с некоторыми примерами. Подробные определения типов могут бытьпосетите здесьПроверить. Нижеследующее будет объяснено на примере, поэтому его будет легче понять.

Уведомление

  • Следующие случаи основаны на RxJS 6
  • Если вы недостаточно знаете о хуках React, рекомендуется посмотреть видео, рекомендованное в начале статьи, илиОфициальный блог React.

1. useObservable

Случай 1: нет значения по умолчанию, нет зависимости от внешнего состояния

function Timer() {
  const val = useObservable(() => interval(1000));

  return <h1>{val}</h1>
}

В этом случае передается только первый параметр, который является фабричной функцией Observable, которая должна возвращать Observable, а возвращаемое значение useObservable всегда является последним значением потока. Только один контент пуст при первом рендеринге<h1>; через 1 секунду содержимое становится0; через 2 секунды содержимое становится1

Случай 2: есть значения по умолчанию

function Timer() {
  const val = useObservable(() => interval(1000), -1);

  return <h1>{val}</h1>
}

Во втором случае мы передаем второй параметр, которыйvalзначение по умолчанию для . Таким образом, в этом случае первое отображаемое содержимое уже не пустое, а-1.

Случай 3: зависит от последнего состояния выполнения

Если вам нужно получить результат последнего вывода в потоке, фабричная функция перейдет вstate$Поток, чтобы помочь вам сделать именно это. (необходимо использовать здесьwithLatestFromобъединить этот поток, иначе это вызовет бесконечный цикл)

function Timer() {
  const val = useObservable((state$) => interval(1000).pipe(
	withLatestFrom(state$),
	map(([index, prevVal]) => index + prevVal),
  ), 0);

  // first render:    0
  // 1s later:        1    (1 + 0)
  // 2s later:        3    (2 + 1)  
  // 3s later:        6    (3 + 3)
  // 4s later:       10    (4 + 6)
  return <h1>{val}</h1>
}

Случай 4: Зависимость от внешнего состояния

Фабричная функция может полагаться на некоторое внешнее состояние, переданное через третий параметр useObservable (иuseEffect,useMemoИнтерфейс похож)

Если передать третий параметр, то в фабричной функции вы получите два потока, которыеinput$а такжеstate$. В приведенном ниже примереinput$Значение, испускаемое потоком, всегда[a, b]кортеж. Чтобы упростить понимание примера, мы не будем использоватьstate$поток.

function Timer({ a }) {
  const [b, setB] = useState(0);
  const val = useObservable(
    (inputs$, _state$) => timer(1000).pipe(
      combineLatest(inputs$),
      map(([_, [a, b]]) => a + b),
    ),
    0,
    [a, b],
  );

  return (
    <div>
      <h1>{val}</h1>
      <button onClick={() => setB(b + 10)}>b: +10</button>
    </div>
  )
}

function App() {
  const [a, setA] = useState(100);

  return (
    <div>
      <Timer a={a} />
      <button onClick={() => setA(a + 100)}>a: +100</button>
    </div>
  );
}

Этот пример относительно сложен и может быть объединен сlive demoпонимать.

2. useEventCallback

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

Сначала посмотрите на следующий пример (live demo), легко увидеть, что возвращаемое значение не совпадает с useEventCallback, оно вернет[callback, value]кортеж. Также принимает фабричную функцию, которая принимаетevent$параметр, всякий разcallbackкогда звонили,event$Поток всегда имеет новое значение, вытекающее наружу. а такжеuseEventCallbackВторой параметр функции по-прежнему является привычным значением по умолчанию.

function App() {
  const [clickCallback, [description, x, y]] = useEventCallback((event$) =>
    event$.pipe(
      map((event) => [event.target.innerHTML, event.clientX, event.clientY]),
    ),
    ["nothing", 0, 0],
  )

  return (
    <div className="App">
      <h1>click position: {x}, {y}</h1>
      <h1>"{description}" was clicked.</h1>
      <button onClick={clickCallback}>click me</button>
      <button onClick={clickCallback}>click you</button>
      <button onClick={clickCallback}>click him</button>
    </div>
  );
}

Больше практических случаев

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

Случай 1: Перетащите меня

live demo

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

live demo

Случай: скользящая очередь

live demo

резюме

слишком далекоrxjs-hooksПросто представь это здесь. Наша реализация не обязательно является лучшим пониманием хуков, и она должна быть хорошей идеей. Мы надеемся, что больше людей в сообществе примут участие в этом изменении, и мы рады поделиться с вами различными путешествиями, с которыми мы столкнулись. В то же время вы можете в любое время отправлять вопросы или PR в этот проект.


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

Вакансии и уровень заработной платы:

  • Бэкенд-инженер Python 20-40 тыс.
  • Младший Front-end Engineer 18к-25к
  • Старший Front-end инженер 25к-45к
  • Full stack инженер 25к-45к

Отправьте свое резюме:Присоединяйтесь к нам - LeetCodeили напишите мне напрямуюjerry@leetcode.com