通過實(shí)例了解Render Props回調(diào)地獄解決方案
簡而言之,只要一個(gè)組件中某個(gè)屬性的值是函數(shù),那么就可以說該組件使用了 Render Props 這種技術(shù)。聽起來好像就那么回事兒,那到底 Render Props 有哪些應(yīng)用場景呢,咱們還是從簡單的例子講起,假如咱們要實(shí)現(xiàn)一個(gè)展示個(gè)人信息的組件,一開始可能會(huì)這么實(shí)現(xiàn):
const PersonInfo = props => (
<div>
<h1>姓名:{props.name}</h1>
</div>
);
// 調(diào)用
<PersonInfo name='web前端'/>
如果,想要在 PersonInfo 組件上還需要一個(gè)年齡呢,咱們會(huì)這么實(shí)現(xiàn):
const PersonInfo = props => (
<div>
<h1>姓名:{props.name}</h1>
<p>年齡:{props.age}</[>
</div>
);
// 調(diào)用
<PersonInfo name='web前端' age='18'/>
然后如果還要加上鏈接呢,又要在 PersonInfo 組件的內(nèi)部實(shí)現(xiàn)發(fā)送鏈接的邏輯,很明顯這種方式違背了軟件開發(fā)六大原則之一的 開閉原則,即每次修改都要到組件內(nèi)部需修改。
開閉原則:對(duì)修改關(guān)閉,對(duì)拓展開放。
那有什么方法可以避免這種方式的修改呢?
在原生 js 中,如果咱們調(diào)用函數(shù)后,還要做些騷操作,咱們一般使用回調(diào)函數(shù)來處理這種情況。
在 react 中咱們可以使用 Render Props,其實(shí)和回調(diào)一樣:
const PersonInfo = props => {
return props.render(props);
}
// 使用
<PersonInfo
name='web前端' age = '18' link = 'link'
render = {(props) => {
<div>
<h1>{props.name}</h1>
<p>{props.age}</p>
<a href="props.link" rel="external nofollow" ></a>
</div>
}}
/>
值得一提的是,并不是只有在 render 屬性中傳入函數(shù)才能叫 Render Props,實(shí)際上任何屬性只要它的值是函數(shù),都可稱之為 Render Props,比如上面這個(gè)例子把 render 屬性名改成 children 的話使用上其實(shí)更為簡便:
const PersonInfo = props => {
return props.children(props);
};
<PersonInfo name='web前端' age = '18' link = 'link'>
{(props) => (
<div>
<h1>{props.name}</h1>
<p>{props.age}</p>
<a href={props.link}></a>
</div>
)}
</PersonInfo
這樣就可以直接在 PersonInfo 標(biāo)簽內(nèi)寫函數(shù)了,比起之前在 render 中更為直觀。
所以,React 中的 Render Props 你可以把它理解成 js 中的回調(diào)函數(shù)。
React 組件的良好設(shè)計(jì)是可維護(hù)且易于更改代碼的關(guān)鍵。
從這個(gè)意義上說,React 提供了許多設(shè)計(jì)技術(shù),比如組合、Hooks、高階組件、Render Props等等。
Render props 可以有效地以松散耦合的方式設(shè)計(jì)組件。它的本質(zhì)在于使用一個(gè)特殊的prop(通常稱為render),將渲染邏輯委托給父組件。
import Mouse from 'Mouse';
function ShowMousePosition() {
return (
<Mouse
render = {
({ x, y }) => <div>Position: {x}px, {y}px</div>
}
/>
)
}
使用此模式時(shí),遲早會(huì)遇到在多個(gè) render prop 回調(diào)中嵌套組件的問題: render props 回調(diào)地獄。
1. Render Props 的回調(diào)地獄
假設(shè)各位需要檢測并顯示網(wǎng)站訪問者所在的城市。
首先,需要確定用戶地理坐標(biāo)的組件,像<AsyncCoords render={coords => ... } 這樣的組件進(jìn)行異步操作,使用 Geolocation API,然后調(diào)用Render prop 進(jìn)行回調(diào)。。
然后用獲取的坐標(biāo)用來近似確定用戶的城市:<AsyncCity lat={lat} long={long} render={city => ...} />,這個(gè)組件也叫Render prop。
接著咱們將這些異步組件合并到<DetectCity>組件中
function DetectCity() {
return (
<AsyncCoords
render={({ lat, long }) => {
return (
<AsyncCity
lat={lat}
long={long}
render={city => {
if (city == null) {
return <div>Unable to detect city.</div>;
}
return <div>You might be in {city}.</div>;
}}
/>
);
}}
/>
);
}
// 在某處使用
<DetectCity />
可能已經(jīng)發(fā)現(xiàn)了這個(gè)問題:Render Prop回調(diào)函數(shù)的嵌套。嵌套的回調(diào)函數(shù)越多,代碼就越難理解。這是Render Prop回調(diào)地獄的問題。
咱們換中更好的組件設(shè)計(jì),以排除回調(diào)的嵌套問題。
2. Class 方法
為了將回調(diào)的嵌套轉(zhuǎn)換為可讀性更好的代碼,咱們將回調(diào)重構(gòu)為類的方法。
class DetectCity extends React.Component {
render() {
return <AsyncCoords render={this.renderCoords} />;
}
renderCoords = ({ lat, long }) => {
return <AsyncCity lat={lat} long={long} render={this.renderCity}/>;
}
renderCity = city => {
if (city == null) {
return <div>Unable to detect city.</div>;
}
return <div>You might be in {city}.</div>;
}
}
// 在某處使用
<DetectCity />
回調(diào)被提取到分開的方法renderCoords()和renderCity()中。這樣的組件設(shè)計(jì)更容易理解,因?yàn)殇秩具壿嫹庋b在一個(gè)單獨(dú)的方法中。
如果需要更多嵌套,類的方式是垂直增加(通過添加新方法),而不是水平(通過相互嵌套函數(shù)),回調(diào)地獄問題消失。
2.1 訪問渲染方法內(nèi)部的組件 props
方法renderCoors()和renderCity()是使用箭頭函法定義的,這樣可以將 this 綁定到組件實(shí)例,所以可以在<AsyncCoords>和<AsyncCity>組件中調(diào)用這些方法。
有了this作為組件實(shí)例,就可以通過 prop 獲取所需要的內(nèi)容:
class DetectCityMessage extends React.Component {
render() {
return <AsyncCoords render={this.renderCoords} />;
}
renderCoords = ({ lat, long }) => {
return <AsyncCity lat={lat} long={long} render={this.renderCity}/>;
}
renderCity = city => {
// 看這
const { noCityMessage } = this.props;
if (city == null) {
return <div>{noCityMessage}</div>;
}
return <div>You might be in {city}.</div>;
}
}
<DetectCityMessage noCityMessage="Unable to detect city." />
renderCity()中的this值指向<DetectCityMessage>組件實(shí)例。現(xiàn)在就很容易從this.props獲取 noCityMessage 的值 。
3. 函數(shù)組合方法
如果咱們想要一個(gè)不涉及創(chuàng)建類的更輕松的方法,可以簡單地使用函數(shù)組合。
使用函數(shù)組合重構(gòu) DetectCity 組件:
function DetectCity() {
return <AsyncCoords render={renderCoords} />;
}
function renderCoords({ lat, long }) {
return <AsyncCity lat={lat} long={long} render={renderCity}/>;
}
function renderCity(city) {
if (city == null) {
return <div>Unable to detect city.</div>;
}
return <div>You might be in {city}.</div>;
}
// Somewhere
<DetectCity />
現(xiàn)在,常規(guī)函數(shù)renderCoors()和renderCity()封裝了渲染邏輯,而不是用方法創(chuàng)建類。
如果需要更多嵌套,只需要再次添加新函數(shù)即可。代碼垂直增長(通過添加新函數(shù)),而不是水平增長(通過嵌套),從而解決回調(diào)地獄問題。
這種方法的另一個(gè)好處是可以單獨(dú)測試渲染函數(shù):renderCoords()和renderCity()。
3.1 訪問渲染函數(shù)內(nèi)部組件的 prop
如果需要訪問渲染函數(shù)中的 prop ,可以直接將渲染函數(shù)插入組件中
function DetectCityMessage(props) {
return (
<AsyncCoords
render={renderCoords}
/>
);
function renderCoords({ lat, long }) {
return (
<AsyncCity
lat={lat}
long={long}
render={renderCity}
/>
);
}
function renderCity(city) {
const { noCityMessage } = props;
if (city == null) {
return <div>{noCityMessage}</div>;
}
return <div>You might be in {city}.</div>;
}
}
// Somewhere
<DetectCityMessage noCityMessage="Unknown city." />
雖然這種結(jié)構(gòu)有效,但我不太喜歡它,因?yàn)槊看?lt;DetectCityMessage>重新渲染時(shí),都會(huì)創(chuàng)建renderCoords()和renderCity()的新函數(shù)實(shí)例。
前面提到的類方法可能更適合使用。同時(shí),這些方法不會(huì)在每次重新渲染時(shí)重新創(chuàng)建。
4. 實(shí)用的方法
如果想要在如何處理render props回調(diào)方面具有更大的靈活性,那么使用React-adopt是一個(gè)不錯(cuò)的選擇。
使用 react-adopt 來重構(gòu) <DetectCity> 組件:
import { adopt } from 'react-adopt';
const Composed = adopt({
coords: ({ render }) => <AsyncCoords render={render} />,
city: ({ coords: { lat, long }, render }) => (
<AsyncCity lat={lat} long={long} render={render} />
)
});
function DetectCity() {
return (
<Composed>
{ city => {
if (city == null) {
return <div>Unable to detect city.</div>;
}
return <div>You might be in {city}.</div>;
}}
</Composed>
);
}
<DetectCity />
react-adopt需要一個(gè)特殊的映射器來描述異步操作的順序。同時(shí),庫負(fù)責(zé)創(chuàng)建定制的渲染回調(diào),以確保正確的異步執(zhí)行順序。
你可能會(huì)注意到的,上面使用react-adopt 的示例比使用類組件或函數(shù)組合的方法需要更多的代碼。那么,為什么還要使用“react-adopt”呢?
不幸的是,如果需要聚合多個(gè)render props的結(jié)果,那么類組件和函數(shù)組合方法并不合適。
4.1 聚合多個(gè)渲染道具結(jié)果
想象一下,當(dāng)咱們渲染3個(gè)render prop回調(diào)的結(jié)果時(shí)(AsyncFetch1、AsyncFetch2、AsyncFetch3)
function MultipleFetchResult() {
return (
<AsyncFetch1 render={result1 => (
<AsyncFetch2 render={result2 => (
<AsyncFetch3 render={result3 => (
<span>
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
</span>
)} />
)} />
)} />
);
}
<MultipleFetchResult />
<MultipleFetchResult>組件沉浸所有3個(gè)異步獲取操作的結(jié)果,這是一個(gè)闊怕回調(diào)地獄的情況。
如果嘗試使用類組件或函數(shù)的組合方法,它會(huì)很麻煩。 回調(diào)地獄轉(zhuǎn)變?yōu)閰?shù)綁定地獄:
class MultipleFetchResult extends React.Component {
render() {
return <AsyncFetch1 render={this.renderResult1} />;
}
renderResult1(result1) {
return (
<AsyncFetch2
render={this.renderResult2.bind(this, result1)}
/>
);
}
renderResult2(result1, result2) {
return (
<AsyncFetch2
render={this.renderResult3.bind(this, result1, result2)}
/>
);
}
renderResult3(result1, result2, result3) {
return (
<span>
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
</span>
);
}
}
// Somewhere
<MultipleFetchResult />
咱們必須手動(dòng)綁定render prop回調(diào)的結(jié)果,直到它們最終到達(dá)renderResult3()方法。
如果不喜歡手工綁定,那么采用react-adopt可能會(huì)更好:
mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) => <AsyncFetch1 render={render} />,
result2: ({ render }) => <AsyncFetch2 render={render} />,
result3: ({ render }) => <AsyncFetch3 render={render} />
});
function MultipleFetchResult() {
return (
<Composed>
{({ result1, result2, result3 }) => (
<span>
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
</span>
)}
</Composed>
);
}
// Somewhere
<MultipleFetchResult />
在函數(shù)({result1, result2, result3}) =>{…}提供給<Composed>。因此,咱們不必手動(dòng)綁定參數(shù)或嵌套回調(diào)。
當(dāng)然,react-adopt的代價(jià)是要學(xué)習(xí)額外的抽象,并略微增加應(yīng)用程序的大小。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 使用react render props實(shí)現(xiàn)倒計(jì)時(shí)的示例代碼
- 談?wù)凴eact中的Render Props模式
- 何時(shí)/使用 Vue3 render 函數(shù)的教程詳解
- Unity使用LineRender實(shí)現(xiàn)繪畫功能
- Vue Render函數(shù)創(chuàng)建DOM節(jié)點(diǎn)代碼實(shí)例
- Vue自定義render統(tǒng)一項(xiàng)目組彈框功能
- vue渲染方式render和template的區(qū)別
- vue+render+jsx實(shí)現(xiàn)可編輯動(dòng)態(tài)多級(jí)表頭table的實(shí)例代碼
相關(guān)文章
javascript常見數(shù)字進(jìn)制轉(zhuǎn)換實(shí)例分析
這篇文章主要介紹了javascript常見數(shù)字進(jìn)制轉(zhuǎn)換,結(jié)合實(shí)例形式分析了JavaScript十進(jìn)制,十六進(jìn)制及二進(jìn)制的相互轉(zhuǎn)換原理與技巧,需要的朋友可以參考下2016-04-04
防止頁面url緩存中ajax中post請(qǐng)求的處理方法
這篇文章主要介紹了防止頁面url緩存中ajax中post請(qǐng)求的處理方式的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10
layui實(shí)現(xiàn)根據(jù)table數(shù)據(jù)判斷按鈕顯示情況的方法
今天小編就為大家分享一篇layui實(shí)現(xiàn)根據(jù)table數(shù)據(jù)判斷按鈕顯示情況的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09
Javascript中的for in循環(huán)和hasOwnProperty結(jié)合使用
當(dāng)檢測某個(gè)對(duì)象是否擁有某個(gè)屬性時(shí),hasOwnProperty 是唯一可以完成這一任務(wù)的方法,在 for in 循環(huán)時(shí),建議增加 hasOwnProperty 進(jìn)行判斷,可以有效避免擴(kuò)展本地原型而引起的錯(cuò)誤2013-06-06
javascript對(duì)象的創(chuàng)建和訪問
這篇文章主要為大家詳細(xì)介紹了javascript對(duì)象的創(chuàng)建和訪問實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-03-03

