亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

關(guān)于Vue3中的響應(yīng)式原理

 更新時(shí)間:2022年09月07日 10:47:39   作者:SunsetFeng  
這篇文章主要介紹了關(guān)于Vue3中的響應(yīng)式原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一、簡介

本章內(nèi)容主要通過具體的簡單示例來分析Vue3是如何實(shí)現(xiàn)響應(yīng)式的。理解本章需要了解Vue3的響應(yīng)式對(duì)象。只注重原理設(shè)計(jì)層面,細(xì)節(jié)不做太多講解。

二、響應(yīng)核心

1.核心源碼

export class ReactiveEffect<T = any> {
  //是否激活
  active = true
  //依賴列表
  deps: Dep[] = []
  // can be attached after creation
  computed?: boolean
  //是否允許遞歸
  allowRecurse?: boolean
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope | null
  ) {
  	//將自身添加到一個(gè)全局的EffectScope容器中
    recordEffectScope(this, scope)
  }
  run() {
    if (!this.active) {
      //沒有激活,直接調(diào)用回調(diào)方法
      return this.fn()
    }
    //棧中不存在當(dāng)前對(duì)象
    if (!effectStack.includes(this)) {
      try {
        //推入棧頂,并且置為全局激活對(duì)象
        effectStack.push((activeEffect = this))
        //開啟依賴收集開關(guān)
        enableTracking()
        //位操作符:用于優(yōu)化
        trackOpBit = 1 << ++effectTrackDepth
        //源碼中maxMarkerBits取30 猜測(cè)是因?yàn)檎麛?shù)位運(yùn)算時(shí)是按照32位計(jì)算 當(dāng)1<<31時(shí)為負(fù)值了,后續(xù)負(fù)值的位運(yùn)算得不到預(yù)期結(jié)果 所以取的最大30
        if (effectTrackDepth <= maxMarkerBits) {
          //將當(dāng)前依賴列表的所有依賴置為"已經(jīng)收集"
          initDepMarkers(this)
        } else {
          //不采用優(yōu)化模式,使用老流程,直接移除依賴的全部狀態(tài)
          cleanupEffect(this)
        }
        //調(diào)用回調(diào)
        return this.fn()
      } finally {
        if (effectTrackDepth <= maxMarkerBits) {
          //斷掉依賴關(guān)聯(lián)
          finalizeDepMarkers(this)
        }
        //重置位操作狀態(tài)
        trackOpBit = 1 << --effectTrackDepth
        //重置依賴收集狀態(tài)
        resetTracking()
        //棧頂出棧
        effectStack.pop()
        const n = effectStack.length
        activeEffect = n > 0 ? effectStack[n - 1] : undefined
      }
    }
  }
  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

上述ReactiveEffect對(duì)象,其實(shí)需要關(guān)注的就是一個(gè)run方法,這個(gè)方法設(shè)計(jì)得十分巧妙,所有動(dòng)態(tài)響應(yīng)的本質(zhì)其實(shí)都是通過調(diào)用run方法實(shí)現(xiàn)的

比如如下代碼:

    let dummy
    const counter = reactive({ num: 0 })
    let innerfunc = () => dummy = counter.num;
    effect(innerfunc)
    //下面的賦值,最終會(huì)執(zhí)行innerfunc,所以dummy會(huì)變成7
    counter.num = 7

