共计 3998 个字符,预计需要花费 10 分钟才能阅读完成。
对于指令(directive)
属性绑定、事件绑定和 v-modal
底层都是通过指令 (directive
) 实现的,那么什么是指令呢?咱们一起看看 Directive
的定义吧。
// 文件 ./src/directives/index.ts
export interface Directive<T = Element> {(ctx: DirectiveContext<T>): (() => void) | void
}
指令 (directive
) 其实就是一个承受参数类型为 DirectiveContext
并且返回 cleanup
函数或啥都不返回的函数。那么 DirectiveContext
有是如何的呢?
// 文件 ./src/directives/index.ts
export interface DirectiveContext<T = Element> {
el: T
get: (exp?: string) => any // 获取表达式字符串运算后的后果
effect: typeof rawEffect // 用于增加副作用函数
exp: string // 表达式字符串
arg?: string // v-bind:value 或:value 中的 value, v-on:click 或 @click 中的 click
modifiers?: Record<string, true> // @click.prevent 中的 prevent
ctx: Context
}
深刻 v-bind
的工作原理
walk
办法在解析模板时会遍历元素的个性汇合 el.attributes
,当属性名称name
匹配 v-bind
或:
时,则调用 processDirective(el, 'v-bind', value, ctx)
对属性名称进行解决并转发到对应的指令函数并执行。
// 文件 ./src/walk.ts
// 为便于浏览,我将与 v -bind 无关的代码都删除了
const processDirective = (
el: Element,
raw, string, // 属性名称
exp: string, // 属性值:表达式字符串
ctx: Context
) => {
let dir: Directive
let arg: string | undefined
let modifiers: Record<string, true> | undefined // v-bind 有且仅有一个 modifier,那就是 camel
if (raw[0] == ':') {
dir = bind
arg = raw.slice(1)
}
else {const argIndex = raw.indexOf(':')
// 因为指令必须以 `v-` 结尾,因而 dirName 则是从第 3 个字符开始截取
const dirName = argIndex > 0 ? raw.slice(2, argIndex) : raw.slice(2)
// 优先获取内置指令,若查找失败则查找以后上下文的指令
dir = builtInDirectives[dirName] || ctx.dirs[dirName]
arg = argIndex > 0 ? raw.slice(argIndex) : undefined
}
if (dir) {
// 因为 ref 不是用于设置元素的属性,因而须要非凡解决
if (dir === bind && arg === 'ref') dir = ref
applyDirective(el, dir, exp, ctx, arg, modifiers)
}
}
当 processDirective
依据属性名称匹配相应的指令和抽取入参后,就会调用 applyDirective
来通过对应的指令执行操作。
// 文件 ./src/walk.ts
const applyDirective = (
el: Node,
dir: Directive<any>,
exp: string,
ctx: Context,
arg?: string
modifiers?: Record<string, true>
) => {const get = (e = exp) => evaluate(ctx.scope, e, el)
// 指令执行后可能会返回 cleanup 函数用于执行资源开释操作,或什么都不返回
const cleanup = dir({
el,
get,
effect: ctx.effect,
ctx,
exp,
arg,
modifiers
})
if (cleanup) {
// 将 cleanup 函数增加到以后上下文,当上下文销毁时会执行指令的清理工作
ctx.cleanups.push(cleanup)
}
}
当初咱们终于走到指令 bind
执行阶段了
// 文件 ./src/directives/bind.ts
// 只能通过个性的形式赋值的属性
const forceAttrRE = /^(spellcheck|draggable|form|list|type)$/
export const bind: Directive<Element & {_class?: string}> => ({
el,
get,
effect,
arg,
modifiers
}) => {
let prevValue: any
if (arg === 'class') {el._class = el.className}
effect(() => {let value = get()
if (arg) {// 用于解决 v -bind:style="{color:'#fff'}" 的状况
if (modifiers?.camel) {arg = camelize(arg)
}
setProp(el, arg, value, prevValue)
}
else {// 用于解决 v -bind="{style:{color:'#fff'}, fontSize:'10px'}" 的状况
for (const key in value) {setProp(el, key, value[key], prevValue && prevValue[key])
}
// 删除原视图存在,而以后渲染的新视图不存在的属性
for (const key in prevValue) {if (!value || !(key in value)) {setProp(el, key, null)
}
}
}
prevValue = value
})
}
const setProp = (el: Element & {_class?: string},
key: string,
value: any,
prevValue?: any
) => {if (key === 'class') {
el.setAttribute(
'class',
normalizeClass(el._class ? [el._class, value] : value) || ''
)
}
else if (key === 'style') {value = normalizeStyle(value)
const {style} = el as HTMLElement
if (!value) {
// 若 `:style=""` 则移除属性 style
el.removeAttribute('style')
}
else if (isString(value)) {if (value !== prevValue) style.cssText = value
}
else {
// value 为对象的场景
for (const key in value) {setStyle(style, key, value[key])
}
// 删除原视图存在,而以后渲染的新视图不存在的款式属性
if (prevValue && !isString(prevValue)) {for (const key in prevValue) {if (value[key] == null) {setStyle(style, key, '')
}
}
}
}
}
else if (!(el instanceof SVGElement) &&
key in el &&
!forceAttrRE.test(key)) {// 设置 DOM 属性(属性类型能够是对象)
el[key] = value
// 留给 `v-modal` 应用的
if (key === 'value') {el._value = value}
} else {// 设置 DOM 个性(个性值仅能为字符串类型)
/* 因为 `<input v-modal type="checkbox">` 元素的属性 `value` 仅能存储字符串,* 通过 `:true-value` 和 `:false-value` 设置选中和未选中时对应的非字符串类型的值。*/
if (key === 'true-value') {;(el as any)._trueValue = value
}
else if (key === 'false-value') {;(el as any)._falseValue = value
}
else if (value != null) {el.setAttribute(key, value)
}
else {el.removeAttribute(key)
}
}
}
const importantRE = /\s*!important/
const setStyle = (
style: CSSStyleDeclaration,
name: string,
val: string | string[]) => {if (isArray(val)) {val.forEach(v => setStyle(style, name, v))
}
else {if (name.startsWith('--')) {
// 自定义属性
style.setProperty(name, val)
}
else {if (importantRE.test(val)) {
// 带 `!important` 的属性
style.setProperty(hyphenate(name),
val.replace(importantRE, ''),'important'
)
}
else {
// 一般属性
style[name as any] = val
}
}
}
}
总结
通过本文咱们当前不单能够应用 v-bind:style
绑定繁多属性,还用通过 v-bind
一次过绑定多个属性,尽管如同不太倡议这样做 >_<
后续咱们会深刻了解 v-on
事件绑定的工作原理,敬请期待。
正文完
发表至: javascript
2022-03-08