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,now what’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的佈局和樣式問題!