網(wǎng)站首頁 編程語言 正文
render prop
是一個技術(shù)概念。它指的是使用值為function類型的prop來實現(xiàn)React component之間的代碼共享。
如果一個組件有一個render屬性,并且這個render屬性的值為一個返回React element的函數(shù),并且在組件內(nèi)部的渲染邏輯是通過調(diào)用這個函數(shù)來完成的。那么,我們就說這個組件使用了render props
技術(shù)。
<DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/>
不少類庫都使用了這種技術(shù),比如說:React Router和Downshift。
在這個文檔里面,我們將會討論為什么render props
是如此有用,你該如何編寫自己的render props
組件。
正文
使用Render Props來完成關(guān)注點(diǎn)分離
在React中,組件是代碼復(fù)用的基本單元(又來了,官方文檔不斷地在強(qiáng)調(diào)這個準(zhǔn)則)。到目前為止,在React社區(qū)里面,關(guān)于共享state或者某些相似的行為(比如說,將一個組件封裝進(jìn)另一擁有相同state的組件)還沒有一個明朗的方案。
舉個例子,下面這個組件是用于在web應(yīng)用中追蹤鼠標(biāo)的位置:
class MouseTracker extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <h1>Move the mouse around!</h1> <p>The current mouse position is ({this.state.x}, {this.state.y})</p> </div> ); } }
隨著光標(biāo)在屏幕上面移動,這個組件將會在文檔的<p>
標(biāo)簽里面顯示當(dāng)前光標(biāo)在x,y軸上的坐標(biāo)值。
那么問題來了: 我們該如何在別的組件復(fù)用這種行為(指的是監(jiān)聽mouseMove事件,獲取光標(biāo)的坐標(biāo)值)呢?換句話說,如果別的組件也需要知道目前光標(biāo)的坐標(biāo)值,那我們能不能將這種行為封裝好,然后在另外一個組件里面開箱即用呢?
因為,在React中,組件是代碼復(fù)用的基本單元(again)。那好,我們一起來重構(gòu)一下代碼,把我們需要復(fù)用的行為封裝到<Mouse>
組件當(dāng)中。
// The <Mouse> component encapsulates the behavior we need... class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* ...but how do we render something other than a <p>? */} <p>The current mouse position is ({this.state.x}, {this.state.y})</p> </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse /> </div> ); } }
現(xiàn)在,<Mouse>
組件看似把所有跟監(jiān)聽mousemove事件,保存光標(biāo)的坐標(biāo)值等相關(guān)的行為封裝在一起了。實際上,它還不能達(dá)到真正的可復(fù)用。
假設(shè),我們需要實現(xiàn)這么一個組件。它需要渲染出一只用圖片表示的貓去追逐光標(biāo)在屏幕上移動的視覺效果。我們可能會通過向<Cat>
組件傳遞一個叫mouse(它的值為{{x,y}})的prop來獲得當(dāng)前光標(biāo)所在位置。參考React實戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
首先,我們會在<Mouse>
組件的render方法里面插入這個<Cat>
組件,像這樣子:
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class MouseWithCat extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* We could just swap out the <p> for a <Cat> here ... but then we would need to create a separate <MouseWithSomethingElse> component every time we need to use it, so <MouseWithCat> isn't really reusable yet. */} <Cat mouse={this.state} /> </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <MouseWithCat /> </div> ); } }
這種方式的實現(xiàn)可能對個別的場景有用,但是,我們還是沒有達(dá)成通過封裝讓這種行為真正地復(fù)用的目標(biāo)。在別的應(yīng)用場景下,每一次當(dāng)我們需要獲取光標(biāo)在屏幕上的坐標(biāo)的時候,我們都需要重新創(chuàng)建一個組件(例如,一個跟<MouseWithCat>
相似組件)來完成這個業(yè)務(wù)場景所對應(yīng)的渲染任務(wù)。
這個時候,就輪到render props 出場啦:相比直接把<Cat>
這個組件硬編碼到<Mouse>
組件當(dāng)中,刻意地去改變<Mouse>
組件的UI輸出(也就是我們重新定義一個<MouseWithCat>
組件的原因)。更好的做法是,我們可以給<Mouse>
組件定義一個值為函數(shù)類型的prop,讓這個prop自己來動態(tài)地決定要在Mouse組件的render方法要渲染東西。這個值為函數(shù)類型的prop就是我們所說的render prop
了。
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
現(xiàn)在,相比每一次都要重復(fù)地將<Mouse>
組件的代碼復(fù)制一遍,然后將我們要渲染的東西硬編碼到<Mouse>
的render方法中去,我們采取了一個更省力的辦法。那就是給Mouse新增了一個render屬性,讓這個屬性來決定要在<Mouse>
組件中渲染什么。
更加具體和直白地說,一個render prop(這里不是代指技術(shù),而是組件屬性) 就是一個值為函數(shù)類型的prop。通過這個函數(shù),我們讓掛載了這個prop的組件知道自己要去渲染什么。
這種技術(shù)使得我們之前想要共享的某些行為(的實現(xiàn))變得非常之可移植(portable)。假如你想要得到這種行為,你只需要渲染一個帶render屬性的類<Mouse>
組件到你的組件樹當(dāng)中就可以了。剩下的就讓這個render prop來獲取相關(guān)的數(shù)據(jù)(通過函數(shù)形參被實例化時得到。拿上述例子來說,就是(mouse)=> <Cat mouse={mouse}>
的mouse
),然后決定如何干預(yù)這個組件的渲染。
一個很有意思的,并值得我們注意的事情是,你完全可以通過一個帶render屬性的普通組件來實現(xiàn)大部分的HOC。舉個例子,假如你在共享行為(監(jiān)聽mousemove事件,獲得光標(biāo)在屏幕上的坐標(biāo))時不想通過<Mouse>
組件來完成,而是想通過高階組件withMouse
來完成的話,那么就可以很簡單地通過創(chuàng)建一個帶render prop的<Mouse>
組件來達(dá)成:
// If you really want a HOC for some reason, you can easily // create one using a regular component with a render prop! function withMouse(Component) { return class extends React.Component { render() { return ( <Mouse render={mouse => ( <Component {...this.props} mouse={mouse} /> )}/> ); } } }
可以這么說,render props
(指技術(shù))讓HOC技術(shù)與其他技術(shù)(在這里,指它自己)的組合使用成為了可能。
render prop的prop名不一定叫render
如上面的標(biāo)題,你要牢牢記住,這種技術(shù)雖然叫render props
,但是prop屬性的名稱不一定非得叫“render”。實際上,只要組件上的某個屬性值是函數(shù)類型的,并且這個函數(shù)通過自己的形參實例化時獲取了這個組件的內(nèi)部數(shù)據(jù),參與到這個組件的UI渲染中去了,我們就說這個組件應(yīng)用了render props
這種技術(shù)。
在上面的例子當(dāng)中,我們一直在使用“render”這個名稱。實際上,我們也可以輕易地?fù)Q成children
這個名稱!
<Mouse children={mouse => ( <p>The mouse position is {mouse.x}, {mouse.y}</p> )}/>
同時,我們也要記住,這個“children”prop不一定非得羅列在在JSX element的“屬性”列表中。它實際上就是我們平時用JSX聲明組件時的children,因此你也可以像以前一樣把它放在組件的內(nèi)部。
<Mouse> {mouse => ( <p>The mouse position is {mouse.x}, {mouse.y}</p> )} </Mouse>
在react-motion這個庫的API中,你會看到這種寫法的應(yīng)用。
因為這種寫法比較少見,所以假如你這么做了,為了讓看你代碼的人不產(chǎn)生疑惑的話,你可能需要在靜態(tài)屬性propTypes中顯式地聲明一下children的數(shù)據(jù)類型必須為函數(shù)。
Mouse.propTypes = { children: PropTypes.func.isRequired };
注意點(diǎn)
當(dāng)跟React.PureComponent結(jié)合使用時,要當(dāng)心
如果你在組件的render方法里面創(chuàng)建了一個函數(shù)的話,然后把這個函數(shù)賦值給這個組件的prop的話,那么得到的結(jié)果很有可能是違背了你初衷的。怎么說呢?因為一旦你這么做了,React在作shallow prop comparison的時候,new props都會被判斷為不等于old props的。現(xiàn)實是,這么做恰恰會導(dǎo)致在每一次render的調(diào)用的時候生成一個新的值給這個屬性。
我們繼續(xù)拿上面的<Mouse>
組件作為例子。假如<Mouse>
組件繼承了React.PureComponent的話,我們的代碼應(yīng)該是像下面這樣的:
class Mouse extends React.PureComponent { // Same implementation as above... } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> {/* This is bad! The value of the `render` prop will be different on each render. */} <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
在上面的代碼例子當(dāng)中,每一次<MouseTracker>
組件的render方法被調(diào)用的時候,它都會生成一個新的函數(shù)實例給<Mouse>
組件,作為“render”屬性的值。然而,我們之所以繼承React.PureComponent,就是想減少<Mouse>
組件被渲染的次數(shù)。如此一來,<Mouse>
因為一個新的函數(shù)實例被判定為props已經(jīng)發(fā)生改變了,于是乎進(jìn)行了不必要的渲染。這與我們的讓<Mouse>
組件繼承React.PureComponent的初衷是相違背的。
為了避開(To get around)這個問題,你可以把render prop的值賦值為<MouseTracker>
組件實例的一個方法,這樣:
class MouseTracker extends React.Component { // Defined as an instance method, `this.renderTheCat` always // refers to *same* function when we use it in render renderTheCat(mouse) { return <Cat mouse={mouse} />; } render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={this.renderTheCat} /> </div> ); } }
在某些場景下,你可能無法把prop的值靜態(tài)地賦值為組件實例的某個方法(例如,你需要覆蓋組件的props值或者state值,又兩者都要覆蓋)。那么,在這種情況下,你只能老老實實地讓<Mouse>
組件去繼承React.Component了。
原文鏈接:https://blog.csdn.net/grooyo/article/details/127767359
相關(guān)推薦
- 2023-02-07 Pytorch中的廣播機(jī)制詳解(Broadcast)_python
- 2021-12-11 Linux環(huán)境變量和進(jìn)程地址空間介紹_Linux
- 2022-07-16 gitee設(shè)置ssh key
- 2022-03-18 Linux系統(tǒng)配置(服務(wù)控制)詳細(xì)介紹_Linux
- 2024-02-16 springboot開啟mybatis二級緩存
- 2022-09-03 Nginx代理Redis哨兵主從配置的實現(xiàn)_nginx
- 2022-07-27 shell腳本中一鍵部署zookeeper集群服務(wù)的方法_linux shell
- 2022-04-28 WPF依賴屬性用法詳解_實用技巧
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支