【面試問到就是不會系列】React.Children與React.cloneElement雜談

 介紹倆日常沒使用的React API,近日踩雷了,遂藉此篇提出來品品...javascript

  首先這倆貨同屬於React的頂層API,即咱們import React from 'react';後,能夠經過React.xxx的方式來調用。html

  再看官方文檔對它們的劃分:java

  圖中的幾個API都是對React元素進行操做的,isValidElement就不贅述了,用來校驗入參是不是一個合法的React元素,返回一個布爾值。node

React.Children

  咱們都知道在props對象中還有children這個屬性。它可以從某種程度上減小咱們在一個組件內的嵌套層級,可能這樣描述有點抽象,舉個栗子:react

// 好比咱們有個Modal模態框組件
export default class Modal extends React.Component {
    //...
}

// 有不少場景須要在Modal框內展現子組件的東西,最多見的結構相似下面

<Modal>
    <Content /> </Modal>

// 的確能夠在定義Modal的文件內import子組件,但咱們這是一個公共的組件,它僅是一個套套,因此一般會使用下面這種方案

render() {
    return (
        <div> {this.props.children} </div>
    )
}
複製代碼

  這樣來講,咱們的父組件就和可能傳入的children解耦了,各個模塊都是獨立的,各司其職。更多的關於props.children的語法闡釋能夠閱讀官方文檔數組

  看到這裏,咱們也發現了一個問題,就是props.children對於咱們開發者來講就是一個黑盒,咱們對它可能傳入的數據結構是不可知的(表達式、布爾、render function等等),若是咱們沒有對其進行操做,那其實沒什麼所謂。但只要咱們對其進行操做了,好比下意識覺得是個數組進行props.children.map這樣的調用就要注意,非Array就直接報TypeError了。那怎麼處理相似這樣的情景呢?數據結構

  其實React.Children剛好就是爲咱們提供處理props.children數據結構能力的API。注意這裏React.ChildrenChildren是大寫dom

React.Children.map

  React.Children.map(children, function[(thisArg)])這個類方法可以cover前文我提到的未知數據結構下的遍歷問題,只須要簡單修改:函數

React.Children.map(props.children, child => {})
複製代碼

  能夠看到這個API接收兩個參數,第一個就是咱們一般要處理的黑盒prop.children,第二個入參回調,其實就是咱們遍歷的元素上下文,經過它,咱們可以進行定製化的操做。this

  筆者結合源碼獲得當props.childrennullundefined時,最終會原值返回,其他情景則是返回一個數組。

React.Children.forEach

  跟React.Children.map相似,都是迭代操做,只不過這個不會返回數組。undefinednull時的判斷邏輯同上。

React.Children.count

  返回其中內部元素數,其值與前面兩個迭代方法的回調觸發次數相等。

React.Children.only

  用於判斷傳入的children是否只有一個child。注意接收類型是React element。不能拿React.Children.map()返回的結果再去判斷是幾個child,由於此時你拿到的已然是一個Array類型。

React.Children.toArray

  這個API會將黑盒的props.children數據結構以扁平的Array結構暴露給咱們,以下面這樣:

  經常使用在往下傳props時,從新排序或過濾部分children的情景。

React.cloneElement

  有了上面的鋪墊,這個API的引入就比較天然了,前文中咱們經過React.Children的類方法獲得了訪問本是黑盒的props.children的能力。React.cloneElement則是能讓咱們在操做React element時,進行淺層的新props merge,傳入的新children則會替換舊的children。原elementkeyref都會保留。

  看下API定義:

React.cloneElement(
  element,
  [props],
  [...children]
)
複製代碼

  其實跟React.createElement的構造有點像:

React.createElement(
  type,
  [props],
  [...children]
)
複製代碼

  畢竟是拷貝返回一個新的組合元素,React.cloneElement處理element時能夠大體理解成<element.type {...element.props} {...props}>{children}</element.type>

  那這個API到底有啥用呢?舉一個場景:

<Tabs active=''>
    <Tab id='a' title='a'>
        Content: {Math.random()}
    </Tab>
    <Tab id='b' title='b'>
        Content: {Math.random()}
    </Tab>
    <Tab id='c' title='c'>
        Content: {Math.random()}
    </Tab>
</Tabs>
複製代碼

  我但願點擊對應Tab的時候,再顯示Content信息,而且再也不修改以上組件結構(不額外在每一個子組件上加onClickprops),實際展現相似下圖:

  此時,咱們已經瞭解了前文中介紹的API的能力,大體有兩種解決方案,主體思路是一致的,區分在是否是每一個子組件都掛一個回調亦或在父組件上掛一個事件代理,去判斷。

  這裏我使用HOOKS的函數式寫法:

// 事件代理
const Tabs = props => {
    const { children, ...rest } = props;
    const [active, setActive] = useState(rest.active);
    let handleClick = e => {
        if (e.target.nodeName === 'A') {
            setActive(e.target.id);
        }
    }
    return (
        <header> <nav className={styles.nav}> <ul onClick={handleClick}> { React.Children.map(children, child => React.cloneElement(child, {active: active})) } </ul> </nav> </header>
    )
}
複製代碼
// 每個child 都綁定回調 經過cloneElement傳props
const Tabs = props => {
    const { children, ...rest } = props;
    const [active, setActive] = useState(rest.active);
    let toggleActive = (e, id) => {
        e.preventDefault();
        setActive(id);
    }
    return (
        <header> <nav className={styles.nav}> <ul> { React.Children.map(children, child => React.cloneElement(child, {active: active, toggleActive: toggleActive})) } </ul> </nav> </header>
    )
}
複製代碼

  主體思想都相似,就是把子組件須要的屬性和回調函數經過cloneElement的方式merge進去。

  以上DEMO,可藉此傳送門移步。

小結

  React.Children提供了咱們直接訪問黑盒props.children數據結構的能力;

  React.cloneElement接收一個React element並支持往其中淺層合併props,替換舊children;筆者看來該API能夠從必定程度上減小代碼的重複書寫,使組件標籤表達更加清晰。

相關文章
相關標籤/搜索