如何設計實現一個React UI組件庫——Ant Design源碼閱讀與淺析

概述

在咱們進行平常的項目開發的過程當中,咱們常常會遇到使用一些通用的UI組件庫如BootStrap、Ant Design等。做爲成熟的UI組件庫,它可以提供提供一整套UI組件用來知足使用需求,能大大減小開發成本。javascript

在使用了他人提供的組件庫後,我天然就會有興趣去了解一下別人開發的組件庫究竟是如何設計的,如何進行相關的組件封裝。本文以Ant Design爲例,讓咱們來了解一下目前較爲有名的UI組件庫是如何設計與實現的?同時,咱們又可以有哪些經驗能夠借鑑?css

閱讀本文,你最好有以下的基礎知識來幫助你理解本文內容:java

PS: 博客寫一半,手受傷骨折了囧囧囧,後面大部分文字是經過語音輸入轉換的,若是有什麼錯誤或者邏輯不清晰,歡迎在評論中指正。react

如何實現單個的React UI組件

首先,須要瞭解Ant Design提供的組件,咱們先來看下單個組件是如何實現的。git

目錄結構

須要瞭解一個組件的內容,咱們應該先從目錄接口開始。咱們以avatar頭像組件爲例,目錄結構以下圖所示:github

咱們一個一個來看一下:typescript

  • index.tsx,UI組件源文件,即TSX(TypeScript+JSX),包含整個組件的內容和邏輯。
  • index.zh-CN.md,組件使用說明文檔
  • style,UI組件樣式文件,包含當前UI組件的相關樣式
  • tests,UI組件測試文件,包含當前UI組件相關的單元測試,使用Jest單元測試框架。
  • demo,用來進行展現和用法示例說明的文檔

介紹完了目錄結構,咱們來看下這個插件的具體內容。antd

TSX文件

咱們首先來看一下這個插件的TSX文件。這個文件包含了插件的結構和功能。如代碼示例示例所示:app

export interface AvatarProps {
  /** Shape of avatar, options:`circle`, `square` */
  shape?: 'circle' | 'square';
  /** Size of avatar, options:`large`, `small`, `default` */
  size?: 'large' | 'small' | 'default';
  /** Src of image avatar */
  src?: string;
  /** Type of the Icon to be used in avatar */
  icon?: string;
  style?: React.CSSProperties;
  prefixCls?: string;
  className?: string;
  children?: any;
}

export interface AvatarState {
  scale: number;
  isImgExist: boolean;
}
複製代碼

咱們先看一下聲明文件。在你TypeScript聲明中咱們能夠看到:它經過interface定義了props和state兩個屬性的值。這樣能夠明確界定傳入的屬性和內部的屬性類型,在代碼規範和質量中也可以有一個保證。框架

下面讓咱們來看一下具體的組件類。具體示例以下:

export default class Avatar extends React.Component<AvatarProps, AvatarState> {
    render() {
        const {
          prefixCls, shape, size, src, icon, className, ...others,
        } = this.props;
    
        const sizeCls = classNames({
          [`${prefixCls}-lg`]: size === 'large',
          [`${prefixCls}-sm`]: size === 'small',
        });
    
        const classString = classNames(prefixCls, className, sizeCls, {
          [`${prefixCls}-${shape}`]: shape,
          [`${prefixCls}-image`]: src && this.state.isImgExist,
          [`${prefixCls}-icon`]: icon,
        });
    
        let children = this.props.children;
        if (src && this.state.isImgExist) {
          children = (
            <img
              src={src}
              onError={this.handleImgLoadError}
            />
          );
        } else if (icon) {
          children = <Icon type={icon} />;
        } else {
          const childrenNode = this.avatarChildren;
          if (childrenNode || this.state.scale !== 1) {
            const childrenStyle: React.CSSProperties = {
              msTransform: `scale(${this.state.scale})`,
              WebkitTransform: `scale(${this.state.scale})`,
              transform: `scale(${this.state.scale})`,
              position: 'absolute',
              display: 'inline-block',
              left: `calc(50% - ${Math.round(childrenNode.offsetWidth / 2)}px)`,
            };
            children = (
              <span
                className={`${prefixCls}-string`}
                ref={span => this.avatarChildren = span}
                style={childrenStyle}
              >
                {children}
              </span>
            );
          } else {
            children = (
              <span
                className={`${prefixCls}-string`}
                ref={span => this.avatarChildren = span}
              >
                {children}
              </span>
            );
          }
        }
    return (
      <span {...others} className={classString}>
        {children}
      </span>
    );
  }
}
複製代碼

從上面的示例代碼中咱們能夠看到,這是一個很常規的React的組件類。它經過傳入的屬性來判斷應該選擇哪一種方式渲染頭像,而後完成組件的渲染過程。同時,在組件渲染完成後,這個組件會根據參數來調整相關的圖片大小用於適配。

