1
对于市面上的一些优秀UI库,如element-ui、Ant Design React、React Material-UI等,其中每个组件的核心实现由两部分组件:属性、行为。而作为一枚程序员,你是否想过自己去进行一个UI组件库的实现呢?那么本篇文章就是从认识组件化思想开始,并逐步为大家实现一个基础的UI组件库。

文章内容大致分为以下四个部分:

  1. 重新认识组件
  2. 数据管理
  3. 组件的"职责问题"
  4. UI组件的封装(button、input、dialog)

重新认识组件

对于大部分前端工程师来说,提到组件这个词,首先想到的应该是复用(可用性),具体表现就是对数据和方法的简单封装,那么一个组件在实现的过程中,应该从哪些方面去进行思考了,下面简单列举了四个点:

  1. 组件是拿来用的:应该从使用者(程序员)的感受出发
  2. 没有"最好怎么做":需要考虑项目的特点
  3. 好组件不是设计出来的,是改出来的:经常调整,有时还要重构
  4. 组件的功能应该单一、简单:不要试图把众多功能塞到一个组件中
劣质的组件库 优秀的组件库
混乱的使用风格 风格统一
不合符用户预期的使用方式 符合直觉的使用方式
多余的步骤 直接了当
过度的封装 适度的封装
无法定制 把代码写死

数据管理

数据管理应该遵循就近原则:

  • 如果数据在两个组件间公用,则应该在父级中管理此数据
  • 不要在全局数据中(redux、vuex)堆放过多数据

情况一:

情况1
情况二:
那么对于需要垮更多层级的组件来说,共享公用(全局数据),适当使用redux、vuex等更灵活、方便

组件的"职责问题"

原则上,组件应该自己搞定自己的工作,而不是让父级来帮忙(让子级处理部分工作是可以的,而且是好的),例如一个分页组件,上一页、下一页、第一页、最后页等,其细节实现逻辑就应该是该组件本身的职责,而当使用该组件的时候,只是对外提供了“接口”供外界使用,外界根本不关心其实现细节

这样的例子在生活中也随处可见。如电脑的USB接口:

Usb接口
U盘

当我们想要使用USB接口进行数据传输(通过U盘),我们只需要知道我们的U盘适合插2.0接口还是3.0接口,这样就可以进行数据传输了,并不需要知道电脑上提供的usb2.0、3.0接口的具体实现过程以及细节。

UI组件的封装

组件的继承

通过把单一组件组合起来完成更复杂的功能,接下来会对button、input、dialog等组件进行封装,来实现不同场景的dialog,如注册、删除等的dialog。该例子会以bootstrap库为基础(比较懒,不想写css)

Button组件的封装:

首先来想一个问题,一个按钮有哪些属性、哪些方法?最基本的属性应该有id或class(描述宽高、背景、边框等)、可用、禁用、childen等,方法有点击(click)。那么在遵循React对组件的写法上以及所拥有属性和方法我们来进行封装操作。

先来看一下原始代码:

<button type="button" class="btn btn-primary">按钮</button>

以及效果图:

图片描述

封装后的组件,HCButton.js:


import React, { Component } from 'react';

class HCButton extends Component {
    render() {
        return (
            <div>
                <button 
                    type="button" 
                    className={['btn',`btn-${this.props.type||'default'}`,this.props.className].join(' ')}
                    onClick = {this.props.onClick}
                    disabled = {this.props.disabled}
                >
                    {this.props.children}    
                </button>
            </div>
        );
    }
}

export default HCButton;

从上面封装的代码,来进行props对象不同属性的分析:

  • type、className:来控制按钮外在的显示情况
  • onClick:来控制按钮的点击事件
  • disabled:控制按钮是否可用
  • children:控制按钮上显示不同文字内容

使用说明

属性 类型 必传 描述
type string 值有:primary、success、warning、danger等
className string 为按钮增加自定义类
onClick function 按钮点击事件
disabled boolean 控制按钮是否可用
children string 按钮上显示的内容

使用情况一
APP.js

