iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
自我挑戰組

玩轉 React 從0到1系列 第 25

【Day 25】用 SOLID 方式開發 React (2)

  • 分享至 

  • xImage
  •  

SOLID

介面隔離原則 (IIP, Interface Segregation Principle)

對於客戶端,切分成許多個小型的介面,比一個通用的介面好,除此之外,介面不應該定義不相關的方法

以 Typescript 來舉例子說明(因為 Typescript 有 Interface 關鍵字):

interface MyClock {
  currentTime: Date;  // 定義 currentTime 需為 Date 形式
  setTime(d: Date);  // 定義 setTime 這個抽象方法
}
interface MyAlertClock {
  alertWhenTimeout: Function // 定義 alertWhenTimeout 需為 Function 形式
}
class Clock implements MyClock, MyAlertClock{
  currentTime: Date;
  setTime(d: Date) {
    this.currentTime = d;
  }
  alertWhenTimeout() {
    if ( this.currentTime <= Date.now() ) {
      console.log('time has timeout!'); 
  }
}

這裏的時鐘要實作(implements)MyClock 這個介面,還需要先有 currentTime 才能 setTime,但是只要實現 MyClock 這個介面,Clock 就是一個正常的時鐘,介面之間不互相干擾(MyClock 與 MyAlertClock各自獨立),像 MyAlertClock 算是一個增強的介面,根據需要而實現。
而 React 類似的做法是依靠 PropTypes 以及搭配 DefaultProps。
舉例而言:

class ProductTable extends Component {
  ...
  render() {
    const product = {id: 1, content: '杯子', price: 200};
    return (
        <div>
          <ProductDetail product={product}
        </div>
    )
  }
}
class ProductDetail extends Component {
    static propTypes = {
        product: PropTypes.object.isRequired,
    };
    render() {
        return (
            <tr>
                <td>Id: {this.props.product.id}</td>
                <td>Content: {this.props.product.content}</td>
            </tr>
        )
    }
}

上方這個例子,可以很明顯的看見 ProductTable 是將整個 product (Object) 傳進了 ProductRow ,但事實上 ProductRow 真正使用的值只有 Id 與 Content,如果今天我們要做測試,我們就需要 Mock 整個 Product不然就會出現問題,而介面隔離原則告訴我們將介面切分到最細,不要有不相關的方法。
以下為更改後程式碼

class ProductTable extends Component {
    ...
    render() {
        const product = {id: 1, content: '杯子', price: 200};
        return (
            <div>
                ...
                  <ProductDetail id={product.id} name={product.content}/>
                ...
            </div>
        );
    }
    ...
}
class ProductDetail extends Component {
    static propTypes = {
        id: PropTypes.number.isRequired,
        content: PropTypes.string.isRequired,
    };
    render() {
        return (
            <tr>
                <td>Id: {this.props.id}</td>
                <td>Content: {this.props.content}</td>
            </tr>
        )
    }
}

依賴倒置原則 (DIP, Dependency Inversion Principle)

用戶端參照的必須是介面(抽象)而不是物件

舉例而言:

const Class = ({classroom, grade}) => (
  <li>{classroom}'s grade is {grade}</li>
)
const ListClass = ({data}) => (
  <ul>{
    data.map(item=>(
      <Class key={item.name}
        classroom={item.classroom} grade={item.grade} />
    ))
  }
  </ul>
);
ReactDOM.render(
  <ListClass data={[
      {classroom:"301",grade:"三年級"},
      {classroom:"201",grade:"二年級"}
    ]} />,
  document.getElementById('root')
);

乍看之下,這寫法是沒問題的,但如果這時候有另外一個元件也要使用 ListClass 時並且希望用不同呈現方式,這時候就會有問題了,可能就不能複用 ListClass 這個元件而要另外新增,因此這時候應該使用的是依賴倒轉原則,使得 ListClass 不要依賴於物件而是依賴於抽象。

const ListClass = ({data, ItemComponent}) => (
  <ul>{
    data.map(item=>(
      <ItemComponent key={item.name}
        {...item} />
    ))
  }
  </ul>
);   // 這裏的 ItemComponent 就是抽象
ReactDOM.render(
  <ListClass data={[
      {classroom:"301",grade:"三年級"},
      {classroom:"201",grade:"二年級"}
    ]} 
    ItemComponent={Class}/>,
  document.getElementById('root')
);

結論

  • 介紹了 IIP, DIP

/images/emoticon/emoticon81.gif


上一篇
【Day 24】用 SOLID 方式開發 React (1)
下一篇
【Day 26】關於 Deno 與 NodeJS 的這些年和那些事
系列文
玩轉 React 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言