Как связать и использовать компонент более высокого порядка в ReasonReact

Допустим, у меня есть компонент более высокого порядка, что-то вроде следующего тривиального определения, экспортированного из модуля JavaScript ./hoc.js:

export const withStrong =
  Component => props =>
    <strong> <Component ...props/> </strong>

Предполагая, что у меня есть какой-то компонент с именем HelloMessage, что эквивалентно этому фрагменту JavaScript:

import { withStrong } from './hoc.js';

const HelloMessage = ...

const StrongMessage = withStrong(HelloMessage);

ReactDOM.render(
  <StrongMessage name="Joe" />,
  document.getElementById('react-app')
);

person glennsl    schedule 24.08.2019    source источник


Ответы (1)


TL;DR:

Это должно быть точным эквивалентом запрошенного фрагмента JavaScript:

[@bs.module ./hoc.js]
external withStrong
  : React.component('props) => React.component('props)
  = "withStrong";

module HelloMessage = ...

module StrongMessage = {
  include HelloMessage;
  let make = withStrong(make);
};

ReactDOMRe.renderToElementWithId(
  <StrongMessage name="Joe" />,
  "react-app"
);

There'a also a runnable example на игровой площадке Reason с некоторыми адаптациями, сделанными для решения проблемы отсутствия отдельного файла JavaScript.

Объяснение следующее:

Связывание

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

Даже что-то такое простое, как это, сработает

[@bs.module ./hoc.js]
external withStrong : 'a => 'a = "withStrong";

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

В исходном коде ReasonReact указано, что компоненты имеют тип component('props), так что это то, что мы будем использовать.

[@bs.module ./hoc.js]
external withStrong
  : React.component('props) => React.component('props)
  = "withStrong";

Использование переменной типа 'props как в аргументе, так и в возвращаемом типе означает, что мы ограничиваем их, чтобы они были одинаковыми. То есть возвращенный компонент будет иметь точно такие же реквизиты, как и переданный, а это именно то, что нам нужно в данном случае.

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

let strongMessage = withStrong(HelloMessage.make);

К сожалению, это не поддерживает JSX. Чтобы отобразить strongMessage как есть, нам пришлось бы написать что-то вроде

React.createElementVariadic(strongMessage, { "name": "Joe" }, [||]);

Не хорошо. Итак, давайте это исправим.

JSX

<StrongMessage name="Joe" />

преобразовывает в

React.createElementVariadic(
  StrongMessage.make,
  StrongMessage.makeProps(~name="Joe", ()),
  [||]
);

Итак, нам нужен модуль StrongMessage с двумя функциями, make и makeProps, которые соответствуют ожиданиям React.createElementVariadic. make — это просто сам компонент, так что это достаточно просто. makeProps — это функция, которая принимает реквизиты как помеченные аргументы, заканчивающиеся unit (поскольку реквизиты могут быть необязательными) и возвращает объект js. Это также оказывается именно тем, что делает [@bs.obj], чего нет в как-то случайно.

Собрав это вместе, мы получим:

module StrongMessage = {
  let make = withStrong(HelloMessage.make);

  [@bs.obj]
  external makeProps
    : (~name: string, unit) => {. "name" string }
    = "";
}

И это все! Ура!

Дополнение: ярлыки

Итак, функция makeProps немного раздражает. К счастью, в нашем случае, когда реквизиты обернутого компонента такие же, как у оригинала, это также не нужно, поскольку StrongMessage.makeProps будет идентично HelloMessage.makeProps. Тогда давайте просто украдем это! И теперь у нас есть

module StrongMessage = {
  let make = withStrong(HelloMessage.make);
  let makeProps = HelloMessage.makeProps;
}

Но мы можем сделать еще лучше! Используя include HelloMessage, мы можем полностью отказаться от makeProps (спасибо @bloodyowl через @idkjs за это).

module StrongMessage = {
  include HelloMessage;
  let make = withStrong(make);
}

Это довольно мило, не так ли? Это работает, потому что include HelloMessage будет включать все экспортированные определения из HelloMessage, такие как makeProps, а также make и все остальное. Это, вероятно, то, что вам нужно, когда вы заключаете компонент таким образом, но имейте в виду, что он импортирует и реэкспортирует все из включенного модуля, если это не то, что вы хотеть.

использование

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

ReactDOMRe.renderToElementWithId(
  <StrongMessage name="Joe" />,
  "react-app"
);
person glennsl    schedule 24.08.2019
comment
Привет @glennsl, как это выглядит, если ваш HOC возвращает что-то с компонентом, например, состояние авторизации? Или, другими словами, как мне получить доступ к свойствам, которые возвращаются вместе с компонентом? Спасибо. - person armand; 07.10.2019
comment
@idkjs Я не понимаю, что ты имеешь в виду. Можете ли вы указать мне пример в JavaScript? - person glennsl; 07.10.2019