import HCButton from './components/HCButton'
class App extends Component {
     fn(){
        alert('按钮点击了')
     }
     render() {
        return (
          <div>
             <HCButton 
                type="success"
                onClick={this.fn.bind(this)}
                >
               确定
            </HCButton>'
         </div>   
}

运行结果一

图片描述

使用情况二

    <HCButton 
        type="danger"
        onClick={this.fn.bind(this)}
        className="big-btn"
        disabled
        >
       删除
    </HCButton>


    .big-btn{
      width: 200px;
      height: 150px;
      margin-left: 200px;
      margin-top: 200px;
    }
    

运行结果二

图片描述

Input组件的封装:

封装思路也是类似Button组件的封装,一个input标签大致有的属性:type、id、class、placeholder、name等,方法大致有:oninput、onfocus、onblur、onchange等。

原始代码:

<input type="text" class="form-control" id="username" name="username" placeholder="请输入用户名">

及效果图:

图片描述

封装后的组件,HCInput.js:

import React, { Component } from 'react';

class HCInput extends Component {
    constructor(props){
        super(props);
        this.value = this.props.value || ''
    }
    handleInput(event){
        this.value = event.target.value;
        this.props.onInput && this.props.onInput(event.target.value);
    }
    render() {
        return (
            <div>
                <input 
                    type={this.props.type || 'text'} 
                    className={['form-control',this.props.className].join(' ')} 
                    id={this.props.id} 
                    name={this.props.name} 
                    placeholder={this.props.placeholder}
                    onInput={this.handleInput.bind(this)}
                    defaultValue={this.value}
                >

                </input>
            </div>
        );
    }
}

export default HCInput;

从上面封装的代码,来进行props对象不同属性的分析:

  • type:输入框的类型
  • className:输入框自定义类
  • id:输入框的id
  • name:输入框的name
  • placeholder:输入框的placeholder
  • onInput:输入框内容改变的事件
  • defaultValue:输入框默认显示的内容

使用说明

属性 类型 必传 描述
type string 值有:text、password、email等
className string 为输入框自定义类
id string 输入框的id
name string 输入框的name
placeholder string 输入框提示信息
defaultValue string 输入框默认值
onInput funtion 通过该事件实时获取输入框的内容

使用情况一

fn1(val){
    console.log('val:',val)
}
render() {
    return (
      <div>
          <HCInput 
              type="text"
              name="username"
              placeholder="请输入用户名"
              onInput = {this.fn1.bind(this)}
            />
       </div>
     )
     
}     

运行结果一

图片描述

使用情况二

图片描述

Dialog组件的封装
思想和前面封装Button、Input类似,就不再赘述了。

原始代码:

<!--dialog-->
    <div class="dialog-shadow"></div>
    <div class="panel panel-default dialog-panel">
      <div class="panel-heading">
        <h2 class="panel-title">
          登录
          <a href="#" class="glyphicon glyphicon-remove pull-right"></a>
        </h2>
      </div>
      <div class="panel-body">
        <!--内容-->
        内容放在这里
      </div>
    </div>

及运行效果图:

图片描述

封装后的组件,HCDialog.js:

import React, { Component } from 'react';

class HCDialog extends Component {
    constructor(props){
        super(props);
        this.state = {
            show:false
        }
    }
    open(){
        this.setState({
            show:true
        })
    }
    close(){
        this.setState({
            show:false
        })
    }
    render() {
        return (
            <div>
                {
                    this.state.show ? (
                        <div>
                            {
                                this.props.shadow===false ? (<div></div>):(
                                    <div className="dialog-shadow"></div>
                                )
                            }
                            <div className="panel panel-default dialog-panel">
                            <div className="panel-heading">
                                <h2 className="panel-title">
                                {this.props.title || '对话框'}
                                {
                                    this.props.closeBtn === false ? (<div></div>):(
                                        <a className="glyphicon glyphicon-remove pull-right" onClick={this.close.bind(this)}></a>
                                    )
                                }
                                
                                </h2>
                            </div>
                            <div className="panel-body">
                                {this.props.children}
                            </div>
                            </div>
                        </div>    
                    ):(<div></div>)
                }
            </div>
        );
    }
}

export default HCDialog;

从上面封装的代码,来进行props对象不同属性的分析:

  • shadow:控制弹窗遮罩层是否显示
  • title:对话框title
  • closeBtn:控制对话框关闭按钮是否显示
  • children:对话框真正显示的内容

使用说明

属性 类型 必传 描述
shadow boolean 控制弹窗遮罩层是否显示
title string 对话框title
closeBtn boolean 控制对话框关闭按钮是否显示
children any 对话框真正显示的内容

使用情况一,登录框

    <HCDialog 
      ref="dialog" 
      title="登录"
      shadow={true}
      closeBtn={true}
    >
     <div className="u-login">
      <HCInput 
        type="text"
        name="username"
        placeholder="请输入用户名"
        onInput = {this.handleGetUName.bind(this)}
      />
      <HCInput 
        type="password"
        name="password"
        placeholder="请输入密码"
        ref="pwd"
        onInput = {this.handleGetPwd.bind(this)}
      />
      <HCInput 
        type="email"
        name="email"
        placeholder="请输入邮箱地址"
        value="hc386271623@163.com"
      />
     </div>
     <div className="login-btns">
       <HCButton type="primary" className="btn-login-ok">登录</HCButton>        
       <HCButton  className="btn-login-cancle">取消</HCButton>
     </div>
    </HCDialog>

运行效果图一:

图片描述

使用情况二,删除框:

 <HCDialog 
      ref="dialog" 
      title="删除"
      shadow={false}
      closeBtn={true}
    >
     该条商品信息删除后不再恢复,是否删除?
     <div className="login-btns">
       <HCButton type="danger" className="btn-login-ok">删除</HCButton>
       <HCButton  className="btn-login-cancle">取消</HCButton>
     </div>
 </HCDialog>

运行效果图二

图片描述

OK,以上就是关于基于组件封装思想以及简单实现几个例子的封装。最后祝大家中秋快乐,O(∩_∩)O


前端扫地僧
2.5k 声望1.2k 粉丝

慢慢理解世界、慢慢更新自己