React Fragments – wrapper div終結者

楊小事er發表於2018-11-11

image
原文:getstream.io/blog/react-…

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源網站很常見的佈局。

https://user-gold-cdn.xitu.io/2018/11/11/167027d5f62cc475?w=600&h=313&f=png&s=104304
如果您還不瞭解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的佈局和樣式問題!

相關文章