文章封面来自于深圳湾桥,很漂亮!
本文是阅读源码之后,学习到一些新写法,平常业务开发也可以用起来。在我看来,阅读源码,不但能知道该框架的底层原理,出现bug时,可以快速排查和修复,更重要的是,阅读源码就像向优秀的人学习,掌握我们不曾了解的新知识点,看看别人是如何编写出漂亮的,可复用的代码。
操作符!.
onMounted(() => {
// 组合在一起,!. 就是“强制执行方法,然后再访问它的返回值”。
const items = breadcrumb.value!.querySelectorAll(`.${ns.e('item')}`)
if (items.length) {
items[items.length - 1].setAttribute('aria-current', 'page')
}
})
element-plus: 只有控制到页面上行为的函数就存在于vue文件中,其他逻辑处理函数,都在ts文件中
取值和监听值变化,不使用watch
const checkedValue = computed<CascaderValue>({
get() {
return cloneDeep(props.modelValue) as CascaderValue
},
set(val) {
emit(UPDATE_MODEL_EVENT, val)
emit(CHANGE_EVENT, val)
if (props.validateEvent) {
formItem?.validate('change').catch((err) => debugWarn(err))
}
},
})
方法调用后面加!
// 透传
const carouselContext = inject(carouselContextKey)!
在 JavaScript 中,方法调用后面加上感叹号 ! 通常是用来强制执行(Force execution)这个方法。以数组作为例子:- 使用数组方法时不加感叹号,它只会返回方法的结果,但不会改变数组自身:
let arr = [1, 2, 3];
let newArr = arr.map(x => x * 2);
// newArr [2, 4, 6]
// arr 还是 [1, 2, 3], 数组本身没有变化
而加上感叹号后,会修改数组自身:
let arr = [1, 2, 3];
arr.map!(x => x * 2);
// arr 变为 [2, 4, 6]
// 数组自身被修改了
这是因为,数组的 map 等方法本身是不会修改原数组的,而加上感叹号后则会强制修改数组自身。
其他数组方法同理,比如 filter、forEach、sort 等,不加感叹号只返回结果,加感叹号则修改原数组。
总的来说,感叹号 ! 被用于强制执行某个不会产生副作用的方法,产生副作用(side effect),修改调用者自身。
这一点和 Haskell 中的特写运算符(Bang pattern)! 类似,也用于产生 computations with effects。
使用感叹号需要小心,可能会影响程序的可预测性和数据不变性,但对于需要改变源数据的场景,使用感叹号可以使代码更加简洁。
透传使用symbol
provide(
CASCADER_PANEL_INJECTION_KEY, // 透传下去,使用Symbol作为唯一值
reactive({
config,
expandingNode,
checkedNodes,
isHoverMenu,
initialLoaded,
renderLabelFn,
lazyLoad,
expandNode,
handleCheckChange,
})
)
export const CASCADER_PANEL_INJECTION_KEY: InjectionKey<ElCascaderPanelContext> =
Symbol()
强制执行,会影响最终值
获取组件实例,调用组件自定义事件
const { emit } = getCurrentInstance()!
const model = computed({
get() {
return isGroup.value
? checkboxGroup?.modelValue?.value
: props.modelValue ?? selfModel.value
},
set(val: unknown) {
if (isGroup.value && isArray(val)) {
isLimitExceeded.value =
checkboxGroup?.max?.value !== undefined &&
val.length > checkboxGroup?.max.value
isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
} else {
emit(UPDATE_MODEL_EVENT, val)
selfModel.value = val
}
},
})
-
getCurrentInstance() 函数可以在 setup() 中获取到当前组件实例。
-
但是直接调用 getCurrentInstance() 会返回一个可选型(maybe)的实例,可能为 null。
-
为了确保获取到实例,使用 ! 非空断言运算符,强制转换为非空实例。
-
所以 getCurrentInstance()! 确保了返回的实例不会是 null。
-
然后使用 ES6 解构赋值语法,从实例中取出 emit 方法。
-
emit 方法用于在组件内部触发自定义事件。
-
这种方式避免了代码中出现隐式的 this,使代码更清晰可读。
-
是 Vue 3 Composition API 中常用的一种实例访问模式,用于在 setup() 中获取实例属性和方法。
unref与ref
unref 和 ref 都可以用来获取响应式对象的值,但是有以下几点关键区别:
-
unref 是一个函数,ref 是创建ref对象的方法。unref直接返回值,ref会创建一个响应式的引用对象。
-
unref 获取值,ref 创建值的引用。unref目的是获取值,ref是创建一个值的响应式引用。
-
unref 参数可以是基础值或响应式对象,ref只接受基础值创建引用。unref的参数可以是基础类型的值,也可以是ref或reactive对象,它会返回对象的值或对象本身。ref只接受基础类型的值来创建一个响应式的引用对象。
-
unref 使用场景是在组件逻辑中获取值,ref创建应用于模板的响应式引用。unref常用于组件逻辑中,需要获取响应式对象的原始值时使用。ref更多地在模板中使用,创建一个可以响应式跟踪的引用。
unref直接返回的值,不需要用.value访问
rAF & cAF
requestAnimationFrame 和 cancelAnimationFrame 是浏览器用来实现高性能动画的 API。它可以把每一帧的代码编排到浏览器的一次重绘中,避免频繁的重绘导致性能问题。
这两个函数在频繁重绘调用,可以减轻浏览器负担
比如:定时器
const isClient = typeof window !== "undefined";
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)
(event.target as HTMLElement).closest
const handleMouseDown = (event: MouseEvent) => {
const target = (event.target as HTMLElement).closest('td')
if (!target) return
focusWithClick = true
}
const handleMouseUp = (event: MouseEvent) => {
const target = (event.target as HTMLElement).closest('td')
if (!target) return
focusWithClick = false
}
closest() 方法会沿着 DOM 树向上寻找匹配的选择器的第一个祖先元素,在这里是查找最近的 元素。所以整个代码的作用就是:- 获取点击事件的目标元素
- 将其转化为 HTMLElement 类型
- 在其祖先元素中查找最近的 td元素
- 并返回这个 td 元素这样可以方便地通过点击事件获取到对应的 td 元素进行后续操作。