可能會(huì)有疑惑,上述代碼并沒有出現(xiàn)ReactiveEffect類型的對(duì)象,它其實(shí)是在effect方法中創(chuàng)建的,我們接下來分析下effect方法。

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  //創(chuàng)建對(duì)象并傳參
  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  if (!options || !options.lazy) {
    //執(zhí)行
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

這個(gè)方法的簡單用法很簡單,就是創(chuàng)建一個(gè)ReactiveEffect類型對(duì)象,然后執(zhí)行run方法。

可能對(duì)于recordEffectScope方法有疑惑,其實(shí)這個(gè)方法和響應(yīng)式無關(guān),它的主要作用是將一個(gè)ReactiveEffect對(duì)象放入一個(gè)effectScope容器對(duì)象內(nèi),這個(gè)容器對(duì)象可以方便快捷的對(duì)容器內(nèi)所有的ReactiveEffect對(duì)象和其子effectScope調(diào)用stop方法。只關(guān)注響應(yīng)式的話可以不作考慮。

2.逐步分析上述示例代碼

    let dummy
    //步驟1:創(chuàng)建一個(gè)響應(yīng)式對(duì)象
    const counter = reactive({ num: 0 })
    let innerfunc = () => dummy = counter.num;
    //步驟2:調(diào)用effect方法
    effect(innerfunc)
    //步驟3:修改響應(yīng)式對(duì)象數(shù)據(jù)
    counter.num = 7

上述的測(cè)試代碼看似就3個(gè)步驟,其實(shí)內(nèi)部做的東西非常多,我們來跟蹤下運(yùn)行流程。 

步驟1:這一步很簡單,就是單純的創(chuàng)建一個(gè)Proxy對(duì)象,此時(shí)counter對(duì)象變成響應(yīng)式的。 

步驟2:effect方法里面最終調(diào)用的是run方法,而run方法主要是將自身掛載到全局激活并入棧,此時(shí)調(diào)用回調(diào)方法?;卣{(diào)方法此時(shí)為上面innerfunc方法,調(diào)用這個(gè)方法會(huì)讀取counter.num屬性,讀取響應(yīng)式對(duì)象的屬性會(huì)調(diào)用代理攔截處理的get方法,在get方法里面,會(huì)收集依賴。此時(shí)將依賴存于棧頂?shù)哪莻€(gè)ReactiveEffect對(duì)象的deps屬性中。 

步驟3:當(dāng)響應(yīng)式對(duì)象的屬性修改后,會(huì)觸發(fā)依賴更新,由于觸發(fā)更新的依賴列表里面存在effect方法里面創(chuàng)建的ReactiveEffect對(duì)象,所以會(huì)重新調(diào)用其run方法,在這兒也就會(huì)調(diào)用innerfunc方法。所以dummy屬性就會(huì)跟隨counter.num屬性的變化而變化

備注:上述三步驟中,提及了收集依賴和觸發(fā)依賴更新。接下來我們便看一下是如何收集依賴和觸發(fā)依賴更新的。

3.收集依賴和觸發(fā)依賴更新

(1).收集依賴

export function track(target: object, type: TrackOpTypes, key: unknown) {
  //是否允許收集
  if (!isTracking()) {
    return
  }
  //對(duì)象map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  //依賴map
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }
  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined
  //收集依賴
  trackEffects(dep, eventInfo)
}
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      //本一輪調(diào)用新收集的依賴
      dep.n |= trackOpBit // set newly tracked
      //是否應(yīng)該被收集
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }
  if (shouldTrack) {
    //收集依賴
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

上述代碼是收集依賴的核心代碼??催^我響應(yīng)式對(duì)象文章的話,應(yīng)該會(huì)注意到,在涉及“讀”相關(guān)操作時(shí),就會(huì)調(diào)用track方法來收集依賴。此時(shí)就是調(diào)用的上述track方法。track方法很簡單,主要是找到對(duì)應(yīng)的依賴列表,如果沒有就創(chuàng)建一個(gè)依賴列表。

trackEffects里面先只需要關(guān)注收集依賴的邏輯,可以很明顯的看到,里面就是一個(gè)依賴的雙向添加。至于上面的那些邏輯,最主要的目的是防止重復(fù)添加依賴,我會(huì)在后面的優(yōu)化環(huán)節(jié)詳細(xì)講。

依賴模型存儲(chǔ)模型大致如下:

在這里插入圖片描述

(2).觸發(fā)依賴更新

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  //獲取依賴map
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }
    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }
  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined
  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        //只需要關(guān)注這兒
        effect.run()
      }
    }
  }
}

trigger方法在響應(yīng)式對(duì)象的"寫"操作中調(diào)用,這個(gè)方法整體上只是根據(jù)不同的依賴更新類型,將依賴添加進(jìn)一個(gè)依賴數(shù)組里面,最終通過triggerEffects方法更新這個(gè)依賴數(shù)組里面的依賴。

在triggerEffects方法里面,暫時(shí)只需要關(guān)注effect.run即可,此時(shí)調(diào)用的是ReactiveEffect關(guān)聯(lián)的那個(gè)回調(diào)方法,這時(shí)候也就正確的響應(yīng)式變化了。

至于effect.scheduler,我會(huì)在后續(xù)的計(jì)算屬性篇章中講到,這個(gè)方法是給計(jì)算屬性用的。

三、V3.2的響應(yīng)式優(yōu)化

上述篇幅只講述了一個(gè)整體的響應(yīng)式變化原理,接下來介紹一下V3.2帶來的響應(yīng)式性能優(yōu)化。我們先看一下Dep類型的定義

export type Dep = Set<ReactiveEffect> & TrackedMarkers
/**
 * wasTracked and newTracked maintain the status for several levels of effect
 * tracking recursion. One bit per level is used to define whether the dependency
 * was/is tracked.
 */
type TrackedMarkers = {
  /**
   * wasTracked
   */
  w: number
  /**
   * newTracked
   */
  n: number
}

可以看到,依賴列表不是一個(gè)簡簡單單的Set集合,它還存在2個(gè)用于輔助的屬性。我們創(chuàng)建依賴也是通過createDep方法,實(shí)現(xiàn)如下:

