本章主要讲解,一个好的框架在构建的时候,需要考虑到的要素,包含报错信息反馈、警告信息反馈、减少打包体积、良好的输出、特性开关(兼容)等
1、提升用户开发体验
提升用户开发体验主要体现在用户使用框架进行开发时,框架能够提供良好的交互体验,如及时的警告。这在Vue.js中做的很好,在源码中可以看到各个部分都有很多的warn函数的调用,并且在warn函数中尽可能提供了有用信息。
并且Vue.js支持使用自定义的打印数据,比如当打印reacitve或者ref时打印出的信息并不直观。打印const value = ref(0)
中value的值时,打印结果为:
为了解决这一问题,Vue.js可以通过DevTools更改其Console选项设定合适的输出结果。
2、控制框架代码的体积
在保证功能完善的情况下肯定是使用的代码越少越好,但是根据上述情况为了提升开发者体验加入了很多警告信息,这些信息会让代码体积增多。
其实做过项目开发者都知道,在项目中分为开发者模式和生产模式。根据不同的模式削减代码以控制代码体积,比如警告信息只会出现在开发者模式之下。所以在Vue.js的源码中所有的warn信息都加入了控制的字段。
if(__DEV__ && !res) {
warn(
// 警告信息
)
}
这里的__DEV__
只有在开发模式之下会被替换成字面量true
,所以在非开发模式下上面的警告部分的代码就会变成dead code,在最后打包输出的时候被移除。
这样就做到了友好的开发,并且在生成环境下不会产生冗余代码。
3、框架做到良好的Tree-Shaking
Tree-Shaking就是将项目中永远不会执行的代码打包时删去,如上所说的警告信息在生产环境下属于dead code在打包的时候需要被去除,那么怎么区分代码是否会被执行呢。
要使用Tree-Shaking那么模块必须是ESM(ES Module),因为Tree-Shaking依赖ESM的静态结构。对于ESM来说模块是静态引入的,可以不依靠运行程序进行分析,即所谓的静态分析。
在打包时根据入口文件无法到达的代码会被舍弃。同时如果一个函数有副作用那么这个函数也是不会被删去的,比如:
const data = {value: 1}
let getCount = 0
// 副作用函数,计数value被获取的次数,修改全局遍历getCount
function countAdd(target, key) {
getCount += 1
}
// 对data对象进行代理
const obj = new Proxy(data, {
get(target, key) {
// 在获取value的时候执行副作用函数
countAdd(target, key)
return target[key]
}
})
console.log(obj.value)
此时函数countAdd
在执行时会改变变量getCount
的值,即这个函数产生了副作用,所以这个函数是不属于dead code。
函数产生副作用其实是相对于纯函数来说的,纯函数的定义需同时满足以下两条:
- 函数在输入相同的情况下总可以得到相同的输出,即该函数的运行并不会依赖于任何外部的数据。
- 函数在运行过程中并不会导致外部数据变化,即不会产生副作用。
其实上述函数产生副作用的就是对是不是纯函数的判定,只要满足以上两个条件即为纯函数。在Vue框架内可以在函数的前面加注释/*# __PURE__*/
来对其进行Tree-Shaking。这在Vue.js3源码中可以看到很多这样的函数注释。
4、特性开关
在Vue的框架设计中会设计很多的特性(功能),同时在框架迭代的过程中也会产生很多的遗留API,这些功能在一个项目中可能不会完全用到,或者随着框架越来越臃肿打包体积会越来越大。所以一个良好的框架肯定会对相应的功能设置特性开关,将不需要的功能关掉,在打包的时候忽略掉,减少打包的体积。
特性开关的功能实现也和上文提到的__DEV__
常量一样,给这些功能设定一个一个的常量,通过给常量赋值来决定功能是否开启。
比如在Vue2中我们使用的是选项型API,其data、methods、computed…是区分开来的:
export default {
data() {}, //data选项
computed() {}, //computed选项
// 其他选项
}
而在Vue3中使用的是组合型API,其代码结构可以根据功能划分,如:
export default {
setup() {
const count = ref(1)
const doubleCount = computed(() => count.value * 2)
// 其他功能代码
}
}
在Vue3中其实我们也是可以使用选项型API的,根据__VUE_OPTIONS_API__
开关来开闭该特性,这是理解特性开关一个很好的例子。
其实Vue3选择使用组合型API的好处很多,大家最常说的是后期维护和大页面编写更加的友善了,因为之前的选项型API需要把数据放一部分,函数放一部分…根据类型来做区分,在后面需要对相应功能修改的时候需要滑上滑下地寻找,而选项型API可以把相应的功能代码放在一块,代码定位更加简单。
其实组合型API的改变更多的是一种思维上的变化,从关注数据到关注功能,这样以功能为核心可以更加的灵活,对于相同功能的代码复用,嵌套等更加轻松灵活。
5、报错处理
无论在什么框架中报错处理都是一个十分重要的环节,框架错误处理机制决定了用户程序的健壮性,同时好的报错处理机制也减少了开发者的心智负担。
假设我们有一个函数,接收一个回调函数,然后执行该回调函数,简单可以这样写:
function foo(fn) {
fn && fn()
}
此时如果用户提供的回调函数出错怎么办?其实可以使用try…catch捕获
function foo(fn) {
try {
fn && fn()
} catch(e) {
console.log(e)
}
}
但是有个问题,比如我们现在有很多个工具函数,都是接受回调函数,然后执行,那么全部使用try…catch是十分臃肿的,那么进一步可以封装出一个报错的函数。
// 统一报错函数
function callWithEroorHandling(fn) {
try {
fn && fn()
} catch(e) {
console.log(e)
}
}
//这里直接调用
function foo(fn) {
callWithEroorHandling(fn)
}
function bar(fn) {
callWithEroorHandling(fn)
}
将报错处理部分单独提出来,统一处理:
let handleError = null
function registerErrorHandle(fn) {
handleError = fn
}
function callWithEroorHandling(fn) {
// 执行程序
try {
fn && fn()
} catch(e) {
// 报错后给注册的报错处理函数
handleError(e)
}
}
// 注册报错之后的处理
registerErrorHandle((e) => {
console.log("注册报错!")
console.log(e)
})