Здесь очень много частей, но я собрал некоторые ссылки, работающие от самого низкого уровня до самого высокого, при освещении вопросов.
Первыми и наиболее важными определениями являются компоненты React и элементы реагирования. This page содержит подробное объяснение. Я рекомендую полностью пропустить раздел «Управление экземплярами», поскольку он смешивает воды, описывая традиционную модель пользовательского интерфейса при использовании терминов по-разному так, как они используются в React. В целом я понимаю, что:
Компонент React - это общая концепция, которая может быть реализована несколькими способами. Однако он реализован, по сути, это функция из реквизита (и необязательно) для содержимого страницы - средство визуализации.
A React Element - это описание содержимого страницы, представляющее конкретный рендеринг компонента.
React components and props docs Опишите два способа определения компонента React, первый из которых - это функция от реквизита к реагирующему элементу, это тот, который нас интересует. Документы React.createFactory затем подтверждают, что мы можем передать такую функцию для созданияFactory. Насколько я могу судить, это существует для того, чтобы адаптироваться от множества способов определения компонента React (путем подкласса React.Component или React.PureComponent, используя React.createClass или с помощью функции от Props to ReactElement) до способа рендеринга реквизита для ReactElement. Мы можем увидеть что-то об этом, посмотрев на это gist, представляя React.createFactory в React 0.12 - по существу они хотели ввести некоторую абстракцию между классом, используемым для определения компонента React и возможной функцией из реквизита для React Elements, который используется при рендеринге, а не просто позволить классу оказывать реквизиты напрямую.
Далее у нас есть небольшая морщина - React.createFactory
помечен как наследие в документах. К счастью, это не является серьезной проблемой, опять же, насколько я могу судить, React.createFactory(type)
просто производит функцию f(props)
, которая идентична React.createElement(type, props)
- мы просто фиксируем аргумент type
в React.createElement
. Я испытал это в реакции трёхсортном-специальной оболочкой, и мы можем использовать createElement вместо createFactory:
val componentFunction = js.Dynamic.global.SortableContainer(wrappedComponent.factory)
(props) => (wrappedProps) => {
val p = props.toJS
p.updateDynamic("v")(wrappedProps.asInstanceOf[js.Any])
React.asInstanceOf[js.Dynamic].createElement(componentFunction, p).asInstanceOf[ReactComponentU_]
}
Мы теперь почти на вопрос 2). Если мы посмотрим на the source for SortableElement, мы увидим, что функция sortableElement
принимает аргумент WrappedComponent
- это используется для создания другого компонента React, с помощью подхода подкласса React.Component. В функции render
этого класса видно, что WrappedComponent
используется как компонент React, поэтому мы знаем, что это действительно компонент, даже без статического типа :) Это означает, что WrappedComponent должен быть чем-то принят React.createElement
, поскольку это то, что JSX component use desugars to.
Поэтому мы знаем, что нам нужно передать функции sortableElement
что-то, что можно использовать в качестве компонента React в функции javascript React.createElement
. Глядя на scalajs-react types doc, мы видим, что ReactComponentC выглядит как хорошая ставка - он конструирует компоненты, предположительно из реквизита. Смотря at the source for this, мы видим, что у нас есть два перспективных значения - reactClass
и factory
. На этом этапе я понимаю, что код, вероятно, использует неправильный - я пробовал заменить .factory
на .reactClass
, и это все еще работает, но имеет гораздо больше смысла, поскольку у нас есть комментарий, чтобы сообщить нам, что он дает Output of [[React.createClass()]]
, который является одним из варианты действительного компонента React. Я подозреваю, что factory
также работает, по существу, обертывая предоставленный компонент в createFactory дважды, поскольку вывод createFactory также можно использовать в качестве его ввода ... Я думаю, что с учетом этой поправки мы ответили на вопрос 2 :) Это также в значительной степени отвечает на вопрос 1a) - ReactComponentC
- это признак scala, который дает нам .reactClass
val, который нам нужен для Scala-defined React Component. Мы только заботимся о типе реквизита, который он использует (поскольку мы должны их предоставить), следовательно, P
. Поскольку scala IS typed мы знаем, что это то, что мы получаем от создания Scala React Component в обычном режиме (по крайней мере для компонентов, которые я пробовал).
На вопрос 1b), я нашел тип ReactComponentU_
из кода вроде ReactCssTransitionGroup
фасада в scalajs-react Addons и scalajs-react-components notes on interop, который показывает упаковку компонента не-HOC. Посмотрев на сам тип, мы видим, что он расширяет ReactElement
, что имеет смысл - это ожидаемый результат рендеринга компонента React.В нашей функции wrap
на фасадах SortableElement
и SortableContainer
мы производим (в конечном итоге) еще одну функцию от реквизита до ReactElement, только один, который перескакивает через несколько обручей, чтобы добраться туда с помощью HOC-подхода. Я не уверен, почему ReactComponentU_
используется вместо ReactElement
. Я думаю, что это связано с отслеживанием состояния компонентов через тип, но код все еще компилируется, если я возвращаю ReactElement
, что является нечетным.
Вопрос 3) намного проще - scalajs-реагирует на работу с реквизитами, которые могут быть Ints, Longs и т. Д., Но в Javascript это не объекты. Чтобы каждый реквизит компонента scalajs был объектом, scalajs-react обертывает их в объект, например {"v": props}
, а затем снова распаковывается, когда они используются. Когда мы завершаем компонент Scala React с помощью HOC, нам нужно каким-то образом получить реквизиты завернутого компонента. Компонент, созданный функцией HOC (компонент «wrapping», SortableElement или SortableContainer) делает это, ожидая, что его собственные реквизиты уже будут содержать реквизиты обернутого компонента в качестве полей, а затем просто перейдут к обернутому компоненту, например в SortableElement визуализирует:
<WrappedComponent
ref={ref}
{...omit(this.props, 'collection', 'disabled', 'index')}
/>
this.props
передается через обернутую компоненту. Поскольку для обернутого компонента scala требуется поле «v» с объектом реквизита scala, нам нужно добавить это в реквизит компонента обертки. К счастью, это поле будет проходить без изменений, которое будет интерпретироваться позже компонентом scala. Вы не увидите поле «v», так как scalajs-response разворачивает его для вас.
Это вызывает проблемы при упаковке некоторых других HOC - например, ReactGridLayout's WidthProvider измеряет ширину упакованного компонента и передает его в реквизитах как {"width": width}
, но, к сожалению, мы не можем видеть это из scala. Для этого вполне может быть обходной путь.
Это покрывает деталь и ссылки на части HOC упаковки, но на самом деле этот процесс является довольно легко (при условии, вы не хотите, чтобы получить доступ к реквизиту «инъекционный» в обернутом компонент):
- Сделайте объект scala для фасада, чтобы организовать код.
- Определите, какие реквизиты необходимы компоненту обертки. Например, в
SortableElement
это index
, collection
и disabled
. Создайте класс case of thePosts с этими полями в объекте фасада.
- Напишите функцию «обертывания» в объекте фасада. Это делает следующее:
- Принимает
wrappedComponent: ReactComponentC[P,_,_,_]
и передает его функции havascript HOC (например, SortableElement
) для создания нового компонента React.
- Создает объект реквизита javascript с реквизитами компонента обертки и магическим полем «v» с реквизитами обернутого компонента.
- Использует функцию react.createElement javascript для создания ReactElement, который отображает завернутый компонент, и отбрасывает это на ReactComponentU_.
Обратите внимание, что на этапе 5 нам нужно преобразовать из нашего класса Scala Props (реквизита компонента оболочки) в простой объект javascript, который может быть понят HOC. Реквизиты обернутого компонента просто идут прямо в поле «v» без преобразования, просто отбрасывая до js.Any
.
Код, который я написал для SortableElement и SortableContainer, немного расщепляется, так что wrap возвращает функцию curried, которая принимает реквизиты для компонента-обертки и создает другую функцию из обернутых реквизитов в конечный элемент React. Это означает, что вы можете предоставить реквизиты обертки один раз, а затем использовать результирующую функцию, как обычный компонент в вашем коде рендеринга Scala.
Я обновил SortableElement facade с улучшениями выше, и это в значительной степени минимальный пример HOC-фасада. Я бы предположил, что другие HOC будут очень похожи. Вероятно, вы могли бы отвлечь часть кода для DRY, но на самом деле там действительно не так много.
Благодарим за вопросы и за помощь в этом, посмотрев назад процесс, и особенно ваш вопрос на .factory
оставил меня более уверенным в том, что теперь он работает правильно (с описанными изменениями).