阅读源码最简单也是最容易上手的部分就是先阅读该库的工具函数,这篇文章会挑选 Vue3 和 Element Plus 源码里面的几个工具函数,带大家走出阅读源码的第一步。
一、类型判断函数
这部分的函数 是 Vue/shared 里的,Element 也用直接使用到。
1. isUndefined
// 判断值是不是 undefined
const isUndefined = (val: any): val is undefined => val === undefined
比typeof操作更精准的 undefined 检测,避免 undefined 被重写的情况。用于检测变量是否为原始 undefined 类型。
is是TS的一个类型谓词(TypePredicate),在这个位置的意思是当函数isUndefined返回true时,TypeScript编译器会认为在后续相关代码块中,val的类型就是undefined;当函数返回false时,val就不是undefined类型。
2. isBoolean
// 判断值是不是 boolean
const isBoolean = (val: any): val is boolean => typeof val === 'boolean'
高效判断 Boolean 类型,包括原始 true/false 和 Boolean 对象。
3. isFunction
// 判断值是不是 function
const isFunction = (val: any): val is Function => typeof val === 'function'
// 判断值是不是 object
export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'
完美区分函数与对象,支持 async functions 和 generator functions 的判断。
因为 null 在 JS 里 typeof 后也是 ‘object’,所以先一步排除后再判断。
4. hasOwn
cconst hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
val: object,
key: string | symbol,
): key is keyof typeof val => hasOwnProperty.call(val, key)
// 使用示例:
// 定义 hasOwn 函数,使用类型谓词
const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>
Object.prototype.hasOwnProperty.call(val, key);
// 示例对象
const myObj = {
prop1: 'value1',
prop2: 'value2'
};
let myKey: string | symbol = 'prop1';
// 使用 hasOwn 函数进行类型判断
if (hasOwn(myObj, myKey)) {
// 因为 hasOwn 返回 true,TypeScript 知道 myKey 现在是 myObj 对象的有效键类型
// 所以可以安全地访问 myObj[myKey]
console.log(myObj[myKey]);
}
hasOwn 函数接收一个对象 val 和一个键 key 作为参数
使用 Object.prototype.hasOwnProperty 方法来判断该对象是否直接拥有指定的键对应的属性
key is keyof typeof val 表示如果函数返回 true,那么 key 的类型将被缩小为 val 对象的键的类型。也就是说,在后续代码中,当 hasOwn(val, key) 返回 true 时,TypeScript 编译器会知道 key 是 val 对象自身拥有的一个有效键。
5. isArray
export const isArray: typeof Array.isArray = Array.isArray
这里 Vue 判断数组是直接使用了 isArray。typeof 的意思是将 isArray 的类型指定为 typeof Array.isArray,也就是让 isArray 具有和 Array.isArray 相同的函数类型签名。确保 isArray 函数的使用方式和 Array.isArray 一致,遵循相同的参数和返回值规则。
6. isMap、isSet、isDate、isRegExp
// 获取 toString 方法
export const objectToString: typeof Object.prototype.toString =
Object.prototype.toString
export const toTypeString = (value: unknown): string =>
objectToString.call(value)
// 判断是不是 Map 类型
export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]'
// 判断是不是 Set 类型
export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'
// 判断是不是 RegExp 类型
export const isRegExp = (val: unknown): val is RegExp =>
toTypeString(val) === '[object RegExp]'
同样,typeof 保证了类型的一致,防止 toString 函数被重写,提高了代码的类型安全性和可维护性。
7. isPromise
// 判断值是不是 Promise
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return (
(isObject(val) || isFunction(val)) &&
isFunction((val as any).then) &&
isFunction((val as any).catch)
)
}
该函数先设置了一个泛型 T ,并且函数返回值为 true 时 val 的值为 Promise,并且类型为 any。
然后用到了上面的两个判断函数,并且连同 val 的 then 和 catch 也是函数。
as 是类型断言,作用是将 val 临时转换为 any 类型。
8. 样式操作
下面是 Element Plus 直接书写的函数
export function addClass(el: HTMLElement, cls: string) {
if (!cls || !cls.trim()) return
const classes = cls.split(' ')
classes.forEach(c => el.classList.add(c))
}
// 添加类名到目标元素
- 支持空格分隔的多类名同时添加
- 自动过滤空类名
- 比直接操作 className 更高效
9. raf(动画优化)
关于 requestAnimationFrame 的具体作用在这里就先不赘述,可以先理解为是一个可以让页面更加平滑流畅的 API,可以通过它确保动画的每一帧在浏览器重绘前更新,避免掉帧。
import { isClient, isIOS } from '@vueuse/core'
export const rAF = (fn: () => void) =>
isClient
? window.requestAnimationFrame(fn)
: (setTimeout(fn, 16) as unknown as number)
export const cAF = (handle: number) =>
isClient ? window.cancelAnimationFrame(handle) : clearTimeout(handle)
-
isClient是一个只读的布尔型常量。它用于判断当前代码是否在客户端(浏览器)环境中运行。在 Vue 开发中,有时需要根据代码运行的环境(客户端或服务器端,如在使用 Vue SSR 时)来执行不同的逻辑,isClient就可以帮助我们实现这种判断。 -
isIOS同样是一个只读的布尔型常量,用于判断当前运行环境是否为 iOS 设备(如 iPhone、iPad 等)。它通过检测用户代理字符串(navigator.userAgent)来确定设备类型。在开发移动端应用时,可能需要根据不同的设备系统来提供不同的交互体验或功能支持,isIOS就可以用于这种场景下的条件判断。如果是浏览器环境,就使用 requestAnimationFrame API 来优化动画。如果不是就使用 setTimeout 模拟,浏览器更新动画的频率是1/60秒,约等于 16毫秒,这样能模拟浏览器的更新间隔。
1. 动画性能优化
-
组件示例 :
<el-scrollbar>(自定义滚动条)、<el-collapse-transition>(折叠动画)、Tooltip(提示框动画)等。// 伪代码:滚动条平滑滚动 function smoothScroll() { let step = () => { if (未到达目标位置) { 更新位置(); rAF(step); } }; raf(step); }
总结
虽然样本不多,但是已经可以看到,Element Plus 以及 Vue 对函数的边界处理以及类型约束都是比较严格的。这样做可以提高代码的可读性、稳定性。函数的抽离也大大减少了项目的冗余代码。像 Element 就大大的使用了 Vue 提供出来的函数。