React 運用設想之道 - curry 化妙用

《React 運用設想之道 - curry 化妙用》

運用 React 開闢運用,賦予了前端工程師無窮“組合拼裝”快感。但在此基本上,組件怎樣離別,數據怎樣流轉等運用設想都決議了代碼層面的美感和強壯性。

同時,在 React 天下里提到 curry 化,或許許多開闢者會第一時候反應出 React-redux 庫的 connect 要領。但是,假如僅僅机械化地停止於此,而沒有更多天真地運用,黑白常惋惜的。

這篇文章以一個實在場景為基本,從細節動身,剖析 curry 化怎樣化簡為繁,更文雅地完成需求。

場景引見

需求場景為一個賣食物的電商網站,左邊部分為商品挑選欄目,用戶能夠依據:價錢區間、商品年限、商品品牌舉行過濾。右邊展現對應產物。以下圖:

《React 運用設想之道 - curry 化妙用》

作為 React 開闢者,我們曉得 React 是組件化的,第一步將斟酌依據 UE 圖,舉行組件拆分。這個歷程比較簡樸直觀,我們對拆分效果用下圖示意:

《React 運用設想之道 - curry 化妙用》

對應代碼為:

<Products>
    <Filters>
        <PriceFilter/>
        <AgeFilter/>
        <BrandFilter/>
    </Filters>
    <ProductResults/>
</Products>

低級完成

React 是基於數據狀況的,緊接着第二步就要斟酌運用狀況。商品展現效果數據我們臨時不須要體貼。這裏主要斟酌運用最主要的狀況,即過濾前提信息

我們運用命名為 filterSelections 的 JavaScript 對象示意過濾前提信息,以下:

filterSelections = {
  price: ...,
  ages: ...,
  brands: ...,
}

此數據須要在 Products 組件中舉行保護。由於 Products 組件的子組件 Filters 和 ProductResults 都將依靠這項數據狀況。

Filters 組件經由過程 prop 吸收 filterSelections 狀況,並拆解通報給它的三項挑選子組件:

class Filters extends React.Component {
  render() {
    return (
      <div>
        <PriceFilter price={this.props.filterSelections.price} />
        <AgeFilter ages={this.props.filterSelections.ages} />
        <BrandFilter brands={this.props.filterSelections.brands} />
      </div>
    );
  };
}

一樣地,ProductResults 組件也經由過程 prop 吸收 filterSelections 狀況,舉行響應產物的展現。

關於 Filters 組件,它肯定不僅僅是吸收 filterSelections 數據罷了,一樣也須要對此項數據舉行更新。為此,我們在 Products 組件中設想響應的 handler 函數,對過濾信息舉行更新,命名為 updateFilters,並將此處置懲罰函數作為 prop 下發給 Filters 組件:

class Products extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterSelections: {
        price: someInitialValue,
        ages: someInitialValue,
        brands: someInitialValue,
      }
    }
  }

  updateFilters = (newSelections) => {
    this.setState({
      filterSelections: newSelections
    })
  };

  render() {
    return(
      <div>
        <Filters 
          filterSelections={this.state.filterSelections}
          selectionsChanged={this.updateFilters}
        />
        <Products filterSelections={this.state.filterSelections} />
      </div>
    );
  }
}

注重這裏我們對 this 綁定體式格局。有興緻的讀者能夠參考我的另一篇文章:從 React 綁定 this,看 JS 言語生長和框架設想

作為 Filters 組件,一樣也要對處置懲罰函數舉行進一步拆分和分發:

class Filters extends React.Component {
  updatePriceFilter = (newValue) => {
    this.props.selectionsChanged({
      ...this.props.filterSelections,
      price: newValue
    })
  };

  updateAgeFilter = (newValue) => {
    this.props.selectionsChanged({
      ...this.props.filterSelections,
      ages: newValue
    })
  };

  updateBrandFilter = (newValue) => {
    this.props.selectionsChanged({
      ...this.props.filterSelections,
      brands: newValue
    })
  };
  
  render() {
    return (
      <div>
        <PriceFilter 
          price={this.props.filterSelections.price} 
          priceChanged={this.updatePriceFilter} 
        />
        <AgeFilter 
          ages={this.props.filterSelections.ages} 
          agesChanged={this.updateAgeFilter} 
        />
        <BrandFilter 
          brands={this.props.filterSelections.brands} 
          brandsChanged={this.updateBrandFilter} 
        />
      </div>
    );
  };
}

我們依據 selectionsChanged 函數,經由過程通報差別範例參數,設想出 updatePriceFilter、updateAgeFilter、updateBrandFilter 三個要領,離別通報給 PriceFilter、AgeFilter、BrandFilter 三個組件。

如許的做法異常直接,但是運轉優越。但是在 Filters 組件中,多了許多函數,且這些函數看上去做着雷同的邏輯。假如未來又多出了一個或多個過濾前提,那末一樣也要多出一致數目的“雙胞胎”函數。這明顯不夠文雅。