export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}

我在這兒先說明一下這2個(gè)屬性的作用。w屬性用于判斷依賴是否已經(jīng)被收集,n屬性用于判斷依賴在本次調(diào)用中是否用到??赡墁F(xiàn)在還對(duì)此有疑惑,我用以下一個(gè)簡單示例來解釋。

let status = true;
let dummy
const depA = reactive({ num: 0 })
const depB = reactive({ num: 10 })
let innerfunc = () => {
	dummy = depA.num
	if(status){
		dummy += depB.num
		status = false
	}
	console.log(dummy);
}
effect(innerfunc)
depA.num = 7
depB.num = 20
//輸出為 10 7

我們來分析以下上述代碼的流程,首先調(diào)用effect方法,會(huì)執(zhí)行一次關(guān)聯(lián)的innerfunc,此時(shí)讀取了depA和depB的num屬性,所以此時(shí)ReactiveEffect對(duì)象里面的deps屬性存在2個(gè)依賴,并且輸出10。當(dāng)修改depA.num屬性時(shí),會(huì)觸發(fā)run方法,此時(shí)關(guān)注以下代碼:

        if (effectTrackDepth <= maxMarkerBits) {
          //將當(dāng)前依賴列表的所有依賴置為"已經(jīng)收集"
          initDepMarkers(this)
        } else {
          //不采用優(yōu)化模式,使用老流程,直接移除依賴的全部狀態(tài)
          cleanupEffect(this)
        }

因?yàn)檎{(diào)用effect方法時(shí),收集過一次依賴,所以initDepMarkers方法將所有的依賴都標(biāo)記為已經(jīng)收集。在run方法最后,會(huì)調(diào)用innerfunc方法。在innerfunc方法中,這一次調(diào)用中又會(huì)去收集依賴,此時(shí)關(guān)注trackEffects中的以下代碼:

  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      //本一輪調(diào)用新收集的依賴
      dep.n |= trackOpBit // set newly tracked
      //是否應(yīng)該被收集
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }

在run方法中,有一個(gè)同樣的判斷effectTrackDepth <= maxMarkerBits,這個(gè)判斷是用于控制是否優(yōu)化的,后面會(huì)講為什么會(huì)存在這個(gè)判斷以及為什么maxMarkerBits的取值為30。

在這個(gè)收集邏輯中,會(huì)將本次回調(diào)中第一次使用到的依賴置為"新增依賴",我們?cè)诳磇nnerfunc,此時(shí)只會(huì)使用到depA,不會(huì)使用到depB,因此之前存在的關(guān)于depB對(duì)象的依賴在本次調(diào)用中沒有用到。

shouldTrack屬性表示依賴是否應(yīng)該被收集,如果沒有收集,則被收集。此時(shí)innerfunc里面輸出的dummy為7。

接下來關(guān)注run里面的以下代碼:

  if (effectTrackDepth <= maxMarkerBits) {
    //斷掉依賴關(guān)聯(lián)
    finalizeDepMarkers(this)
  }
  //重置位操作狀態(tài)
  trackOpBit = 1 << --effectTrackDepth
  //重置依賴收集狀態(tài)
  resetTracking()
  //棧頂出棧
  effectStack.pop()
  const n = effectStack.length
  activeEffect = n > 0 ? effectStack[n - 1] : undefined

上述代碼存在于run方法里面的finally關(guān)鍵字內(nèi),當(dāng)innerfunc執(zhí)行完后,里面就會(huì)執(zhí)行這里。首先便會(huì)根據(jù)判斷通過finalizeDepMarkers方法去斷掉依賴關(guān)聯(lián)。

我們看以下方法的實(shí)現(xiàn):

export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].w |= trackOpBit // set was tracked
    }
  }
}
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  const { deps } = effect
  if (deps.length) {
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
      const dep = deps[i]
      if (wasTracked(dep) && !newTracked(dep)) {
        dep.delete(effect)
      } else {
        deps[ptr++] = dep
      }
      // clear bits
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    deps.length = ptr
  }
}

這2個(gè)方法巧妙的通過位運(yùn)算將調(diào)用分層。一開始將存在的依賴打上收集標(biāo)簽,如果在本層中沒有使用到,則斷掉依賴關(guān)聯(lián)。當(dāng)設(shè)置depB.num = 20時(shí),首先會(huì)找到依賴列表,由于依賴列表中已經(jīng)不存在ReactiveEffect對(duì)象了,所以不會(huì)觸發(fā)依賴更新,此時(shí)不會(huì)有新的輸出。

這兒是一個(gè)優(yōu)化,斷掉不必要的關(guān)聯(lián)依賴,減少方法的調(diào)用。但我們?cè)趯戭愃拼a時(shí)必須非常小心,由于斷掉了依賴關(guān)聯(lián),有可能會(huì)因?yàn)閷懛ú灰?guī)范導(dǎo)致響應(yīng)失效的情況。

