共计 5517 个字符,预计需要花费 14 分钟才能阅读完成。
对于市面上的一些优秀 UI 库,如 element-ui、Ant Design React、React Material-UI 等,其中每个组件的核心实现由两部分组件:属性、行为。而作为一枚程序员,你是否想过自己去进行一个 UI 组件库的实现呢?那么本篇文章就是从认识组件化思想开始,并逐步为大家实现一个基础的 UI 组件库。
文章内容大致分为以下四个部分:
- 重新认识组件
- 数据管理
- 组件的 ” 职责问题 ”
- UI 组件的封装(button、input、dialog)
重新认识组件
对于大部分前端工程师来说,提到组件这个词,首先想到的应该是复用(可用性),具体表现就是对数据和方法的简单封装,那么一个组件在实现的过程中,应该从哪些方面去进行思考了,下面简单列举了四个点:
- 组件是拿来用的:应该从使用者 (程序员) 的感受出发
- 没有 ” 最好怎么做 ”:需要考虑项目的特点
- 好组件不是设计出来的,是改出来的:经常调整,有时还要重构
- 组件的功能应该单一、简单:不要试图把众多功能塞到一个组件中
劣质的组件库 | 优秀的组件库 |
---|---|
混乱的使用风格 | 风格统一 |
不合符用户预期的使用方式 | 符合直觉的使用方式 |
多余的步骤 | 直接了当 |
过度的封装 | 适度的封装 |
无法定制 | 把代码写死 |
数据管理
数据管理应该遵循 就近原则:
- 如果数据在两个组件间公用,则应该在父级中管理此数据
- 不要在全局数据中 (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