【Day 25】用 SOLID 方式开发 React (2)

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


<<:  Chart

>>:  [Day 29] - 手把手跨出第一步!– 烧录闪烁程序到Arduino Part.2

Day 09:Python基本介绍02 | 变数、资料型态

⚠行前通知 考量到有些人可能还没学过Python,然後我的主题又是定为从HTML到Python爬虫的...

学习Python纪录Day13 - Web API、JSON

Web API Open data是一种Web API,使用HTTP请求来执行其他系统提供功能来存取...

新新新手阅读 Angular 文件 - Get data from a server(3) - Day12

学习目标 本篇内容为阅读官方文件 Get data from a server 的笔记内容。 接续 ...

强型闯入DenoLand[35] - 完赛心得

强型闯入DenoLand[35] - 完赛心得 年度回顾 今年对笔者我来说是相当特别的一年,从升上...

【Day10】表单 Form:受控元件 Controlled Component

在 React 中,允许直接用 HTML 来建立表单, 但使用 JavaScript functio...