本文提供一个现代前端框架的学习与理解思路,从 组件封装层次 切入,加快从看文档到看懂文档到实际上手的过程。

写在前面

本思路主要应用于以状态管理为核心的前端框架,包括但不限于 react, vue等 web 框架 以及 flutter, swift框架 等非 web 框架

对于框架对应编程语言的学习暂时不在讨论范围内,不过进行多讨论

标题如有雷同纯属巧合(🐶,内容为个人拙见轻喷

封装 is all you need

笔者认为,抛开编程语言细节,对于一个新前端框架的学习思路不外乎以下两步:

  1. 三个层次组件的封装方法
  2. 三个层次组件相互嵌套的方法

一般的前端项目都存在类似“组件树”的概念,不同的组件在嵌套的父子关系中构建起整个应用,具体思路见下文

以老土的盖房子做比喻,封装组件就如构造盖房子的建筑材料,不同层次的封装就如水泥钢筋等不同材料,而组件的嵌套是材料使用的流程

什么是封装?

在正式介绍学习思路之前,首先要先总结下前端开发(非限定前端框架)中的“组件封装”

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体

wiki 百科

在开发过程中,个人认为“封装”的意义关键在于“复用”,是面向对象编程思想的一种表层体现。

换而言之,如果一个项目的开发需求中几乎不存在可复用的“部分”,那么可以完全抛弃“封装”的概念,采用面向过程编程的思路
但如果同一个“部分”不断出现在项目中,那我们应该考虑对他进行抽象其封装来减少维护难度,提高代码鲁棒性。

具体到前端开发中的封装组件,往往是 UI 和交互逻辑的抽象结合体,典型的例子有如 material 提供的 List 组件:

  1. 提供了统一的样式渲染模版
  2. 透明化内部交互逻辑以及样式渲染
  3. 具有一定的样式或逻辑自定义 API

当然,简单功能的纯 UI 组件封装也是十分常见,典型的例子有如 ant design 提供的 Typography 组件

而纯逻辑封装对于开源资源则较为少见,一是部分前端框架并不提供完全剥离 UI 而只抽象逻辑的封装能力(说的就是你,vue2);二是单纯的逻辑封装往往意味着与业务逻辑的强耦合关系,很难跨项目通用;三是对于纯逻辑组件意味着更复杂的项目抽象层次与更高的心智成本,大多数时候懒是第一阻力(🐶

对于纯逻辑封装的典型例子,我想在 react hook 的抽象组件封装中单独探讨

需要注意的是,这里提到的 UI 与 逻辑的抽象封装仅仅只是对封装概念的举例,并不是“封装层次”的总结。笔者想表达的封装层次区分在下一节中介绍

封装的三个层次

学习思路的第一步,了解待学习前端框架三个层次组件的封装方法。下面将结合具体框架中的封装例子从“低层次”到“高层次”分别介绍三种封装层次
以下代码举例将主要为 typescript 版本的 react 与 flutter 框架中的代码

三个层次的名称系笔者自己 yy,可能与专业术语撞车,可能不够贴切,还请见谅

静态组件

定义

无参数且本身在生命周期中不会也不能变化的组件

作为最低层次的封装,静态组件的概念较为简单。从编程语言角度,其实常量本身也是一种静态组件。
这样的封装看似意义不大,但是回到上文对于封装必要性的描述,静态组件可以指代一个全局使用的“颜色”,一个全局默认“参数”或是一个重复使用的“样式”等等

举例

在大多数前端框架的大多数使用场景中,静态组件的声明和使用与变量无异,在这里就不过多赘述了。

react
const primaryColor: string = "#13acd9" const breakpoint = "1000px" const standardSpace = ()=><div style={{width:"2rem",height:"1rem"}}/>
flutter
Color primaryBlue = new Color(0xFF13ACD9); Color primaryRed = new Color(0xFFFF8585); Color primaryGreen = new Color(0xFF27AE60); Divider standardDivider = new Divider( height: 2, color: Color(0x15000000), );

函数组件

定义

有参数且本身在生命周期中不会也不能变化的组件

在代码层次上,相比静态组件,函数组件本身不再是完全不变的,在实例化时可以接受参数。这给予了函数组件更多的自定义空间
但是需要注意的是,顾名思义,函数组件在实例化之后便无法发生变化(如函数执行完成后便退出执行栈,再次执行已经是另一个实例了)

举例

在笔者看来,函数组件其实是在大多数前端工程中使用最多的,但是很容易用而不自知,因为函数组件的心智成本很低(随便就写了,随便就用了)
对于函数组件的深层讨论,我们会在下文介绍状态组件后进行

react
// 一个可以接受参数的占位盒子 const SizeBox = ({ height = 0, width = 0 }) => { return <div style={{ height: height, width: width }}></div> } // 一个上传流程中抽象的封装逻辑 const getUploadUrl = async (fileName: string, api: AxiosInstance) => { const res = await api.get('/static/upload', { params: { fileName } }) if (!res.data.success) { message.error(res.data.reason) return null } // console.log('geturl', res); return res.data.result?.url }

flutter
// 压缩文件对象的抽象逻辑封装 Future<File> compressFile(File file) async { Directory dir = await getTemporaryDirectory(); String fileType = file.absolute.path.split('.').last; File result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, dir.absolute.path + "/" +DateTime.now().millisecondsSinceEpoch.toString() +"." + fileType, quality: 88, ); return result; } // 可以定义高度的分割线 Divider standardDivider(double height,Color color=Color(0x15000000)){ return new Divider( height: height, color: color, ); }

状态组件

定义

有参数(或隐式的状态初值传参)且本身在生命周期中能持有变量(可变化)的组件

状态组件从编程思想来看,是“面向对象编程”思想的体现;从前端框架的角度来说,笔者觉得完整的状态管理流程链是区分一个前端框架“现代”与否的关键性标准。

举例