React.cloneElement的使用詳解
因?yàn)橐邮志S護(hù)一些項(xiàng)目,團(tuán)隊(duì)的技術(shù)棧最近從 vue 轉(zhuǎn)向 react ,作為一個(gè) react 新手,加上一向喜歡通過源碼來學(xué)習(xí)新的東西,就選擇了通過閱讀 antd 這個(gè)大名鼎鼎的項(xiàng)目源碼來學(xué)習(xí)一些 react 的用法。
在閱讀源碼的過程中,發(fā)現(xiàn)好些組件都使用了 React.cloneElement 這個(gè) api ,雖然通過名字可以猜測(cè)它做了什么,但是并不知道具體的作用;然后去看官方文檔,文檔很清晰地描述了它的作用,卻沒有告訴我們什么場(chǎng)景下需要使用它。于是我根據(jù)文檔的描述,結(jié)合源碼的使用,面向 google 和 stackoverflow,總結(jié)出來一些使用場(chǎng)景。
cloneElement 的作用
React.cloneElement( element, [props], [...children] )
首先看一下官方文檔對(duì)這個(gè) API 的描述:
Clone and return a new React element using element as the starting point. The resulting element will have the original element's props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved.
總結(jié)下來就是:
- 克隆原來的元素,返回一個(gè)新的 React 元素;
- 保留原始元素的 props,同時(shí)可以添加新的 props,兩者進(jìn)行淺合并;
- key 和 ref 會(huì)被保留,因?yàn)樗鼈儽旧硪彩?props ,所以也可以修改;
- 根據(jù) react 的源碼,我們可以從第三個(gè)參數(shù)開始定義任意多的子元素,如果定義了新的 children ,會(huì)替換原來的 children ;
使用場(chǎng)景
根據(jù)上面的定義分解,我們可以在不同的場(chǎng)景下根據(jù)需要來使用這個(gè) api 。
添加新的 props
當(dāng)我們創(chuàng)建一個(gè)通用組件時(shí),根據(jù)內(nèi)部的邏輯,想要給每個(gè)子元素添加不同的類名,這個(gè)時(shí)候我們可以修改它的 className
:
假設(shè)我們有一個(gè) Timeline 組件,允許我們根據(jù)需要定義多個(gè) TimelineItem
,在內(nèi)部我們想要給最后一個(gè)TimelineItem 添加一個(gè) timeline-item-last
類來渲染特殊的效果,這個(gè)時(shí)候我們可以這樣做:
const MyTimeline = () => { return ( <Timeline> <TimelineItem>2020-06-01</TimelineItem> <TimelineItem>2020-06-08</TimelineItem> <TimelineItem>2020-07-05</TimelineItem> </Timeline> ) } // 在 Timeline 內(nèi)部,邏輯可能是這樣的 import class from 'classnames'; const Timeline = props => { // ... // ... const itemCount = React.children.count(props.children); const items = React.children.map(props.children, (item, index) => { return React.cloneElement(item, { className: class([ item.props.className, 'timeline-item', index === count - 1 ? 'timeline-item-last' : '' ]) }) } return <div className={'timeline'}>{ items }</div> }
除了添加 className
,還可以動(dòng)態(tài)給子組件添加更多的 props 信息,react-router
的 Switch
會(huì)給匹配的子組件添加 location
和 computedMatch
信息:
class Switch extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Switch> outside a <Router>"); const location = this.props.location || context.location; let element, match; // We use React.Children.forEach instead of React.Children.toArray().find() // here because toArray adds keys to all child elements and we do not want // to trigger an unmount/remount for two <Route>s that render the same // component at different URLs. React.Children.forEach(this.props.children, child => { if (match == null && React.isValidElement(child)) { element = child; const path = child.props.path || child.props.from; match = path ? matchPath(location.pathname, { ...child.props, path }) : context.match; } }); return match ? React.cloneElement(element, { location, computedMatch: match }) : null; }} </RouterContext.Consumer> ); } }
修改 props 的事件
假設(shè)我們有一個(gè) Tab 組件,它下面包含多個(gè) TabPane
子組件,我們想要點(diǎn)擊每個(gè) TabPane
子組件的同時(shí)觸發(fā) Tab 的 onClick
事件,用戶自己本身可能給每個(gè) TabPane 定義了獨(dú)立的 onClick
事件,這時(shí)候我們就要修改子組件 onClick
事件:
const Tab = props => { const { onClick } = props; const tabPanes = React.children.map(props.children, (tabPane, index) => { const paneClick = () => { onClick && onClick(index); tabPane.props?.onClick(); } return React.cloneElement(tabPane, { onClick: paneClick, }) }) return <div>{ tabPanes }</div> }
定制樣式
創(chuàng)建一個(gè)叫 FollowMouse
組件時(shí),我們?cè)试S用戶定義內(nèi)容組件 Content
,當(dāng)鼠標(biāo)移動(dòng)時(shí),根據(jù)內(nèi)容的大小,自動(dòng)計(jì)算 Content
的位置避免溢出屏幕,這個(gè)時(shí)候我們就可以使用 cloneElement
來動(dòng)態(tài)修改它的樣式。
// 簡(jiǎn)單起見,這里省略鼠標(biāo)事件。 const FollowMouse = props => { const { Content } = props; const customContent = React.isValidElement ? Content : <span>{ Content }</span> const getOffset = () => { return { position: 'absolute', top: ..., left: ..., } } const renderContent = React.cloneElement(custonContent, { style: { ...getOffset() } }) return <div>{ renderContent() }</div> }
添加 key
當(dāng)我們創(chuàng)建一個(gè)元素列表時(shí),可以通過 cloneElement
給每個(gè)節(jié)點(diǎn)添加一個(gè) key 。
const ComponentButton = props => { const { addonAfter, children } = props; const button = <button key='button'>{ children }</button> const list = [button, addonAfter ? React.cloneElement(addonAfter, { key: 'button-addon' } : null) return <div>{ list } <div> }
總結(jié)
在開發(fā)復(fù)雜組件中,經(jīng)常會(huì)根據(jù)需要給子組件添加不同的功能或者顯示效果,react
元素本身是不可變的 (immutable) 對(duì)象, props.children
事實(shí)上并不是 children
本身,它只是 children
的描述符 (descriptor) ,我們不能修改任何它的任何屬性,只能讀到其中的內(nèi)容,因此 React.cloneElement
允許我們拷貝它的元素,并且修改或者添加新的 props 從而達(dá)到我們的目的。
當(dāng)然,得益于 react 強(qiáng)大的組合模式,這并不僅僅局限于 props.children
,不管是 props.left
還是 props.right
或者任何其它的 props
傳進(jìn)來的內(nèi)容,只要是合法的 react 元素,我們都可以使用這個(gè) React.cloneElement
對(duì)其進(jìn)行操作。
以上就是React.cloneElement的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于React.cloneElement的使用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react使用useImperativeHandle示例詳解
這篇文章主要為大家介紹了react使用useImperativeHandle示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09React函數(shù)組件hook原理及構(gòu)建hook鏈表算法詳情
這篇文章主要介紹了React函數(shù)組件hook原理及構(gòu)建hook鏈表算法詳情,每一個(gè)?hook?函數(shù)都有對(duì)應(yīng)的?hook?對(duì)象保存狀態(tài)信息,更多詳細(xì)分析,需要的朋友可以參考一下2022-07-07Webpack3+React16代碼分割的實(shí)現(xiàn)
這篇文章主要介紹了Webpack3+React16代碼分割的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03詳解React如何實(shí)現(xiàn)代碼分割Code Splitting
這篇文章主要為大家介紹了React如何實(shí)現(xiàn)代碼分割Code Splitting示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08react-native中路由頁面的跳轉(zhuǎn)與傳參的實(shí)例詳解
這篇文章主要介紹了react-native中路由頁面的跳轉(zhuǎn)與傳參,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08React?useEffect不支持async?function示例分析
這篇文章主要為大家介紹了React?useEffect不支持async?function示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07搭建React?Native熱更新平臺(tái)的詳細(xì)過程
這篇文章主要介紹了搭建React?Native熱更新平臺(tái),要實(shí)現(xiàn)?React?Native?熱更新功能,有四種思路?Code?Push、Pushy、Expo?和自研,感興趣的朋友一起通過本文學(xué)習(xí)吧2022-05-05