currying 是什麼

在剖析越發文雅的處理計劃之前,我們先扼要相識一下 curry 化是什麼。curry 化事實上是一種變形,它將一個函數 f 變形為 f’,f’ 的參數吸收底本函數 f 的參數,同時返回一個新的函數 f”,f” 吸收盈餘的參數並返回函數 f 的計算效果。

這麼形貌無疑是籠統的,我們照樣經由過程代碼來明白。這是一個簡樸的乞降函數:

add = (x, y) => x + y;

curried 以後:

curriedAdd = (x) => {
  return (y) => {
    return x + y;
  }
}
    

所以,當實行 curriedAdd(1)(2) 以後,獲得效果 3,curriedAdd(x) 函數有一個名字叫 partial application,curriedAdd 函數只須要底本 add(X, y) 函數的一部分參數。

Currying a regular function let’s us perform partial application on it.

curry 化運用

再回到之前的場景,我們設想 curry 化函數:updateSelections,

updateSelections = (selectionType) => {
  return (newValue) => {
    this.props.selectionsChanged({
      ...this.props.filterSelections,
      [selectionType]: newValue,
    });
  }
};

進一步能夠簡化為:

updateSelections = (selectionType) => (newValue) => {
   this.props.selectionsChanged({
      ...this.props.filterSelections,
      [selectionType]: newValue,
   })
};

關於 updateSelections 的偏運用(即上面提到的 partial application):

updateSelections('ages');
updateSelections('brands');
updateSelections('price');

置信人人已明白了這麼做的優點。如許一來,我們的 Filters 組件完全為:


class Filters extends React.Component {
  
  updateSelections = (selectionType) => {
    return (newValue) => {
      this.props.selectionsChanged({
        ...this.props.selections,
        [selectionType]: newValue,  // new ES6 Syntax!! :)
      });
    }
  };

  render() {
    return (
      <div>
        <PriceFilter 
          price={this.props.selections.price} 
          priceChanged={this.updateSelections('price')} 
        />
        <AgeFilter 
          ages={this.props.selections.ages} 
          agesChanged={this.updateSelections('ages')} 
        />
        <BrandFilter 
          brands={this.props.selections.brands} 
          brandsChanged={this.updateSelections('brands')} 
        />
      </div>
    );
  };
}

固然,currying 並非處理上述問題的唯一計劃。我們再來相識一種要領,舉行對照消化,updateSelections 函數 uncurried 版本:


updateSelections = (selectionType, newValue) => {
  this.props.updateFilters({
    ...this.props.filterSelections,
    [selectionType]: newValue,
  });
}

如許的設想使得每個 Filter 組件:PriceFilter、AgeFilter、BrandFilter 都要挪用 updateSelections 函數自身,而且請求組件自身感知 filterSelections 的屬性名,以舉行響應屬性的更新。這就是一種耦合,完全完成:

class Filters extends React.Component {

      updateSelections = (selectionType, newValue) => {
        this.props.selectionsChanged({
          ...this.props.filterSelections,
          [selectionType]: newValue, 
        });
      };
    
      render() {
        return (
          <>
            <PriceFilter 
              price={this.props.selections.price} 
              priceChanged={(value) => this.updateSelections('price', value)} 
            />
            <AgeFilter 
              ages={this.props.selections.ages} 
              agesChanged={(value) => this.updateSelections('ages', value)} 
            />
            <BrandFilter 
              brands={this.props.selections.brands} 
              brandsChanged={(value) => this.updateSelections('brands', value)} 
            />
          </>
        );
      };
    }

實在我以為,在這類場景下,關於兩種計劃的挑選,能夠依據開闢者的偏好來決議。

總結

這篇文章內容較為基本,但從細節入手,展現了 React 開闢編寫和函數式理念相結合的魅力。文章譯自這裏,部分內容有所修改。

廣告時候:
假如你對前端生長,特別對 React 手藝棧感興緻:我的新書中,或許有你想看到的內容。關注作者 Lucas HC,新書出書將會有送書運動。

Happy Coding!

PS: 作者 Github堆棧 和 知乎問答鏈接 迎接各種形式交換!

我的其他幾篇關於React手藝棧的文章:

從setState promise化的討論 體味React團隊設想頭腦

從setState promise化的討論 體味React團隊設想頭腦

經由過程實例,進修編寫 React 組件的“最好實踐”

React 組件設想和剖析思索

從 React 綁定 this,看 JS 言語生長和框架設想

做出Uber挪動網頁版還不夠 極致機能打造才見真章**

React+Redux打造“NEWS EARLY”單頁運用 一個項目明白最前沿手藝棧真理**

    原文作者:lucas_580e331d326b4
    原文地址: https://segmentfault.com/a/1190000014458607
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