React Fragments – wrapper div終結者

hi,大家好,我是Ken,目前就職於GetStream.io,為使用者提供個性化可定製的feed流服務。
在過去的幾個月裡,我一直在開發Winds 2.0,一個開源的RSS閱讀器和部落格訂閱應用。用Node.js, Electron, Redux and React開發,並且截止發文(2018-7-11),在Github上已經擁有了超過5000個star。如果你想看看整個專案,可以訪問getstream.io/winds/,或者檢視 github.com/GetStream/w… 上的原始碼。
在Winds中,我們在一些特定的情況需要用到 React Fragments 。React Fragments是一個去年年底React v16.2.0釋出的簡潔小功能——它真的非常小,但是隻要你瞭解了它,就可以在遇到一些非常具體的佈局和樣式情況時避開很多的麻煩。
Okay, 什麼是React Fragment?
讓我們稍微回顧一下 - 我確信每個React開發人員都會在他們職業生涯的某個階段遇到這種情況(或者即將遇到):
class App extends React.Component { render() { return ( <p>I would</p> <p>really like</p> <p>to render</p> <p>an array</p> ); } } 複製程式碼
看起來挺不錯,但是當我們通過JSX轉換器 執行它時......
Failed to compile. ./src/App.js Syntax error: Adjacent JSX elements must be wrapped in an enclosing tag (6:8) 4 |return (<p>I would 5 |</p> 6 |<p> |^ 7 |really like 8 |</p> 9 |<p> 複製程式碼
JSX轉換器 並不喜歡這樣
這背後究竟發生了什麼?JSX 把所有的 <div>
s和 <MyComponent>
s都放進 React.createElement()
中呼叫——當JSX轉換器看到的(最外層如果)是多個元素而不是單個元素時,它就不知道到底要渲染哪個tag,更多: React.createElement in the React documentation 。
那麼我們該怎麼辦?每次我們需要將幾個元素包裝在一起時都會這樣做,——Pinky,用一個 div
包裹起來!就像web開發者從div被髮明出來一直以來做的那樣,在DOM中巢狀另外一個 div
也無傷大雅。
class App extends React.Component { render() { return ( <div> <p>I would</p> <p>really like</p> <p>to render</p> <p>an array</p> </div> ); } } 複製程式碼
嗯,問題確實解決了。但事實證明,還有另一種方法可以在一個React元件裡渲染出這一組內容——通過讓 render
方法返回一個節點陣列。
class App extends React.Component { render() { return [ <p>I would</p>, <p>really like</p>, <p>to render</p>, <p>an array</p> ]; } } 複製程式碼
如果我們返回這樣的一個元素陣列,React將會轉換並渲染它,而且沒有 wrapper<div>
(容器div)。 顯得很整齊!
(還記得 JSX
轉換器是如何將 <div>
和 <MyComponent>
轉換並被 React.createElement()
呼叫的嗎?在這種情況下,轉換器只是將這些一個數組內的元素作為子元素直接附加到父元素,而不是當做沒有父元素包含的元素陣列。React v16.0.0引入了此功能。
因為這樣,Dan Abramov和一些React團隊的聰明人看到之後說,
“Okay,所以你可以用兩種不同的方式來渲染一個元素陣列 - 通過在DOM中引入一個額外的 <div>
,或者使用一些笨重的非JSX語法。這些都無法帶來一個良好的程式設計體驗。”
所以,在v16.2.0, React釋出了React Fragments
okay,nowwhat’s a React Fragment?
這才是是使用React Fragment的正確方法:
class App extends React.Component { render() { return ( <React.Fragment> <p>I would</p> <p>really like</p> <p>to render</p> <p>an array</p> </React.Fragment> ); } } 複製程式碼
看 - 我們寫的就像 wrapper<div>
的方式一樣,但在功能上與 陣列渲染
方式相同,只不過是使用了看起來不錯的JSX語法。 這樣就可以將這些p元素呈現為陣列,而不包含任何型別的 wrapper<div>
。
使用 React Fragments
還有另一種更簡潔的語法:
class App extends React.Component { render() { return ( <> <p>I would</p> <p>really like</p> <p>to render</p> <p>an array</p> </> ); } } 複製程式碼
可能在你的工具,linters,構建流程等等中,還沒有起作用。但是發版說明中提到,這種語法的支援已經在開發中了,但是我注意到在 create-react-app
中還沒有支援。
Okay,但什麼時候才能使用它們呢?
在你需要擺脫 wrapper<div>
的時候。
就是這樣 - 如果你發現自己處於一個 wrapper<div>
搞亂你的React元件佈局的情況下,請使用React Fragment。
所以,就是當你想轉換這個:
<div class="app"> (...a bunch of other elements) <div> (my react component) <ComponentA></ComponentA> <ComponentB></ComponentB> <ComponentC></ComponentC> </div> (...a bunch more elements) </div> 複製程式碼
到這樣:
<div class="app"> (...a bunch of other elements) <ComponentA></ComponentA> <ComponentB></ComponentB> <ComponentC></ComponentC> (...a bunch more elements) </div> 複製程式碼
Example: 2×2 CSS grid
在Winds 2.0中,我們大量的使用了CSS Grid佈局,這是部落格或者RSS源網站很常見的佈局。

如果您還不瞭解CSS Grid佈局,下面的程式碼可以讓您快速瞭解CSS的佈局
.grid { display: grid; grid-template-areas: 'topnav header' 'subnav content'; grid-gap: 1em; } 複製程式碼
Okay,讓我們來解釋一下:
- 在左上角,我們有標誌或者頂級導航位。
- 在左下角,有子導航,可以響應全域性和區域性狀態的一些變化,如“高亮”狀態,tabs或摺疊導航。
- 在右側,有一些需要在螢幕上顯示的內容,在Winds中,有點類似於與文章列表或文章內容配對的RSS提要或文章標題。 這兩個部分將是單一的React元件 - 兩個元件的道具都會根據URL導航進行更改。
所有這些作用於全域性的元件(redux + URL)和本地狀態的互動都略有不同。 這個檢視的結構使得我們有三個React元件互為兄弟:
<div className="grid"> <TopNav /> <SubNav /> <ContentComponent /> </div> 複製程式碼
但是,我們希望實際呈現給頁面的有四個元素:
<div class="grid"> <div class="topnav"/> <div class="subnav"/> <div class="header"/> <div class="content" /> </div> 複製程式碼
這......就是沒有React Fragments的問題。想象一下,我們正在建立2×2網格檢視的兩個右側部分的元件,即 ContentComponent
:
class ContentComponent extends React.Component { render() { return ( <div> <div className="header"/> <div className="content"/> </div> ); } } 複製程式碼
如果我們將渲染的內容包裝在 <div>
中,那麼我們將獲得以下渲染輸出:
<div class="grid"> <div class="topnav"/> <div class="subnav"/> <div> <div class="header"/> <div class="content" /> </div> </div> 複製程式碼
這不起作用 - 它還會搞亂CSS的grid佈局。 從瀏覽器的角度來看,網格中只有3個專案,其中一個還沒有 grid-area
的樣式。
還記得我們應該使用React Fragments嗎? 每當我們想要擺脫 wrapper<div>
。 如果我們將 ContentComponent
包裝在React Fragments中而不是 wrapper<div>
中:
class ContentComponent extends React.Component { render() { return ( <React.Fragment> <div className="header"/> <div className="content"/> </React.Fragment> ); } } 複製程式碼
然後我們將看到另外一種渲染輸出:
<div class="grid"> <div class="topnav"/> <div class="subnav"/> <div class="header"/> <div class="content" /> </div> 複製程式碼
這就符合我們的預期了! 沒有 wrapper<div>
,我們的4個元素從3個React元件渲染,瀏覽器看到所有元素具有正確的 grid area
樣式,並且我們的CSS grid正確呈現。
很整齊! 現在怎麼辦?
React Fragments不是近期React版本中釋出的最重要的功能,但它們在某些特定情況下非常有用。 只要瞭解React Fragments的存在,您就可以節省數小時的谷歌引起的頭痛。這可以讓我們以JSX-y方式呈現元素或者元件陣列,這可以解決大量的表格、列表和CSSgrid的佈局和樣式問題!