现在我接手一个项目”好货网”, 该项目已经上线两年, 我的 title 是前端技术负责人, 团队现有三名前端工程师, 其中一人刚入职.
项目用原生 JS+CSS 编写, 没有使用任何框架, 整个网站共有 10 个一级页面, 20 个子页面, 页面风格一致.
当有新需求时, 开发人员会新建一个页面, 如果新页面的部分模块和已有页面雷同, 会拷贝代码到新页面中, 基本不考虑复用和抽取.
为了解决上述五个问题, 我决定开展一个技术项目, 即实现项目组件化
4.1 框架的选择 有两种方案可以选择, 一种是原生 JS 组件化, 另一种是引入 react,vue 等框架. 考虑到团队成员的水平, react 和 vue 基本没用过, 这样会有学习成本, 而且这些框架入门容易,用好难, 考虑到团队成员的 js 基础还不错, 保险起见, 决定采用 js 实现组件化.
4.2 制定方案 分析所有页面后发现, 30 个页面的风格一致, 按钮,下拉框,弹出层等都一致,商品展示模块 ui 一致, 用户信息模块 ui 一致, 所以打算分为基础组件和业务组件两部分进行封装, 为了保证线上功能稳定性和迭代的正常开发, 按如下步骤完成最终的上线:
4.3 组件的实现过程 组件分为两大类, 一类是基础组件, 一类是业务组件, 业务组件使用基础组件+业务逻辑进行封装, 除了一些简单的基础组件,比如 button 等, 其他组件都继承一个组件基类 Component
4.3.1 基类 Component 的实现 1. Component 有三个最重要的生命周期: init: 组件实例被创建, 我们在这里去创建一个 div mounted: 在 div 挂在到 dom 树上, 该方法会触发, 供子类去重写 unmounted: 把 div 移除 dom 树 2. 提供一个 attributes 数组, 用于存放组件的属性值, 比如 style 属性,data 属性, 由使用者传递进来 3. 组件之间需要互相挂载, 所以提供两个方法 appendTo 和 appendChild. 考虑到大多数组件都是被挂载到 Component 上, 提供一个 render 方法供重写 4. 很多时候, 组件的逻辑很复杂, 不同状态下展示不同的 div,这时候需要有状态去标识, 所以我们可以封装一个 states 数组到 components 中 5. Component 要暴露出去 div 的事件供子类设置, 封装 addEventListener, removeEventListener, triggerEvent 方法, 子类可重写
4.3.2 基础组件的实现
基础组件包括 TabContainer, ScrollContainer 组件等, 带有业务特性, 需继承 Component 并按组件要求重写某些方法, 举例
1. TabContainer 组件会重写 render 实现 tab 头的切换效果.
2. ScrollContainer 组件的最外层 div 要设置为可滚动, 一种方法是每次使用 ScrollContainer 时传入 style 属性, 但对 ScrollContainer 来说这种属性是固定的, 应该封装到 ScrollContainer 更合适, 所以我采用重写 init 方法,里边(先调用super.init()
)
4.3.3 业务组件的实现 业务组件继承 Component, 可以使用基础组件, 一般是用于通用业务模块的抽取, 比如商品模块,ui 和交互都一样,就抽取出来, 商品信息作为属性传递进来, 根据需求去展示商品信息, 内部交互自己处理,有的交互需要通知父组件时通过事件向外传递
4.3.4 页面 页面由各个业务组件+基础组件+业务逻辑封装, 页面一方面负责取服务端数据, 并把部分数据传递到业务组件中, 另一方面需要处理子组件之间的交互
4.3.5 过程中的点
1. 应该会有不少人在子类中直接调用 component 创建的 div, 这种是否合理呢? 我的想法是不太合理, div 应该由 component 自己维护, 但是开发的过程中发现有的子类真的需要调用,比如 scrollContainer 需要知道 div 的高度, 所以在 Component 中提供了一个 get 方法供子类调用, 纠结… 其实也可以考虑不能被子类调用, scrollContainer 自己定义一个最大的 div, 挂载到 component 的 div 上,然后 scrollContainer 取自己的 div 高度
2. 如果有埋点的需求, 比如大部分组件的 mounted 后都需要发送后端埋点, 可以考虑封装到 component 的 mounted 中, 提供一个 attribute 作为是否发送埋点的开关, 控制 mounted 里要不要加载
3. 子类组件封装自己能提供的事件, 对于使用者来说只需要知道事件的名称, 然后把事件函数传入. 比如 scrollContainer 在滚动条触底时要对外抛出一个事件, 不具备通用性, 需封装到 scrollContainer 中, 所以它需要提供一个 scrollEnd 事件. 一开始我直接调用this.container.addEventListener(‘scroll’)
去做的, 但是思考了一下觉得应该通过重写 addEventListener 方法去实现比较合理
4.4 组件化的工具 组件化的过程中, 会发现很多问题, 比如:
4.5 保证项目可维护性的其他措施
这是个计划, 我还没做完 4.3.2, 没有结果, 中间肯定还有很多坑…. , 这是个漫长的过程, 但是会有完成的那天的, 毕竟时间如白驹过隙, 岁月又曾绕过谁呢
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.