前言

关于前端中台的一些事

越来越多的公司都有前端中台,说起中台,这个最早是从阿里在2015年提出的“大中台,小前台”战略中延伸出来的。

一般来说公司随着业务不断发现,业务线不断增加,会出现各个业务线虽然业务不同,但是核心底层架构(项目搭建的相关脚手架,项目目录,编译,部署)和公共组件(地区选择,日期,弹层,表单校验等)都是一致的情况。如果各个业务线在完成业务的同时还要自己搭建项目,开发各类组件,必然会导致重复开发、重复建设,效率低,产研资源浪费,进而影响产品上线时间。

在竞争如此激烈的时代,为了快速迭代产品,为了公司能快速的发展,就需要一个中间平台去帮各业务线完成底层的相关搭建,公共框架组件的封装。

在这个大背景下,作为中台的一名前端,职责就是开发维护供整个公司前端使用的一些公共组件(插件),也总结了相关经验。

正文

思路与逻辑

  1. 在组件开发之前,需要根据需求考虑组件需要实现的功能,这些功能需要暴露出去。
  2. 设计组件,秉着“最简单的调用,最灵活的扩展”原则,也就是使用者可以传入最少的配置参数完成组件调用展示,也可以通过配置进行灵活的定制化。例如开发一个公共弹层组件,业务端既可以只传入文本进行简单调用展示,也可以扩展配置,实现一个更加定制,更加复杂的弹层。 例如antd的Modal组件。
  3. 我们所开发的组件一般都会发布npm包,方便调用和维护。各个公司都有自己的npm, npm包的一般目录如下,当然如果简单的组件可能只有index.js,目录根据实际设置就好。image.png
  4. 默认情况调用的入口就是index.js, 多数组件是一个类,一般我们在

举例

  • 使用类的方式实现一个原生js的简单订阅发布插件:
class EventListner{
    constructor(){
        // 单例模式保证此类只创建一次
        if (EventListner['EventListnerInstance']) {
        return EventListner['EventListnerInstance']
        }
        EventListner['EventListnerInstance'] = this;
        this.subscribeList = {} // 订阅事件名称
    }
    //订阅
    on(evt, fn){
        if (typeof fn === 'function') {
            this.subscribeList[evt] = this.subscribeList[evt] || []
            this.subscribeList[evt].push(fn)
            // unique为对比同一监听事件的回调是否一样,一样则排除
            this.subscribeList[evt] = unique(this.subscribeList[evt], (item1, item2) => item1.toString() === item2.toString())
        }
    }
    // 发布
    publish(...args) {
        const fns = this.subscribeList[args[0]]
        if (fns !== undefined) {
            const _args = args[1] || ''
            for (const fn in fns) {
                if (Object.prototype.hasOwnProperty.call(fns, fn)) {
                    fns[fn](_args)
                }
            }
        }
    }
    // 解绑事件
    off(ev, fn) {
        if (typeof ev !== 'string') {
            return
        }
        if (this.subscribeList.hasOwnProperty(ev)) {
            this.subscribeList[ev] = []
        }
        fn && fn()
    }
}
export default new EventListner();
  • 既支持组件方式可以传入子组件,又需要支持方法直接调用, 类似antd的Modal组件。
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
// 实现组件方式调用
class Dialog extends Component {
    componentDidMount() {
        this.appendDialog()
    }
    componentDidUpdate() {
        this.appendDialog()
    }
    appendDialog() {
        // 需要根据传入的props处理渲染的相关逻辑
        ...
    }
    render() {
        return null
    }
}
// 将各配置进行类型检查
Dialog.propTypes = {
    title:'', // 标题
    content:'', // 内容
    ...
}

// 实现方法直接调用
// 各种success, error,warning等都会走的逻辑,提出一个方法来
function confirm(props) {
    let _confirm
    if(!document.querySelector('dialog-container')){
        _confirm = document.createElement('div')
        _confirm.id = 'dialog-container'
        document.body.appendChild(_confirm)
    }
    ReactDOM.render(
        // 此处渲染一个弹层
        <DialogContent {...props} />,
        _confirm,
    )
}
// 此处只举例其中一种弹层success,其他error,confirm都类似只有图标,按扭等不同
Dialog.success = function(props) {
    const config = {
        footer: false,
        width: 280,
        position: 'center',
        visible: true,
        closeBtn: false,
        ...props,
    },
    return confirm(config)
}
export default Dialog
  • 继承,并改写父类一些方法的sdk。

这通常是在某个底层sdk需要再包一层嵌套业务的逻辑时使用。比如说公司需要一个直播的服务,需要中台的前后端配合提供,前端负责封装sdk, 后端负责提供接口和服务,这时按照我们之前的思路是可以实现,但是随着业务的扩展,当一个新兴的业务线也需要一个直播服务,但是他们想要自己去搭建后端服务,前端底层逻辑与API接口是一致的。这时候我们之前所写的融入了一些业务逻辑的sdk就不能满足了。这种情况我们需要将底层逻辑与业务相关逻辑拆分为两个类,基类只提供基本的方法与调用要足够纯净,继承的子类去实现通用业务逻辑处理。这样当业务逻辑层无法满足时,可以自行封装子类去实现。

class Base{
    getSigniture(){
       this.getVideoSigniture()
    }
    play(){
    }
    pause(){
    }
    ...
}
class Child1 extends Base{
    getVideoSigniture(){
        // 此处自行实现该子类需要的逻辑
        ...
    }
}

常用到的一些设计模式

1, 单例模式。
当我们创建的sdk或插件在使用中可能会被多次实例化时,需要在我们的sdk或插件的构造函数中处理,多次实例化返回指向同一个对象的指针。

2, 发布订阅。
此类设计模式在im的sdk中常常用到,因为链接、接收消息等都是异步的,用户需要去监听链接成功、断开,接受消息等事件

总结

其实业务前端在平时工作中同样会需要封装一些供自己业务端使用的公共组件或插件,方便多个项目同时使用,也方便后期维护。
以上均为工作中的一些总结,还有很多需要改进和学习的地方,欢迎大家提出问题与建议。


Jamie
13 声望1 粉丝

逆水行舟,不进则退