這樣,一個簡單的UI組件就已經基本知足相關的功能了。

接下來,讓咱們來看下樣式相關的文件。

Style文件

仍是以Avatar組件爲例,具體的樣式代碼這裏就不舉例了,只像你們介紹下:Ant Design的樣式是經過Less語言來完成的,而且經過TSX文件來進行引入。

Test文件

Test文件經過enzyme來對React組件進行測試。咱們簡單介紹如下enzyme,這是一個對React組件進行測試的JavaScript框架,可以提供mount等相關API接口來對組件選人和相關的數據更新操做進行測試。

咱們選取一部分測試示例以下:

import React from 'react';
import { mount } from 'enzyme';
import Avatar from '..';

describe('Avatar Render', () => {
  it('Render long string correctly', () => {
    const wrapper = mount(<Avatar>TestString</Avatar>);
    const children = wrapper.find('.ant-avatar-string');
    expect(children.length).toBe(1);
  });

  it('should render fallback string correctly', () => {
    const div = global.document.createElement('div');
    global.document.body.appendChild(div);

    const wrapper = mount(<Avatar src="http://error.url">Fallback</Avatar>, { attachTo: div });
    wrapper.instance().setScale = jest.fn(() => wrapper.instance().setState({ scale: 0.5 }));
    wrapper.setState({ isImgExist: false });

    const children = wrapper.find('.ant-avatar-string');
    expect(children.length).toBe(1);
    expect(children.text()).toBe('Fallback');
    expect(wrapper.instance().setScale).toBeCalled();
    expect(div.querySelector('.ant-avatar-string').style.transform).toBe('scale(0.5)');

    wrapper.detach();
    global.document.body.removeChild(div);
  });
});
複製代碼

經過上面的示例咱們能夠知道,enzyme可以根據Avatar組件渲染後的數據來對組件進行測試。

UI組件庫究竟是如何實現以及與使用者交互的

其實UI組件與咱們本身開發的React組件沒有什麼太大的區別,只是一個提供了部分UI和功能的第三方組件而已。想明白了這點,咱們就能知道,咱們開發的組件與第三方UI組件的交互就是經過Props的方式。

以上面的Avatar組件爲例,咱們給組件傳遞sharp, sizesrc等字段,Avatar組件收到相關數據後,在內部進行相關的處理,最終返回一個React組件。具體示例以下:

import Avatar from './avatar/';

class Container extends React.Component {
    render() {
        return (
            <div>
                <Avatar sharp="circle"  size="large"  src="https://www.baidu.com">Avatar!!</Avatar>
            </div>
        );
    }
}
複製代碼

若是咱們須要引入相關樣式文件,咱們則須要引入編譯後的css文件,具體方式以下:

@import '~antd/dist/antd.css';
複製代碼

經過Ant Design,咱們在開發UI組件時學到了什麼

經過對UI組件庫源碼的閱讀,我獲得了以下的一些經驗。

結構清晰

每個UI組件都是一個完整的模塊,都應該有本身獨立的目錄結構;同時全部的UI組件都是屬於同一類,所以全部的UI組件的目錄結構應該類似。

Ant Design中每個UI組件的目錄結構都如前幾章中所述。擁有一個清晰的目錄結構可以方便咱們進行代碼管理,同時也可使用腳本作一些自動化的處理。好比Ant Design就經過腳原本對全部components/**/style文件夾中的less文件進行合併編譯。

組件分離

每一個UI組件應該都是能夠獨立被引用的,並且也應該優先使用「獨立引用」的方式。

Ant Design的每個組件均可以被獨立引用,引用方式以下:

import Button from 'antd/lib/button';
複製代碼

咱們在使用第三方UI組件庫時,一般不會使用到上面全部的組件,而是常用到部分組件。所以咱們在設計UI組件庫時,處於文件大小的考慮,咱們也應該保證每一個UI組件都互不依賴(同一層級的組件,排除自己業務上就有依賴關係的組件),作到不使用的組件不引入,減少業務方文件大小。

測試覆蓋

每個UI組件都是獨立的,所以咱們須要爲每個獨立的組件進行測試覆蓋。

Ant Design中經過Jest和上文提到的enzyme來對每個組件進行測試,從而保證UI組件的代碼質量。

總結

整體上來講,Ant Design相關的源代碼簡單易懂,結構也很清晰,很是容易閱讀。若是你對React開發有必定的瞭解,可是不知道如何進行組件的封裝,或者想了解當前主流的組件庫是如何實現的,推薦能夠閱讀一下相關源碼。你可以從中瞭解到咱們如何對UI組件進行切割和封裝。

固然,Ant Design不只僅是一個UI組件庫,而是一整套UI規範,咱們今天分享的只是這套規範在React上面的實現。若是對相關的UI規範有興趣的同窗,能夠去Ant Design的官網進行了解。

相關文章
相關標籤/搜索