一篇文章告訴你Vue3指令是如何實(shí)現(xiàn)的
前言
Vue 指令 是指 對普通DOM元素進(jìn)行底層操作的JS對象, 它們會被掛在Element VNode對象上,在Element VNode的一些生命周期中會被調(diào)用,從而可以操作Element VNode的底層DOM元素。
指令注冊
指令注冊 是指將指令對應(yīng)的JS代碼放置在某些地方,需要使用的時(shí)候可以在這些地方進(jìn)行查找。
全局注冊
- 全局注冊是調(diào)用app.directive('指令名稱', { 指令代碼 }) 來實(shí)現(xiàn)的
app.directive('pin', (el, binding) => {
el.style.position = 'fixed'
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
})- 全局注冊的邏輯是將 指令名稱 和 對應(yīng)的指令代碼 掛載在全局的context的directives對象上
<!-- apiCreateApp.js -->
directive(name: string, directive?: Directive) {
// 掛載在全局的`context`的`directives`對象上
context.directives[name] = directive
return app
},組件內(nèi)注冊
- 組件內(nèi)注冊是在組件內(nèi)添加 directives 的選項(xiàng)
directives: {
pin: (el, binding) => {
el.style.position = 'fixed'
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
}
}- 組件注冊的邏輯是將 指令名稱 和 對應(yīng)的指令代碼 掛載在組件實(shí)例對象的directives上
<!-- component.ts -->
export function applyOptions(instance: ComponentInternalInstance) {
// 掛載在組件實(shí)例對象的`directives`上
instance.directives = directives
}指令搜尋
指令搜尋的時(shí)機(jī)
開發(fā)者是在模板中使用指令,所以應(yīng)該是在模板渲染的時(shí)候需要先搜尋到對應(yīng)的指令。
不使用指令的模板<h4>指令演示</h4>渲染函數(shù)如下:
function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("h4", null, "指令演示"))
}
}使用指令的模板<h4 v-pin:[right]="20">指令演示</h4>渲染函數(shù)如下:
function render(_ctx, _cache) {
with (_ctx) {
const { createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
const _directive_pin = _resolveDirective("pin")
return _withDirectives((_openBlock(), _createElementBlock("h4", null, _hoisted_2, 512 /* NEED_PATCH */)), [
[_directive_pin, pinPadding, direction]
])
}
}
使用指令的模板需要先搜尋對應(yīng)的指令,然后綁定指令到VNode
指令搜尋的邏輯
- 指令搜尋的邏輯是先從組件實(shí)例instance的directives上尋找,如果沒找到再在appContext的directives上尋找
export function resolveDirective(name: string): Directive | undefined {
return resolveAsset(DIRECTIVES, name)
}
function resolveAsset(
type: AssetTypes,
name: string,
warnMissing = true,
maybeSelfReference = false
) {
const res =
// local registration
// check instance[type] first which is resolved for options API
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
// global registration
resolve(instance.appContext[type], name)
return res
}指令綁定VNode
export function withDirectives<T extends VNode>(
vnode: T,
directives: DirectiveArguments
): T {
const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
if (isFunction(dir)) {
dir = {
mounted: dir,
updated: dir
} as ObjectDirective
}
bindings.push({
dir,
instance,
value,
oldValue: void 0,
arg,
modifiers
})
}
return vnode
}將每個(gè)指令dir和其他一些參數(shù) 掛載在 VNode的dirs上。 其他參數(shù)是: instance組件實(shí)例, value指令的新值(本例為20),oldValue指令的舊值(本例為0),arg指令的參數(shù)(本例為right)
指令調(diào)用
指令調(diào)用是指 指令的代碼什么時(shí)候被執(zhí)行的? 我們最開始提到指令是對普通DOM元素進(jìn)行底層操作的JS對象,所以指令的邏輯應(yīng)該是在 Element VNode中進(jìn)行處理的。
const mountElement = (
vnode: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
// 1
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}
// 2
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
}
// 3
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
needCallTransitionHooks && transition!.enter(el)
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense)
}const patchElement = (
n1: VNode,
n2: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
const el = (n2.el = n1.el!)
// 1
if (dirs) {
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
}
// 2
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
}, parentSuspense)
}const unmount: UnmountFn = (
vnode,
parentComponent,
parentSuspense,
doRemove = false,
optimized = false
) => {
const {
type,
props,
ref,
children,
dynamicChildren,
shapeFlag,
patchFlag,
dirs
} = vnode
// unset ref
if (ref != null) {
setRef(ref, null, parentSuspense, vnode, true)
}
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
return
}
const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
let vnodeHook: VNodeHook | undefined | null
if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
invokeVNodeHook(vnodeHook, parentComponent, vnode)
}
if (shapeFlag & ShapeFlags.COMPONENT) {
unmountComponent(vnode.component!, parentSuspense, doRemove)
} else {
if (shouldInvokeDirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
}
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
shouldInvokeDirs &&
invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
}, parentSuspense)
}在掛載元素VNode的時(shí)候,會調(diào)用指令的created, beforeMount和mounted鉤子函數(shù);
在更新元素VNode的時(shí)候,會調(diào)用指令的beforeUpdate, updated鉤子函數(shù);
在卸載元素VNode的時(shí)候,會調(diào)用指令的beforeUnmount, unmounted鉤子函數(shù);
關(guān)于指令的思考
組件上使用指令
我們上面提到了指令是作用在元素VNode上的,那組件使用指令(例如<son v-pin:[right]="20"></son>)是什么效果呢?結(jié)果是組件上使用的指令會作用在組件內(nèi)部的根節(jié)點(diǎn)的元素VNode上。
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const {
type: Component,
vnode,
proxy,
withProxy,
props,
propsOptions: [propsOptions],
slots,
attrs,
emit,
render,
renderCache,
data,
setupState,
ctx,
inheritAttrs
} = instance
// inherit directives
if (vnode.dirs) {
if (__DEV__ && !isElementRoot(root)) {
warn(
`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`
)
}
root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
}
}在組件渲染子樹VNode的根VNode時(shí)候,會將組件的指令dirs添加在根元素VNode的dirs中。所以作用于組件的指令 等同于 作用于 根節(jié)點(diǎn)的元素VNode上。
組件上的一些使用場景
我覺得一些比較使用的指令的使用場景有:
- v-lazyload: 圖片的懶加載
- v-loading:實(shí)現(xiàn)加一個(gè)加載動畫
- v-permission: 權(quán)限控制,沒有權(quán)限就隱藏DOM元素
- v-debounce: 輸入防抖,特別是搜素框請求的輸入
總結(jié)
到此這篇關(guān)于Vue3指令是如何實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)Vue3指令實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue2.0實(shí)現(xiàn)的tab標(biāo)簽切換效果(內(nèi)容可自定義)示例
這篇文章主要介紹了vue2.0實(shí)現(xiàn)的tab標(biāo)簽切換效果,結(jié)合實(shí)例形式分析了vue.js實(shí)現(xiàn)內(nèi)容可自定義的tab點(diǎn)擊切換功能相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
vue3基礎(chǔ)組件開發(fā)detePicker日期選擇組件示例
這篇文章主要為大家介紹了vue3基礎(chǔ)組件開發(fā)-detePicker(日期選擇組件)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
vue項(xiàng)目本地開發(fā)完成后部署到服務(wù)器后報(bào)404錯(cuò)誤解決方案
很多時(shí)候我們發(fā)現(xiàn)辛辛苦苦寫的VueJs應(yīng)用經(jīng)過打包后在自己本地搭建的服務(wù)器上測試沒有什么問題,但真正放在服務(wù)器上后會發(fā)現(xiàn)或多或少的問題,這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目本地開發(fā)完成后部署到服務(wù)器后報(bào)404錯(cuò)誤的解決方案,需要的朋友可以參考下2024-01-01
前端VUE雙語實(shí)現(xiàn)方案詳細(xì)教程
在項(xiàng)目需求中我們會遇到國際化的中英文切換,這篇文章主要給大家介紹了關(guān)于前端VUE雙語實(shí)現(xiàn)方案的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
vue組件表單數(shù)據(jù)回顯驗(yàn)證及提交的實(shí)例代碼
今天小編就為大家分享一篇vue組件表單數(shù)據(jù)回顯驗(yàn)證及提交的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
echarts設(shè)置tootip輪播切換展示(vue3搭配vue-echarts粘貼即用)
這篇文章主要為大家介紹了echarts設(shè)置tootip輪播切換展示效果,vue3搭配vue-echarts粘貼即用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2023-10-10
Vue3中當(dāng)v-if和v-for同時(shí)使用時(shí)產(chǎn)生的問題和解決辦法
封裝一個(gè)組件時(shí),我使用到了v-for和v-if,它們在同一標(biāo)簽內(nèi),總是提示v-for循環(huán)出來的item在實(shí)例中沒有被定義,查詢資料后原因是因?yàn)関-for和v-if在同級使用時(shí),v-if優(yōu)先級比v-for高,所以本文給大家介紹了Vue3中當(dāng)v-if和v-for同時(shí)使用時(shí)產(chǎn)生的問題和解決辦法2024-07-07

