這裏有相當多的部分,但我已經彙集了一些從最低層到最高層的鏈接,同時涵蓋了問題。
第一個也是最重要的定義是React組件和React元素。 This page有一個深入的解釋 - 我建議完全跳過「管理實例」一節,因爲它通過描述傳統的UI模型來混淆水域,同時使用與React中使用的方式不同的術語。總之,我的理解是:
React Component是一個通用的概念,可以用多種方式實現。然而,它是實現的,它實質上是一個從道具(可選狀態)到頁面內容(渲染器)的函數。
React元素是頁面內容的描述,表示組件的特定呈現。
React components and props docs描述了定義React組件的兩種方式,第一種是從道具到反應元素的函數,這是我們感興趣的。然後React.createFactory文檔確認我們可以傳遞這樣的函數創建工廠。據我所知,這是爲了適應從定義React組件的多種方式(通過React.Component或React.PureComponent的子類,通過使用React.createClass,或通過從Props到ReactElement的函數)到一種方式將道具渲染爲ReactElement。我們可以通過查看這個gist在React 0.12中引入React.createFactory來看到這件事 - 本質上他們想要在用於定義React Component的類和從props到渲染時使用的React Elements的最終函數之間引入一些抽象,而不是讓班級直接提供道具。
接下來我們有一個小小的皺紋 - React.createFactory
被標記爲遺留在文檔中。幸運的是,這不是一個大問題,就我所知React.createFactory(type)
只是產生一個函數f(props)
,它與React.createElement(type, props)
相同 - 我們只是在React.createElement
中修復了type
參數。我的反應可排序,特別的包裝進行了測試,我們可以使用的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.Component」方法創建另一個React組件。在這個類的render
函數中,我們可以看到WrappedComponent
被用作React組件,所以我們知道它確實是一個組件,即使沒有靜態類型:)這意味着WrappedComponent需要被React.createElement
接受,因爲這是一個什麼樣的JSX component use desugars to。
因此我們知道我們需要傳遞給sortableElement
函數,這個函數在javascript React.createElement
函數中可用作React Component。看看scalajs-react types doc,我們可以看到ReactComponentC看起來像是一個不錯的選擇 - 它構建了組件,大概是從道具中提取的。展望at the source for this我們可以看到,我們有兩個看起來很有希望的值 - reactClass
和factory
。此時,我意識到代碼可能使用了錯誤的代碼 - 我試圖用.reactClass
代替.factory
,這仍然有效,但更有意義,因爲我們有評論告訴我們它提供Output of [[React.createClass()]]
,它是有效React組件的選項。我懷疑factory
也基本上將提供的組件包裝在createFactory中兩次,因爲createFactory的輸出也可用作其輸入...我認爲給出了這個更正,我們已經回答了問題2 :)這也回答了很多問題1a) - ReactComponentC
是得到我們.reactClass
val的scala特徵,我們需要一個scala定義的React組件。我們只關心它使用的道具類型(因爲我們必須提供它們),因此P
。由於scala是鍵入的,我們知道這是我們通過正常方式構建一個scala React組件(至少對於我嘗試過的組件)得到的結果。
關於問題1b)中,我發現從像在scalajs-react Addons的ReactCssTransitionGroup
門面和scalajs-react-components notes on interop,其示出了非HOC成分的包裹代碼的類型ReactComponentU_
。查看類型本身,我們可以看到它擴展了ReactElement
,這是合理的 - 這是渲染React組件的預期結果。在我們的SortableElement
和SortableContainer
外牆的wrap
函數中,我們正在生成(最終)另一個從道具到ReactElement的函數,只是一個跳過了幾個圓圈以便使用HOC方法到達那裏。我不知道爲什麼使用ReactComponentU_
代替ReactElement
,我認爲這是跟蹤通過類型的組件狀態,但代碼仍然編譯,如果我返回ReactElement
,這是奇怪的。
問題3)要容易得多 - scalajs-react與可以是Ints,Longs等的Props一起工作,但在Javascript中,這些不是對象。爲了讓每個scalajs組件的道具成爲一個對象,scalajs會將它們包裝在一個對象中,如{"v": props}
,然後在使用它們時再解開。當我們使用HOC包裝一個Scala React組件時,我們需要以某種方式獲取包裝組件的道具。由HOC函數(「包裝」組件,SortableElement或SortableContainer)生成的組件通過期望其自己的道具已經包含了包裝組件的道具作爲字段來做到這一點,然後它只是讓它們流向包裝組件,例如在SortableElement的渲染中:
<WrappedComponent
ref={ref}
{...omit(this.props, 'collection', 'disabled', 'index')}
/>
this.props
被傳遞給被包裝的組件。由於包裝的scala組件需要一個帶有scala props對象的「v」字段,所以我們需要將它添加到包裝組件的道具中。幸運的是,這個字段將會在未經更改的情況下通過scala組件稍後解釋。你不會看到「v」字段,因爲scalajs-react會爲你解開它。
這會在包裝其他HOC時產生問題 - 例如ReactGridLayout's WidthProvider會測量包裝組件的寬度並將其作爲{"width": width}
傳遞給道具,但不幸的是我們無法從scala中看到這一點。這可能有一些解決方法。
這涵蓋了HOC包裹的部分細節和引用,但實際上這個過程是很容易的(提供您不希望訪問「注入」道具到包裹的分量):
- 爲外觀製作一個scala對象來組織代碼。
- 確定包裝組件需要什麼道具。例如在
SortableElement
這是index
,collection
和disabled
。使用這些字段在門面對象中創建一個Props案例類。
- 在外觀對象中寫入'wrap'函數。這包含以下內容:
- 接受
wrappedComponent: ReactComponentC[P,_,_,_]
並將其傳遞給javascript HOC函數(例如SortableElement
)以生成新的React組件。
- 用包裝組件的道具和魔術「v」字段與包裝組件的道具建立一個JavaScript道具對象。
- 使用javascript React.createElement函數生成渲染包裝組件的ReactElement,並將其轉換爲ReactComponentU_。
請注意,在階段5我們需要將我們的Scala Props案例類(包裝器組件的道具)轉換爲HOC可以理解的普通JavaScript對象。被包裹的組件的道具只是直接進入「v」字段而不進行轉換,只需投射到js.Any
即可。
我爲SortableElement和SortableContainer編寫的代碼將它分開了一點,以便wrap返回一個curried函數,該函數接受包裝器組件的props並生成從包裝的props到最終React元素的另一個函數。這意味着你可以提供一次包裝道具,然後在你的Scala渲染代碼中使用像普通組件一樣的結果函數。
我已經用上面的改進更新了SortableElement facade,這幾乎是HOC門面的一個簡單例子。我想其他HOC看起來會非常相似。你可能可以爲DRY的目的抽象一些代碼,但實際上這裏並沒有很多。
感謝您提出的問題並幫助您解決問題 - 回顧整個過程,尤其是您在.factory
上的問題,讓我更加確信,現在這種方式正在以正確的方式運行。