• home > webfront > ECMAS > vue >

    前端组件化:Vue/React组件设计思想与遵从原则

    Author:zhoulujun Date:

    从页面元素的可复用性角度考虑,我们将将组件按类型分为公众组件、容器组件和视图组件。模块(Module)通常强调的是职责(分离、内聚),组件是可复用模块和相关依赖的封装。组件拆分目的:复用与隔离


    组件和插件的区别是什么呢?插件是集成到某个平台上的,比如 Jenkins 插件、Chrome 插件等等,jQuery 插件也类似。平台只提供基础能力,插件则提供一些定制化的能力。

    而组件则是偏向于 ui 层面的,将 ui 和业务逻辑封装起来,供其他人使用。

    在一些最简单无脑的 jQuery 插件中,它们一般会将 DOM 结构直接写死到插件中,这样的插件拿来即用,但限制也比较大,我们无法修改插件的 DOM 结构。

    // 轮播图插件
    $("#slider").slider({
        config: {
            showDot: true, // 是否展示小圆点
            showArrow: true // 是否展示左右小箭头
        }, // 一些配置
        data: [] // 数据
    })

    还有另一种极端的插件,它们完全不把 DOM 放到插件中,但会要求使用者按照某种固定格式的结构来组织代码。

    一旦结构不准确,就可能会造成插件内部获取 DOM 出错。但这种插件的好处在于可以由使用者自定义具体的 DOM 结构和样式。

    <div id="slider">
        <ul class="list">
            <li data-index="0"><img src="" /></li>
            <li data-index="1"><img src="" /></li>
            <li data-index="2"><img src="" /></li>
        </ul>
        <a href="javascript:;" class="left-arrow"><</a>
        <a href="javascript:;" class="left-arrow">></a>
        <div class="dot">
            <span data-index="0"></span>
            <span data-index="1"></span>
            <span data-index="2"></span>
        </div>
    </div>
    <script>
    $("#slider").slider({
        config: {} // 配置
    })
    </script>

    当然,你也可以选择将 DOM 通过配置传给插件,插件内部去做这些渲染的工作,这样的插件比较灵活。有没有发现?这和 render props 模式非常相似。

    $("#slider").slider({
        config: {}, // 配置
        components: {
            dot: (item, index) => `<span data-index=${index}></span>`,
            item: (item, index) => `<li data-index=${index}><img src=${item.src} /></li>`
        }
    })

    前面讲了几种 jQuery 插件的设计模式,其实万变不离其宗,不管是 jQuery 还是 React、Vue、AngularJS,组件设计思想都是一样的。

    为什么要组件化

    1. 组件化是对实现的分层,是更有效地代码组合方式

    2. 组件化是对资源的重组和优化,从而使项目资源管理更合理

    3. 组件化有利于单元测试

    4. 组件化对重构较友好

    组件设计原则

    1. 适当的组件粒度:一个组件尽量只做一件事。

    2. 复用相同部分:尽量复用不同组件相同的部分。

    3. 松耦合:组件不应当依赖另一个组件。

    4. 数据解耦:组件不应该依赖特定结构的数据。

    5. 结构自由:组件不应该封闭固定的结构。


    image.png

    容器组件与展示组件

    顾名思义,容器组件就是类似于“容器”的组件,它可以拥有状态,会做一些网络请求之类的副作用处理,一般是一个业务模块的入口,比如某个路由指向的组件。我们最常见的就是 Redux 中被 connect 包裹的组件。
    容器组件有这么几个特点:

    • 容器组件常常是和业务相关的。

    • 统一的数据管理,可以作为数据源给子组件提供数据。

    • 统一的通信管理,实现子组件之间的通信。

    展示组件就比较简单的多,在 React 中组件的设计理念是 view = f(data),展示组件只接收外部传来的 props,一般内部没有状态,只有一个渲染的作用。

      image.png

    Vue组件库包含组件(Component)、指令(Directive)和过滤器(Filter)三种类型的存在。

    组件多层嵌套时,应该把Modal放在根组件里,然后在子组件里通过事件触发。在具体应用里,应该这么用,这符合Vue提倡的“状态驱动”。

    组件化设计流程

    组件化的工作方式信奉独立、完整、自由组合。目标就是尽可能把设计与开发中的元素独立化,使它具备完整的局部功能,通过自由组合来构成整个产品

    从页面元素的可复用性角度考虑,我们将将组件按类型分为公众组件、容器组件和视图组件。

    模块(Module)通常强调的是职责(分离、内聚),组件是可复用模块和相关依赖的封装。

    组件可以如下定义:

    • 可复用的模块,完成既定功能

    • 有明确的接口规定

    • 有上下文依赖、外部依赖资源的定义

    • 可以独立发布

    组件设计的一些理念

    在设计组件的时候需要考虑到很多方面,以便它们可以很好的复用,组合,分离和低耦合

    以下原则尽可能使用

    • 单一职责原则

      • 组件里的每个模块,分别该承担某一个功能

      • 多个组件 / 模块协同完成一件事,而不是一个组件替其他组件完成本该它自己完成的事情

    • 开放封闭原则

      • 属性配置等 API 对外开放;组件内部 dom 及状态的更改、对外封闭

    • 高内聚、低耦合

      • 组件内部通过 callback 方式直接调用,组件与组件之间通过发布订阅的模式通信

    • API 尽量和已知概念保持一致

      • API 命名:比如 聚焦 常用命名是 focusable 而不是 canFocus 等自己臆想的名字、还有如 onDeselect 等规范名字。

      • API 的功能要单一并表意:比如 active 表示活动状态、但不能代替表示 selected 选中状态。

    • 追求短小精悍

    • 避免太多参数

    • 缩小信赖范围和向稳定方向信赖

    • 适用SPOT法则 (Single Point Of Truth,就是尽量不要重复代码,出自《The Art of Unix Programming》)

    • 无副作用

    • 引用透明

    • 避免暴露组件内部实现

    • 避免直接操作DOM

    • 适用好莱坞法则 (好莱坞法则: Don’t call us, we’ll call you, 又称IoC, Inversion of control, 控制反转)

    • 入口处检查参数的有效性,出口处检查返回的正确性

    • 充分隔离变化的部分

    • 组件和数据分享,信赖一致性的数据结构


    以下 8项是我认为值得去注意的

    • 层次结构和 UML 类图

    • 扁平化、面向数据的 state/props

    • 更加纯粹的 State 变化

    • 低耦合

    • 辅助代码分离

    • 提炼精华

    • 及时模块化

    • 集中/统一的状态管理

    单一职责

    组件拆分目的复用与隔离

    • 隔离的类型,组件业务必然很重,此时虽然要保证组件尽可能简单,

    • 复用类型的,通用性更强,所以功能越单一,使用起来就越方便。

    我们知道 react 有一个概念:container/component,

    即 component 只是渲染组件,而 container 才是产生业务的组件,我们 Vue 也可以依照这个理念进行设计。

    即把数据处理等带有副作用的工作放在父组件中,而子组件只进行展示或操作,通过事件的方式让父组件进行处理

    保证逻辑归一,后续维护也更为方便。或者使用 slot 等类似高阶组件的方式来简化当前组件的内容。

    无副作用/引用透明

    和纯函数类似,设计的一个组件不应该对父组件产生副作用,从而达到引用透明(引用多次不影响结果)。

    数据操作前必须进行复制。比如需要添加额外的键值,或者需要对数组类型的数据进行操作,会对原始数据产生影响

    注:引用类型的 props 千万不要直接修改对象,虽然能够达到传递数据的目的,但会产生副作用,如果有其他地方用到该数据,可能产生未知的影响。

    组件划分颗粒度

    组件拆分出来之后,拆成几层或者是拆成几块,影响文件的数量。如果层级比较多,各种 props 传递,事件传递,维护成本比较高。

    保证逻辑处理集中在一个组件,维护也比较方便。

    新功能下添加新属性/新文件

    对于通用类型组件,我们要求它尽可能的短小精悍,调用起来更为简单,所以不能设计太多的参数。基础组件库不能符合这个要求,

    主要是因为基础组件库需要尽可能增加普适性,不会因为没有某个常用的属性,导致该组件需要复制一份重写,再加上日积月累的 pull request,

    属性和参数必然会越来越多。而我们在业务中使用,完全不需要这么多的配置,如果有重大差别,重新复制一份,对于后续的维护反而更方便。

    所以是否新增加属性还是拷贝一份,是根据后续该组件是否会产生比较大的发展方向差异来决定的。

    Vue 组件之间的交互设计

    Vue 组件与 React 组件有比较大的区别,模板的设计更偏向于 HTML,所以要实现类似 react 的高阶组件的需求通常比较少,

    而高阶组件集成度过高,对于业务来说,当业务越来越复杂,组件内部逻辑将拆分困难,未必是件好事,所以我们只讨论普通的组件设计。

    组件设计是考虑组件通讯方式,主要分为以下几个方面:数据流转(向下传值,向上传值),伪双向绑定,方法调用

    简化与抽离的其他实现

    使用插件或者 mixins 实现


    抄袭源头:

    浅谈 React 组件设计 https://github.com/yinguangyao/blog/issues/40

    如何去设计一个组件封装_前端组件化设计思路 www.fly63.com/article/detial/996

    漫谈Vue组件库开发 https://jdc.jd.com/archives/212167

    [译] 前端组件设计原则 https://juejin.im/post/5c49cff56fb9a049bd42a90f

    前端工程——基础篇 https://github.com/fouber/blog/issues/10

    前端组件设计杂谈 warmhug.github.io/2018/09/09/components-design-experience.html

    化整为零!关于组件化设计升级的一些思考 https://www.imspm.com/article/1499653624086?p=1&m=0


    转载本站文章《前端组件化:Vue/React组件设计思想与遵从原则》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/8526.html