首頁技術(shù)文章正文

React 庫實現(xiàn) DRY 的著名的模式——高階組件

更新時間:2018-11-06 來源:黑馬程序員技術(shù)社區(qū) 瀏覽量:

要問程序員同學(xué)們尤為著名的模式是什么,當(dāng)然是在React 庫實現(xiàn) DRY 高階組件,進階文章來了,要準(zhǔn)備好哦~
在你聽到 Don't Repeat Yourself或者 D.R.Y 這樣(中邪一樣)的口號之前你是不會在軟件開發(fā)的鉆研之路上走得很遠的。有時候?qū)嵭羞@些名言會有點過于麻煩,但是在大多數(shù)情況下,(實行它)是一個有價值的目標(biāo)。在這篇文章中我們將會去探討在 React 庫中實現(xiàn) DRY 的尤為著名的模式——高階組件。不過在我們探索答案之前,我們首先必須要完全明確問題來源。
假設(shè)我們要負(fù)責(zé)重新創(chuàng)建一個類似于 Sprite(譯者注:國外的一個在線支付公司)的儀表盤。正如大多數(shù)項目那樣,一切事務(wù)在最后收尾之前都工作得很正常。你發(fā)現(xiàn)在儀表盤上有一串不一樣的提示框需要你某些元素 hover 的時候顯示。 => 你在儀表盤上面發(fā)現(xiàn)了一些不同的、(當(dāng)鼠標(biāo))懸停在某些組成元素上面會出現(xiàn)的提示信息。
1541493221792_DRY 的最著名的模式——高階組件.gif
這里有好幾種方式可以實現(xiàn)這個效果。其中一個你可能想到的是監(jiān)聽特定的組件的 hover 狀態(tài)來決定是否展示 tooltip。在上圖中,你有三個組件需要添加它們的監(jiān)聽功能—— Info、TrendChart 和 DailyChart。
讓我們從 Info 組件開始?,F(xiàn)在它只是一個簡單的 SVG 圖標(biāo)。
class Info extends React.Component {  render() {    return (      <svg        className="Icon-svg Icon--hoverable-svg"        height={this.props.height}        viewBox="0 0 16 16" width="16">          <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />      </svg>    )  }}復(fù)制代碼現(xiàn)在我們需要添加讓它可以監(jiān)測到自身是否被(鼠標(biāo))懸停的功能。我們可以使用 React 所附帶的 onMouseOver 和 onMouseOut 這兩個鼠標(biāo)時間。我們傳遞給 onMouseOver 的函數(shù)將會在組件被鼠標(biāo)懸停后觸發(fā),同時我們傳遞給 onMouseOut 的函數(shù)將會在組件不再被鼠標(biāo)懸停時觸發(fā)。要以 React 的方式來操作,我們會給給我們的組件添加一個 hovering state 屬性,所以我們可以在 hovering state 屬性改變的時候觸發(fā)重繪,來展示或者隱藏我們的提示框。
class Info extends React.Component {  state = { hovering: false }  mouseOver = () => this.setState({ hovering: true })  mouseOut = () => this.setState({ hovering: false })  render() {    return (      <>        {this.state.hovering === true          ? <Tooltip id={this.props.id} />          : null}        <svg          onMouseOver={this.mouseOver}          onMouseOut={this.mouseOut}          className="Icon-svg Icon--hoverable-svg"          height={this.props.height}          viewBox="0 0 16 16" width="16">            <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />        </svg>      </>    )  }}復(fù)制代碼上面的代碼看起來很棒?,F(xiàn)在我們要添加同樣的功能給我們的其他兩個組件——TrendChart 和 DailyChart。如果這兩個組件沒有出問題,就請不要修復(fù)它。我們對于 Info 的懸停功能運行的很好,所以請再寫一遍之前的代碼。
class TrendChart extends React.Component {  state = { hovering: false }  mouseOver = () => this.setState({ hovering: true })  mouseOut = () => this.setState({ hovering: false })  render() {    return (      <>        {this.state.hovering === true          ? <Tooltip id={this.props.id}/>          : null}        <Chart          type='trend'          onMouseOver={this.mouseOver}          onMouseOut={this.mouseOut}        />      </>    )  }}復(fù)制代碼你或許知道下一步了:我們要對最后一個組件 DailyChart 做同樣的事情。
class DailyChart extends React.Component {  state = { hovering: false }  mouseOver = () => this.setState({ hovering: true })  mouseOut = () => this.setState({ hovering: false })  render() {    return (      <>        {this.state.hovering === true          ? <Tooltip id={this.props.id}/>          : null}        <Chart          type='daily'          onMouseOver={this.mouseOver}          onMouseOut={this.mouseOut}        />      </>    )  }}復(fù)制代碼這樣的話,我們就全部做完了。你可能以前曾經(jīng)這樣寫過 React 代碼。但這并不該是你最終所該做的(不過這樣做也還湊合),但是它很不 “DRY”。正如我們所看到的,我們在我們的每一個組件中都 重復(fù)著完全一樣的的鼠標(biāo)懸停邏輯。
從這點看的話,問題變得非常清晰了:我們希望避免在在每個需要添加鼠標(biāo)懸停邏輯的組件是都再寫一遍相同的邏輯。所以,解決辦法是什么?在我們開始前,讓我們先討論一些能讓我們更容易理解答案的編程思想—— 回調(diào)函數(shù) 和 高階函數(shù)。
在 JavaScript 中,函數(shù)是 “一等公民”。這意味著它就像對象/數(shù)組/字符串那樣可以被聲明為一個變量、當(dāng)作函數(shù)的參數(shù)或者在函數(shù)中返回一個函數(shù),即使返回的是其他函數(shù)也可以。
function add (x, y) {  return x + y}function addFive (x, addReference) {  return addReference(x, 5)}addFive(10, add) // 15復(fù)制代碼如果你沒這樣用過,你可能會感到困惑。我們將 add 函數(shù)作為一個參數(shù)傳入 addFive 函數(shù),重新命名為 addReference,然后我們調(diào)用了著個函數(shù)。
這時候,你作為參數(shù)所傳遞進去的函數(shù)被叫做回調(diào)函數(shù)同時你使用回調(diào)函數(shù)所構(gòu)建的新函數(shù)被叫做高階函數(shù)。
因為這些名詞很重要,下面是一份根據(jù)它們所表示的含義重新命名變量后的同樣邏輯的代碼。
function add (x,y) {  return x + y}function higherOrderFunction (x, callback) {  return callback(x, 5)}higherOrderFunction(10, add)復(fù)制代碼這個模式很常見,哪里都有它。如果你之前用過任何 JavaScript 數(shù)組方法、jQuery 或者是 lodash 這類的庫,你就已經(jīng)用過高階函數(shù)和回調(diào)函數(shù)了。
[1,2,3].map((i) => i + 5)_.filter([1,2,3,4], (n) => n % 2 === 0 );$('#btn').on('click', () =>  console.log('回調(diào)函數(shù)哪里都有'))復(fù)制代碼讓我們回到我們之前的例子。如果我們不僅僅想創(chuàng)建一個 addFive 函數(shù),我們也想創(chuàng)建 addTen函數(shù)、 addTwenty 函數(shù)等等,我們該怎么辦?在我們當(dāng)前的實踐方法中,我們必須在需要的時候去重復(fù)地寫我們的邏輯。
function add (x, y) {  return x + y}function addFive (x, addReference) {  return addReference(x, 5)}function addTen (x, addReference) {  return addReference(x, 10)}function addTwenty (x, addReference) {  return addReference(x, 20)}addFive(10, add) // 15addTen(10, add) // 20addTwenty(10, add) // 30復(fù)制代碼再一次出現(xiàn)這種情況,這樣寫并不糟糕,但是我們重復(fù)寫了好多相似的邏輯。這里我們的目標(biāo)是要能根據(jù)需要寫很多 “adder” 函數(shù)(addFive、addTen、addTwenty 等等),同時盡可能減少代碼重復(fù)。為了完成這個目標(biāo),我們創(chuàng)建一個 makeAdder 函數(shù)怎么樣?著個函數(shù)可以傳入一個數(shù)字和原始 add 函數(shù)。因為這個函數(shù)的目的是創(chuàng)建一個新的 adder 函數(shù),我們可以讓其返回一個全新的傳遞數(shù)字來實現(xiàn)加法的函數(shù)。這兒講的有點多,讓我們來看下代碼吧。
function add (x, y) {  return x + y}function makeAdder (x, addReference) {  return function (y) {    return addReference(x, y)  }}const addFive = makeAdder(5, add)const addTen = makeAdder(10, add)const addTwenty = makeAdder(20, add)addFive(10) // 15addTen(10) // 20addTwenty(10) // 30復(fù)制代碼太酷了!現(xiàn)在我們可以在需要的時候隨意地用最低的代碼重復(fù)度創(chuàng)建 “adder” 函數(shù)。
如果你在意的話,這個通過一個多參數(shù)的函數(shù)來返回一個具有較少參數(shù)的函數(shù)的模式被叫做 “部分應(yīng)用(Partial Application)“,它也是函數(shù)式編程的技術(shù)。JavaScript 內(nèi)置的 “.bind“ 方法也是一個類似的例子。
好吧,那這與 React 以及我們之前遇到鼠標(biāo)懸停的組件有什么關(guān)系呢?我們剛剛通過創(chuàng)建了我們的 makeAdder 這個高階函數(shù)來實現(xiàn)了代碼復(fù)用,那我們也可以創(chuàng)建一個類似的 “高階組件” 來幫助我們實現(xiàn)相同的功能(代碼復(fù)用)。不過,不像高階函數(shù)返回一個新的函數(shù)那樣,高階組件返回一個新的組件來渲染 “回調(diào)” 組件

作者:黑馬程序員前端與移動開發(fā)培訓(xùn)學(xué)院
首發(fā):http://web.itheima.com/?v2


分享到:
在線咨詢 我要報名
和我們在線交談!