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

1656632059 znakomstvo s povedencheskim programmirovaniem s react posylajte zaprosy zhdite i

Лука Маттейс

NRxUzjzn41Gs0ANQUlaZtj5NhhcTUQ3CHLPa
Изменение поведения компонента подобно добавлению слоев в круг (источник)

Поведенческое программирование (BP) – это парадигма, изложенная в статье Дэвида Гаррела, Ассафа Маррона и Геры Вайс в 2012 году.

Непосредственно по аннотации:

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

Концепция высокого уровня

Сначала я объясню концепции высокого уровня на примере двух компонентов React MoviesList и MoviesCount. На одном отображается список фильмов, на втором – количество имеющихся фильмов. Затем я углублюсь в то, как именно работает поведенческое программирование.

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

<>  <MoviesList />  <MoviesCount /></>

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

const MoviesCountFromList = withBehavior([  function* () {    // block FETCH_COUNT from happening    yield { block: ['FETCH_COUNT'] }  },  function* () {    // wait for FETCH_LIST, requested by the other    // MoviesList component, and derive the count    const response = yield { wait: ['FETCH_LIST'] }    this.setState({      count: response.length    })  }])(MoviesCount)

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

Поскольку мы пытались избежать запуска обоих запросов, мы заблокировали FETCH_COUNT события от инициирования (поскольку те же данные уже были получены FETCH_LIST событие).

<>  <MoviesList />  <MoviesCountFromList /></>

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

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

В остальной статье я подробнее расскажу о том, как работает поведенческое программирование (ПП), в частности в контексте Отреагировать.

Переосмысление процесса программирования

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

const addHotThreeTimes = behavior(  function* () {    yield { request: ['ADD_HOT'] }    yield { request: ['ADD_HOT'] }    yield { request: ['ADD_HOT'] }  })
const addColdThreeTimes = behavior(  function* () {    yield { request: ['ADD_COLD'] }    yield { request: ['ADD_COLD'] }    yield { request: ['ADD_COLD'] }  })
run(  addHotThreeTimes,  addColdThreeTimes)

Когда мы выполняем вышеприведенный код, мы возвращаем список запрошенных событий:

ADD_HOTADD_HOTADD_HOTADD_COLDADD_COLDADD_COLD

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

...
const interleave = behavior(  function* () {    while (true) {      // wait for ADD_HOT while blocking ADD_COLD      yield { wait: ['ADD_HOT'], block: ['ADD_COLD'] }
      // wait for ADD_COLD while blocking ADD_HOT      yield { wait: ['ADD_COLD'], block: ['ADD_HOT'] }    }  })
run(  addHotThreeTimes,  addColdThreeTimes,  interleave)

В вышеприведенном примере мы представляем новое поведение дежурства, которое делает именно то, что нам нужно.

ADD_HOTADD_COLDADD_HOTADD_COLDADD_HOTADD_COLD

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

Процесс подведен на рисунке ниже.

elehAKVL-hHtde0-NJJ4qaxh5-tUIhFvZfmP

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

  • Запрос событие: предложение рассматривать событие для инициирования и запрос уведомления, когда оно инициируется
  • Ждать на событие: без предложения его инициирования, запрос уведомления об инициировании события
  • Блокировка событие: запрет на инициирование события, наложение вето на запросы других b-потоков.

Каждый b-поток (поведенческий поток) обитает сам по себе и не знает других потоков. Но все они переплетены во время исполнения, что позволяет им взаимодействовать друг с другом очень новым способом.

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

Обратно в React

Как эти концепции BP можно использовать в контексте React?

Оказывается, с помощью компонентов высокого порядка (HOC) вы можете добавить эту поведенческую идиому к существующим компонентам очень интуитивно понятным способом:

class CommentsCount extends React.Component {  render() {    return <div>{this.state.commentsCount}</div>  }}
const FetchCommentsCount = withBehavior([  function* () {    yield { request: ['FETCH_COMMENTS_COUNT']}    const comments = yield fetchComments()    yield { request: ['FETCH_COMMENTS_COUNT_SUCCESS']}    this.setState({ commentsCount: comments.length })  },])(CommentsCount)

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

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

Мы могли бы представить весь веб-сайт Netflix как <Netflix /> компонент:

uPWqKfgGgH0PCYqKsvEyybAcWY5rHQwgG1Ax
Скриншот веб-сайта Netflix

Когда мы используем этот компонент в нашем приложении, мы хотим с ним взаимодействовать. В частности, когда щелкнуть фильм, мы не хотим запускать фильм немедленно, а вместо этого мы хотим сделать HTTP-запрос, показать другие данные о фильме, а затем запустить фильм.

Без изменения кода внутри <Netflix /> компонент, я бы утверждал, что этого было бы невозможно достичь, если бы это ни был поведенческий компонент.

Вместо этого представим это <Netflix /> было разработано с помощью поведенческого программирования:

const NetflixWithMovieInfo = withBehavior([  function* () {    // First, block the MOVIE_START from happening     // within <Netflix /> until a new     // FETCH_MOVIE_INFO_SUCCESS event has been requested.    // The yield statement below can be read as:    // wait for FETCH_MOVIE_INFO_SUCCESS while blocking MOVIE_START    yield {       wait: ['FETCH_MOVIE_INFO_SUCCESS'],       block: ['MOVIE_START']     }  },  function* () {    // Here we wait for MOVIE_CLICKED, which is    // triggered within <Netflix />, and we fetch our    // movie info. Once that's done we request a new event    // which the earlier behavior is waiting upon    const movie = yield { wait: ['MOVIE_CLICKED'] }    const movieInfo = yield fetchMovieInfo(movie)    yield {       request: ['FETCH_MOVIE_INFO_SUCCESS'],       payload: movieInfo     }  }])(Netflix)

Выше мы создали новый NetflixWithMovieInfo компонент, изменяющий поведение <Netflix /> (опять же, без изменения исходного кода). Добавление вышеупомянутых моделей поведения делает это следующим that MOVIE_CОблизанный не будет трigger MOVIE_НАЧАТЬ немедленно.

Вместо этого он использует комбинацию «ожидания при блокировке»: a жди и а блокировать можно определить в рамках одного отчета о доходе.

KFzI-ryqH4Y8RJSMj9sQbhlgOewuCahAjVPJ

Изображение выше описывает более подробно происходящее в наших поведенческих компонентах. Каждая маленькая коробка внутри компонентов представляет собой отчет о доходности. Каждая вертикальная пунктирная стрелка представляет поведение (он же b-поток).

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

Так как ничего не блокирует MOVIE_CLICKED , будет запрос. Затем мы можем перейти к следующему оператору yield для поведения Netflix. В следующей точке синхронизации b-поток крайний справа, ожидающий MOVIE_CLICKEDперейдет в следующий отчет о выходе.

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

q1eZUSHheMd3CdPZLyaaMx2EwdScwta7qHeW

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

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

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

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

Вот простая анимация, изображающая способ выполнения и переплетения b-потоков во время выполнения.

vgOkR26vCmjVgmF6-gJpO5Cetiku9SsaqxFE
Порядок выполнения разных потоков

Программирование без изменения старого кода

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

uH6c2Q8UiiNHTtir3GipE69BK9ld6ZTY0NYe
Каждое добавленное поведение изображено с помощью прямоугольника разного цвета

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

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

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

rtjBXCCf8ftal6Utzyt0wX6EIOmvq9k1Qk0n
Каждая колонка слева является b-нитью

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

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

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

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

API не только как атрибуты, но и как события

На данный момент единственный способ для компонента React общаться с внешним миром – это через свойства (кроме Context API).

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

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

События становятся новым API.

Например, в неповеденческом Counter компонент, мы сообщаем внешнему миру, когда счетчик увеличивается и какой текущий подсчет, с помощью onIncrement опора:

class Counter extends React.Component {  state = { currentCount: 0 }  handleClick = () => {    this.setState(prevState => ({      currentCount: prevState.currentCount + 1    }), () => {      this.props.onIncrement(this.state.currentCount)    })  }  render() {    {this.state.currentCount}    <button onClick={this.handleClick}>+</button>  }}
<Counter   onIncrement={(currentCount) =>     console.log(currentCount)  }/>

Что если мы хотим сделать что-то еще до того, как состояние счетчика будет увеличено? Действительно, мы могли бы добавить новый реквизит, например onBeforeIncrementно дело в том, что мы не хотим добавлять атрибуты и рефакторинг кода всякий раз, когда возникает новая специфика.

Если мы превратим это в поведенческий компонент, мы сможем избежать рефакторинга, когда появятся новые спецификации:

class Counter extends React.Component {  state = { currentCount: 0 }  handleClick = () => {    bp.event('CLICKED_INCREMENT')  }  render() {    {this.state.currentCount}    <button onClick={this.handleClick}>+</button>  }}
const BehavioralCounter = withBehavior([  function* () {    yield { wait: ['CLICKED_INCREMENT'] }    yield { request: ['UPDATE_CURRENT_COUNT'] }
    this.setState(prevState => ({      currentCount: prevState.currentCount + 1    }), () => {      this.props.onIncrement(this.state.currentCount)    })  }])(Counter)

Обратите внимание, как изменили логику, когда состояние обновляется внутри b-потока. Кроме того, перед фактическим обновлением происходит новое событие UPDATE_CURRENT_COUNT спрашивается.

Это фактически позволяет другим b-потокам блокировать обновление.

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

// package-name: movies-listexport const function MoviesList() {  ...}
// package-name: movies-list-with-paginationexport const MoviesListWithPagination = pipe(  withBehavior(addPagination))(MoviesList)
// package-name: movies-list-with-pagination-logicexport const MoviesListWithDifferentPaginationLogic = pipe(  withBehavior(changePaginationLogic))(MoviesListWithPagination)

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

Вывод

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

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

<Environment>  <Netflix />  <Twitter />  <WaitForTwitterBeforeNetflix />  <OnTwitterClickShowLoader /></Environment>

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован.