乐趣区

深入理解组件化思想及手动封装实现一个React-UI库

对于市面上的一些优秀 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) 堆放过多数据

情况一:


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

组件的 ” 职责问题 ”

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

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


当我们想要使用 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

退出移动版