一、前言

React 导读(三) 中介绍了项目的背景、功能需求、项目结构以及组件的划分层次,接下来我们就来看下实际的代码,这一篇文章会主要分享用到的基础组件的封装。

二、基础组件设计

我们在设计组件之前本来是有一个流程和过程的,这里我写的组件并不会像社区内的组件库一样完善或者说一定考虑很完整,但是这样也会有一个好处,可以按照自己项目的需求进行定制、扩展以及冗余的代码会更少,当然很多时候节约的这点代码可以忽略不计(特别是项目业务代码和库的代码比例上升到一定比例过后,所以一切不说场景就说某某库太大的观点都是不正确的),因为大家都有按需加载的配置可选。这不是绝对的,不一定说你自己花时间和精力去开发一个这样的库就更好,因为随着项目规模的扩大,组件的种类和需求会越来越多,即使是一个不错的工程师利用技巧保障项目持续迭代,但是人的时间和精力是有限的,更合理的利用现有资源去提高效率才是最优先考虑的事情。

我这里的基础组件实现了这么几个:
Button, Dialog, Input, Loading, Table

然后分别来介绍一下如何基础开始封装和拆合组件。其实基础组件的设计是很杀脑细胞的,如果要考虑很周全的话,因为要兼顾别人用的爽,也尽可能要保留可扩展性,基础组件如果扩展性太弱,基本等于废了。其实如果有学习设计模式是可以相互连接的,因为设计模式是成熟的经验,不是说非要在写某种逻辑代码或者做架构设计的时候才能使用,它是能够贯穿在整个软件周期内的。

(1) 思考想要如何去组织组件样式

首先这里的组件是 css 和 js 最好能够分开使用的(这里的分开使用不是指传统意义的分离,而是保持独立,可进可退),拿以前 UI 需求来看,就是在同一个结构的 HTML,加上 class 都是能够正常展现的,我这里的 css 结构是以前用过的,也没做什么改动直接拿过来使用的。这种设计思路其实和现在的组件化开发是不冲突的,组件化后还能够使得这种模式更简单的被实现,因为你只需要考虑组件这个作用域内的样式。

(2) Dialog 组件
这里先拿 Dialog 组件先举例,这里我将弹框组件分成了三部分:DialogHeader, DialogFooter, Dialog 拆分上也没什么理由,这是一种简单直接的拆分,因为很多弹框都具有这么几部分:标题、内容、按钮区域。

图片描述

而且不只是这样的才叫弹框,弹框如其名:弹出的框,所以都是可以的,比如下面这种像个 Alert 一样的弹框:

图片描述

我理解的好扩展的组件就像小时候玩的玩具一样,各部分都是可拆解可组合的,所以弹框的这三部分都需要有一定灵活的地方。来看看代码,其实蛮简单的。

class Dialog extends React.Component {
    render() {
        const {
            renderHeader,
            renderFooter,
            className,
        } = this.props;

        const header = renderHeader ? renderHeader() : null;
        const footer = renderFooter ? renderFooter() : null;

        const wrapClassName = cx('st-dialog', className);

        return (
            <div className={wrapClassName}>
                <div className="st-dialogContent">
                    {header}
                    {this.props.children}
                    {footer}
                </div>
                <div className="st-dialogMask"></div>
            </div>
        );
    }
}

上面就是一个我这里 Dialog 的结构,headerfooter采用的是render-props的方式来实现具体的插入,为什么能够采用这种方式其实不难理解,因为在 React 中,一个函数就自然就是一个组件声明,返回值就能是一个组件实例。我这里更直接,你要组件返回值,我就给你一个组件...使用上就是这样:

// 这里结构稍微代码有点多,就不要揉在一起了,给一个变量存一下更清晰,在 React 中组件的使用是自由的。
const footer = (
    <DialogFooter
        onSubmit={this.handleSubmit}
        onClose={this.handleClose}
        submitText="添加"
        closeText="关闭" />
);

<Dialog className="employeeAddDialog"
    renderHeader={() => <DialogHeader title="添加成员" />}
    renderFooter={() => footer}>
    <div className="addDialog">...</div>
</Dialog>

为什么要这样设计呢?主要还是因为有时候需求不定,万一哪天 DialogFooter 组件不是这样子,我就在外面实现好组件给这个renderFooter就行了,其他部分就不需要改动,还有就是实现的时候不要吝啬div这种容器标签的使用,多一层就多一个权重,少一层就多了一份自由。

现在还有一个问题,就是我的基础弹框有了,业务弹框各种各样,这么简单的一个封装根本不靠谱啊...那么这里你就将弹框作为一个流行的渲染组件来使用,但是是否挂载到业务模块中就使用封装的一层业务弹框来控制,比如我的业务弹框叫 EmployeeAddDialog,在 render 方法中:

if(!this.props.visible) {
    return null;
}

return (<Dialog />);

通过一个 visibleprops 值来控制是否挂载 Dialog,那么这样做就有一个好处,在处理异步弹框的时候,想什么时候关闭弹框可以由业务的流程来控制。在业务组件声明业务弹框的地方就这样:

<EmployeeAddDialog visible={this.state.visibleAddDialog}
onClose={this.handleCloseAddDialog} />

然后这样就实现了一个弹框了,灵活性和扩展性都还好,最后还有一个细节就是这个EmployeeAddDialog始终都挂载在业务组件中,业务组件渲染一次这个弹框更新周期也会走一次,所以能够继承一下PureComponent来简单避免多次执行不必要代码:

class EmployeeAddDialog extends React.PureComponent { }

看上去这个弹框组件还算干净,不可能啊,业务太复杂也不会太干净,那么脏的东西去哪儿了呢?我这里有 2 个比较脏的地方:

(1) Footer,因为有按钮,每个需求的按钮是不一样的;
(2) 弹框的内容,这里就更是千奇百怪,每个产品经理的脑洞都不一样。

我这里应对上 Footer 有一定的定制又有简单的开关,比如我就支持2个按钮:提交类、关闭类。

弹框内容我控制不了,那么我就把代码是否更脏的职责交出去,使用了 this.props.children来做这个事情,使用者的代码干净度来决定最后的业务弹框干净度。

具体的全部代码能够在这里看到:

基础 Dialog

业务 Dialog

今天先到这里吧,睡觉了?


小撸
1.4k 声望112 粉丝

热爱产品的前端攻城狮!