接下來解釋為什么要使用位運(yùn)算,以及保留不走位運(yùn)算的邏輯。

關(guān)注以下代碼:

function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

當(dāng)每次觸發(fā)依賴更新時(shí),如果都調(diào)用以上方法,會(huì)涉及大量的集合刪減操作。

我沒看過Set集合的實(shí)現(xiàn),但無非就是數(shù)組或者鏈表。如果使用數(shù)組,增刪操作會(huì)導(dǎo)致數(shù)組擴(kuò)容或者移位,頻繁操作會(huì)耗費(fèi)大量性能,如果是鏈表,也要經(jīng)過一次查找,大量的調(diào)用是會(huì)消耗性能的。

那么為什么又要保留這個(gè)方法呢,這是因?yàn)閖s引擎在進(jìn)行整數(shù)位運(yùn)算時(shí)幾乎都是按32位運(yùn)算的,1 << 31后為負(fù)值,得不到預(yù)期結(jié)果,因此保留原邏輯。但其實(shí)這個(gè)邏輯幾乎不可能調(diào)到,如果真調(diào)用到這個(gè)原始邏輯,我只能說得檢查一下代碼是否規(guī)范了。

四、后話

關(guān)于響應(yīng)式就介紹到這兒,個(gè)人理解,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue面包屑組件的封裝方法

    vue面包屑組件的封裝方法

    這篇文章主要為大家詳細(xì)介紹了vue面包屑組件的封裝方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • vue2.0使用Sortable.js實(shí)現(xiàn)的拖拽功能示例

    vue2.0使用Sortable.js實(shí)現(xiàn)的拖拽功能示例

    本篇文章主要介紹了vue2.0使用Sortable.js實(shí)現(xiàn)的拖拽功能示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • vue2更改data里的變量不生效時(shí),深層更改data里的變量問題

    vue2更改data里的變量不生效時(shí),深層更改data里的變量問題

    這篇文章主要介紹了vue2更改data里的變量不生效時(shí),深層更改data里的變量問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • JavaScript實(shí)現(xiàn)簡單的圖片切換功能(實(shí)例代碼)

    JavaScript實(shí)現(xiàn)簡單的圖片切換功能(實(shí)例代碼)

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)簡單的圖片切換功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2020-04-04
  • 如何在在Vue3中使用markdown 編輯器組件

    如何在在Vue3中使用markdown 編輯器組件

    vue3發(fā)布正式版不久,生態(tài)還沒完全發(fā)展起來,目前支持vue3的開源markdown編輯器組件基本上也寥寥無幾,向大家推薦一個(gè)很好用的v-md-editor 組件,組件功能很強(qiáng)大,文檔也比較詳細(xì)。該文章只介紹組件的常用功能,更多高級(jí)的功能可以參考官方文檔。
    2021-05-05
  • vue3封裝ECharts組件詳解

    vue3封裝ECharts組件詳解

    前端開發(fā)需要經(jīng)常使用ECharts圖表渲染數(shù)據(jù)信息,在一個(gè)項(xiàng)目中我們經(jīng)常需要使用多個(gè)圖表,選擇封裝ECharts組件復(fù)用的方式可以減少代碼量,增加開發(fā)效率。感興趣的可以閱讀一下本文
    2023-04-04
  • vue實(shí)現(xiàn)拖拽交換位置

    vue實(shí)現(xiàn)拖拽交換位置

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)拖拽交換位置,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 淺析vue-router原理

    淺析vue-router原理

    這篇文章主要圍繞Vue的SPA單頁面設(shè)計(jì)展開。SPA(single page application):單一頁面應(yīng)用程序,有且只有一個(gè)完整的頁面,對(duì)vue router原理感興趣的朋友跟隨小編一起看看吧
    2018-10-10
  • Element-Ui組件 NavMenu 導(dǎo)航菜單的具體使用

    Element-Ui組件 NavMenu 導(dǎo)航菜單的具體使用

    這篇文章主要介紹了Element-Ui組件 NavMenu 導(dǎo)航菜單的具體使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Vue項(xiàng)目中使用addRoutes出現(xiàn)問題的解決方法

    Vue項(xiàng)目中使用addRoutes出現(xiàn)問題的解決方法

    大家應(yīng)該都知道可以通過vue-router官方提供的一個(gè)api-->addRoutes可以實(shí)現(xiàn)路由添加的功能,事實(shí)上就也就實(shí)現(xiàn)了用戶權(quán)限,這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中使用addRoutes出現(xiàn)問題的解決方法,需要的朋友可以參考下
    2021-08-08

最新評(píng)論