在咱們進行平常的項目開發的過程當中,咱們常常會遇到使用一些通用的UI組件庫如BootStrap、Ant Design等。做爲成熟的UI組件庫,它可以提供提供一整套UI組件用來知足使用需求,能大大減小開發成本。javascript
在使用了他人提供的組件庫後,我天然就會有興趣去了解一下別人開發的組件庫究竟是如何設計的,如何進行相關的組件封裝。本文以Ant Design爲例,讓咱們來了解一下目前較爲有名的UI組件庫是如何設計與實現的?同時,咱們又可以有哪些經驗能夠借鑑?css
閱讀本文,你最好有以下的基礎知識來幫助你理解本文內容:java
PS: 博客寫一半,手受傷骨折了囧囧囧,後面大部分文字是經過語音輸入轉換的,若是有什麼錯誤或者邏輯不清晰,歡迎在評論中指正。react
首先,須要瞭解Ant Design提供的組件,咱們先來看下單個組件是如何實現的。git
須要瞭解一個組件的內容,咱們應該先從目錄接口開始。咱們以avatar頭像組件爲例,目錄結構以下圖所示:github
咱們一個一個來看一下:typescript
介紹完了目錄結構,咱們來看下這個插件的具體內容。antd
咱們首先來看一下這個插件的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組件就已經基本知足相關的功能了。
接下來,讓咱們來看下樣式相關的文件。
仍是以Avatar組件爲例,具體的樣式代碼這裏就不舉例了,只像你們介紹下:Ant Design的樣式是經過Less語言來完成的,而且經過TSX文件來進行引入。
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組件與咱們本身開發的React組件沒有什麼太大的區別,只是一個提供了部分UI和功能的第三方組件而已。想明白了這點,咱們就能知道,咱們開發的組件與第三方UI組件的交互就是經過Props的方式。
以上面的Avatar組件爲例,咱們給組件傳遞sharp
, size
,src
等字段,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';
複製代碼
經過對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的官網進行了解。