React元件設計模式(一)

陳小俊發表於2019-02-16

完整程式碼可檢視github,這裡擷取的程式碼不影響理解就行。

頁面效果可檢視gitPage

首先編寫一下我們的公共元件

單個商品元件(商品元件:展示價格、購買數量)

goodsItem.js

// 單個商品
import React from `react`;
const GoodsItem = props => {
  const { goods: {name, num, price}, handleSub, handleAdd } = props;
  return <div className="goods-item">
    {name}  
    <button onClick={() => handleSub()}>-</button>
    <span>{num}</span>
    <button onClick={() => handleAdd()}>+</button>
    價格:{price}
  </div>
};
export default GoodsItem;

商品列表元件(迴圈展示庫中的商品)

goodList.js

// 商品列表

import React from `react`;
import GoodsItem from `./goodsItem`;
class GoodsList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      goodsData: []
    }
  }
  componentDidMount() {
    const { goodsData } = this.props;
    this.setState({ goodsData: goodsData});
  }
  handleAddorSub(id, type) {
    let { goodsData } = this.state;
    let newGoods = goodsData.reduce((newData, goods) => {
      if (goods.id === id) {
        goods.num = type === `+` ? goods.num + 1 : goods.num - 1;
      }
      newData.push(goods);
      return newData;
    }, [])
    this.setState({ goodsData: newGoods })
  }
  render() {
    const { goodsData } = this.state;
    return (
      <div className="goods-list">
        {goodsData.map(goods =>
          <GoodsItem 
            key={goods.id} 
            goods={goods}
            handleAdd={() => this.handleAddorSub(goods.id, `+`)}
            handleSub={() => this.handleAddorSub(goods.id, `-`)}
          />
        )}
      </div>
    );
  }
};

export default GoodsList;

我們一般編寫元件,都會這麼去做,list包裹item,迴圈展示item。資料放在list元件中,item作為一個無狀態元件,只做他的展示。

資料互動通過props傳遞,點選+-會改變購物車裡的資料。

現在需求來了,雙12來了(就在昨日),所有商品8折優惠。

這意味著我們需要修改goodsData中所有商品的價格。

這並不難,我葉良辰有100種方法可以修改商品資料。找個可行的生命週期,比如componentDidMount中修改list元件state.goodsData就行了。

如果每次修改都直接修改goodList元件,也是可以的,大不了多傳幾個props來判斷需要打折還是修改商品名稱等等。

但是有些需求是交叉的,如果一直這樣寫,久而久之元件會變得越來越臃腫,最後爆炸。

好的解決方案應該是goodsList不去動他,外加一層來進行包裝,實現我們的邏輯。

這樣既保證了goodsList的,又能實現邏輯的複用。可謂一箭雙鵰。

用兩種元件設計模式可以幫助到我們。

一. renderProps 模式

renderProps其實是利用元件的props.children api,將函式當成元件的一種寫法。

我們呼叫公共元件的方法如下:

<GoodsList goodsData={goodsData} />

我們用renderProps模式實現打折商品元件:

<DiscountedGoodsList goodsData={goodsData}>
  {(data) => <GoodsList goodsData={(data)} />}
</DiscountedGoodsList>

可以看到,DiscountedGoodsList的子元件是一個函式,那麼一個函式是怎麼渲染成元件的?

再來看看DiscountedGoodsList元件的程式碼:

const DiscountedGoodsList = props => {
  // 8折優惠邏輯
  const setRenderPropsData = (data) => {
    let renderPropsData = data.reduce((array, goods) => {
      let obj = {};
      for (let k in goods) {
        obj[k] = k === `price` ? (goods[k] * .9).toFixed(2) : goods[k];
      }
      array.push(obj);
      return array;
    }, []);
    return renderPropsData;
  }

  let goodsData = setRenderPropsData(props.goodsData);

  return (
    <React.Fragment>
      {props.children(goodsData)}
    </React.Fragment>
  );
}

setRenderPropsData的作用是實現8折優惠邏輯,將所有商品價格調整。

然後呼叫props.children這個api,得到在上面我們編寫的函式。

props.children也就是函式(data) => <GoodsList goodsData={(data)} />的引用。

將處理後的資料goodsData作為引數執行,最終返回<GoodsList />元件,這就是renderProps模式。

以後我們需要呼叫價格優惠的商品列表元件,直接呼叫DiscountedGoodsList即可。

renderProps的模式實現了邏輯的共用,且對GoodsList元件毫無副作用,從而達到我們的目的。

二. HOC(高階元件)模式

所謂的高階元件,其實就是一個函式,該接受component為引數,返回一個處理後的component。

編寫我們的高階元件如下:

const BrandGoodsList = (Component, goodsData) => {
  // 8折優惠邏輯
  const setRenderPropsData = (data) => {
    let renderPropsData = data.reduce((array, goods) => {
      let obj = {};
      for (let k in goods) {
        obj[k] = k === `name` ? goods[k] + `【品牌】` : goods[k];
      }
      array.push(obj);
      return array;
    }, []);
    return renderPropsData;
  }

  let brandGoodsData = setRenderPropsData(goodsData);
  return <Component goodsData={brandGoodsData} />
}

BrandGoodsList元件的邏輯就是給商品名稱加上【品牌】的標示,區分商品。

高階元件的呼叫比較簡單:{BrandGoodsList(GoodsList, goodsData)} 直接執行返回元件,然後渲染。

實現了兩種模式,現在我們將他們一起用,實現一個既打折,又是品牌商品的元件。

<DiscountedGoodsList goodsData={goodsData}>
  {(data) => BrandGoodsList(GoodsList, data)}
</DiscountedGoodsList>

挺舒服的吧,隨時分離,隨時結合。正是高內聚、低耦合本人啊。

最後,完整的呼叫看一下:

<div className="App">
    基本商品列表元件:
    <GoodsList goodsData={goodsData} />
    <br />

    打8折商品列表元件(renderProps模式實現):
    <DiscountedGoodsList goodsData={goodsData}>
      {(data) => <GoodsList goodsData={(data)} />}
    </DiscountedGoodsList>
    <br />

    品牌商品列表元件(高階元件模式實現):
    {BrandGoodsList(GoodsList, goodsData)} 
    <br />
    
    既是打折商品,又是品牌商品(兩種模式複用)
    <DiscountedGoodsList goodsData={goodsData}>
      {(data) => BrandGoodsList(GoodsList, data)}
    </DiscountedGoodsList>
  </div>

總結:

1、renderProps 模式的核心是props.children的使用。

2、高階元件的寫法看起來更舒服,比較受歡迎。

3、兩種模式解決的問題:複用邏輯、不汙染底層元件。

覺得有幫助的點個贊,甚至可以關注一波哦~

相關文章