欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解從vue-loader源碼分析CSS Scoped的實現(xiàn)

 更新時間:2019年09月23日 15:05:55   作者:橙紅年代  
這篇文章主要介紹了詳解從vue-loader源碼分析CSS Scoped的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

雖然寫了很長一段時間的Vue了,對于CSS Scoped的原理也大致了解,但一直未曾關(guān)注過其實現(xiàn)細(xì)節(jié)。最近在重新學(xué)習(xí)webpack,因此查看了vue-loader源碼,順便從vue-loader的源碼中整理CSS Scoped的實現(xiàn)。

本文展示了vue-loader中的一些源碼片段,為了便于理解,稍作刪減。參考

Vue CSS SCOPED實現(xiàn)原理
Vue loader官方文檔

相關(guān)概念

CSS Scoped的實現(xiàn)原理

在Vue單文件組件中,我們只需要在style標(biāo)簽上加上scoped屬性,就可以實現(xiàn)標(biāo)簽內(nèi)的樣式在當(dāng)前模板輸出的HTML標(biāo)簽上生效,其實現(xiàn)原理如下

  • 每個Vue文件都將對應(yīng)一個唯一的id,該id可以根據(jù)文件路徑名和內(nèi)容hash生成
  • 編譯template標(biāo)簽時時為每個標(biāo)簽添加了當(dāng)前組件的id,如<div class="demo"></div>會被編譯成<div class="demo" data-v-27e4e96e></div>
  • 編譯style標(biāo)簽時,會根據(jù)當(dāng)前組件的id通過屬性選擇器和組合選擇器輸出樣式,如.demo{color: red;}會被編譯成.demo[data-v-27e4e96e]{color: red;}

了解了大致原理,可以想到css scoped應(yīng)該需要同時處理template和style的內(nèi)容,現(xiàn)在歸納需要探尋的問題

  • 渲染的HTML標(biāo)簽上的data-v-xxx屬性是如何生成的
  • CSS代碼中的添加的屬性選擇器是如何實現(xiàn)的

resourceQuery

在此之前,需要了解首一下webpack中Rules.resourceQuery的作用。在配置loader時,大部分時候我們只需要通過test匹配文件類型即可

{
 test: /\.vue$/,
 loader: 'vue-loader'
}
// 當(dāng)引入vue后綴文件時,將文件內(nèi)容傳輸給vue-loader進(jìn)行處理
import Foo from './source.vue'

resourceQuery提供了根據(jù)引入文件路徑參數(shù)的形式匹配路徑

{
 resourceQuery: /shymean=true/,
 loader: path.resolve(__dirname, './test-loader.js')
}
// 當(dāng)引入文件路徑攜帶query參數(shù)匹配時,也將加載該loader
import './test.js?shymean=true'
import Foo from './source.vue?shymean=true'

vue-loader中就是通過resourceQuery并拼接不同的query參數(shù),將各個標(biāo)簽分配給對應(yīng)的loader進(jìn)行處理。

loader.pitch

參考

pitching-loader官方文檔
webpack的pitching loader

webpack中l(wèi)oaders的執(zhí)行順序是從右到左執(zhí)行的,如loaders:[a, b, c],loader的執(zhí)行順序是c->b->a,且下一個loader接收到的是上一個loader的返回值,這個過程跟"事件冒泡"很像。

但是在某些場景下,我們可能希望在"捕獲"階段就執(zhí)行l(wèi)oader的一些方法,因此webpack提供了loader.pitch的接口。
一個文件被多個loader處理的真實執(zhí)行流程,如下所示

a.pitch -> b.pitch -> c.pitch -> request module -> c -> b -> a

loader和pitch的接口定義大概如下所示

// loader文件導(dǎo)出的真實接口,content是上一個loader或文件的原始內(nèi)容
module.exports = function loader(content){
 // 可以訪問到在pitch掛載到data上的數(shù)據(jù)
 console.log(this.data.value) // 100
}
// remainingRequest表示剩余的請求,precedingRequest表示之前的請求
// data是一個上下文對象,在上面的loader方法中可以通過this.data訪問到,因此可以在pitch階段提前掛載一些數(shù)據(jù)
module.exports.pitch = function pitch(remainingRequest, precedingRequest, data) {
 data.value = 100
}}

正常情況下,一個loader在execution階段會返回經(jīng)過處理后的文件文本內(nèi)容。如果在pitch方法中直接返回了內(nèi)容,則webpack會視為后面的loader已經(jīng)執(zhí)行完畢(包括pitch和execution階段)。

在上面的例子中,如果b.pitch返回了result b,則不再執(zhí)行c,則是直接將result b傳給了a。

VueLoaderPlugin

接下來看看與vue-loader配套的插件:VueLoaderPlugin,該插件的作用是:

將在webpack.config定義過的其它規(guī)則復(fù)制并應(yīng)用到 .vue 文件里相應(yīng)語言的塊中。

其大致工作流程如下所示

  • 獲取項目webpack配置的rules項,然后復(fù)制rules,為攜帶了?vue&lang=xx...query參數(shù)的文件依賴配置xx后綴文件同樣的loader
  • 為Vue文件配置一個公共的loader:pitcher
  • 將[pitchLoder, ...clonedRules, ...rules]作為webapck新的rules
// vue-loader/lib/plugin.js
const rawRules = compiler.options.module.rules // 原始的rules配置信息
const { rules } = new RuleSet(rawRules)

// cloneRule會修改原始rule的resource和resourceQuery配置,攜帶特殊query的文件路徑將被應(yīng)用對應(yīng)rule
const clonedRules = rules
   .filter(r => r !== vueRule)
   .map(cloneRule) 
// vue文件公共的loader
const pitcher = {
 loader: require.resolve('./loaders/pitcher'),
 resourceQuery: query => {
  const parsed = qs.parse(query.slice(1))
  return parsed.vue != null
 },
 options: {
  cacheDirectory: vueLoaderUse.options.cacheDirectory,
  cacheIdentifier: vueLoaderUse.options.cacheIdentifier
 }
}
// 更新webpack的rules配置,這樣vue單文件中的各個標(biāo)簽可以應(yīng)用clonedRules相關(guān)的配置
compiler.options.module.rules = [
 pitcher,
 ...clonedRules,
 ...rules
]

因此,為vue單文件組件中每個標(biāo)簽執(zhí)行的lang屬性,也可以應(yīng)用在webpack配置同樣后綴的rule。這種設(shè)計就可以保證在不侵入vue-loader的情況下,為每個標(biāo)簽配置獨立的loader,如

  1. 可以使用pug編寫template,然后配置pug-plain-loader
  2. 可以使用scss或less編寫style,然后配置相關(guān)預(yù)處理器loader

可見在VueLoaderPlugin主要做的兩件事,一個是注冊公共的pitcher,一個是復(fù)制webpack的rules。

vue-loader

接下來我們看看vue-loader做的事情。

pitcher

前面提到在VueLoaderPlugin中,該loader在pitch中會根據(jù)query.type注入處理對應(yīng)標(biāo)簽的loader

  • 當(dāng)type為style時,在css-loader后插入stylePostLoader,保證stylePostLoader在execution階段先執(zhí)行
  • 當(dāng)type為template時,插入templateLoader
// pitcher.js
module.exports = code => code
module.exports.pitch = function (remainingRequest) {
 if (query.type === `style`) {
  // 會查詢cssLoaderIndex并將其放在afterLoaders中
  // loader在execution階段是從后向前執(zhí)行的
  const request = genRequest([
   ...afterLoaders,
   stylePostLoaderPath, // 執(zhí)行l(wèi)ib/loaders/stylePostLoader.js
   ...beforeLoaders
  ])
  return `import mod from ${request}; export default mod; export * from ${request}`
 }
 // 處理模板
 if (query.type === `template`) {
  const preLoaders = loaders.filter(isPreLoader)
  const postLoaders = loaders.filter(isPostLoader)
  const request = genRequest([
   ...cacheLoader,
   ...postLoaders,
   templateLoaderPath + `??vue-loader-options`, // 執(zhí)行l(wèi)ib/loaders/templateLoader.js
   ...preLoaders
  ])
  return `export * from ${request}`
 }
 // ...
}

由于loader.pitch會先于loader,在捕獲階段執(zhí)行,因此主要進(jìn)行上面的準(zhǔn)備工作:檢查query.type并直接調(diào)用相關(guān)的loader

  • type=style,執(zhí)行stylePostLoader
  • type=template,執(zhí)行templateLoader

這兩個loader的具體作用我們后面再研究。

vueLoader

接下來看看vue-loader里面做的工作,當(dāng)引入一個x.vue文件時

// vue-loader/lib/index.js 下面source為Vue代碼文件原始內(nèi)容

// 將單個*.vue文件內(nèi)容解析成一個descriptor對象,也稱為SFC(Single-File Components)對象
// descriptor包含template、script、style等標(biāo)簽的屬性和內(nèi)容,方便為每種標(biāo)簽做對應(yīng)處理
const descriptor = parse({
 source,
 compiler: options.compiler || loadTemplateCompiler(loaderContext),
 filename,
 sourceRoot,
 needMap: sourceMap
})

// 為單文件組件生成唯一哈希id
const id = hash(
 isProduction
 ? (shortFilePath + '\n' + source)
 : shortFilePath
)
// 如果某個style標(biāo)簽包含scoped屬性,則需要進(jìn)行CSS Scoped處理,這也是本章節(jié)需要研究的地方
const hasScoped = descriptor.styles.some(s => s.scoped)

處理template標(biāo)簽,拼接type=template等query參數(shù)

if (descriptor.template) {
 const src = descriptor.template.src || resourcePath
 const idQuery = `&id=${id}`
 // 傳入文件id和scoped=true,在為組件的每個HTML標(biāo)簽傳入組件id時需要這兩個參數(shù)
 const scopedQuery = hasScoped ? `&scoped=true` : ``
 const attrsQuery = attrsToQuery(descriptor.template.attrs)
 const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
 const request = templateRequest = stringifyRequest(src + query)
 // type=template的文件會傳給templateLoader處理
 templateImport = `import { render, staticRenderFns } from ${request}`
 
 // 比如,<template lang="pug"></template>標(biāo)簽
 // 將被解析成 import { render, staticRenderFns } from "./source.vue?vue&type=template&id=27e4e96e&lang=pug&"
}

處理script標(biāo)簽

let scriptImport = `var script = {}`
if (descriptor.script) {
 // vue-loader沒有對script做過多的處理
 // 比如vue文件中的<script></script>標(biāo)簽將被解析成
 // import script from "./source.vue?vue&type=script&lang=js&"
 // export * from "./source.vue?vue&type=script&lang=js&"
}

處理style標(biāo)簽,為每個標(biāo)簽拼接type=style等參數(shù)

// 在genStylesCode中,會處理css scoped和css moudle
stylesCode = genStylesCode(
 loaderContext,
 descriptor.styles, 
 id,
 resourcePath,
 stringifyRequest,
 needsHotReload,
 isServer || isShadow // needs explicit injection?
)

// 由于一個vue文件里面可能存在多個style標(biāo)簽,對于每個標(biāo)簽,將調(diào)用genStyleRequest生成對應(yīng)文件的依賴
function genStyleRequest (style, i) {
 const src = style.src || resourcePath
 const attrsQuery = attrsToQuery(style.attrs, 'css')
 const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
 const idQuery = style.scoped ? `&id=${id}` : ``
 // type=style將傳給stylePostLoader進(jìn)行處理
 const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${inheritQuery}`
 return stringifyRequest(src + query)
}

可見在vue-loader中,主要是將整個文件按照標(biāo)簽拼接對應(yīng)的query路徑,然后交給webpack按順序調(diào)用相關(guān)的loader。

templateLoader

回到開頭提到的第一個問題:當(dāng)前組件中,渲染出來的每個HTML標(biāo)簽中的hash屬性是如何生成的。

我們知道,一個組件的render方法返回的VNode,描述了組件對應(yīng)的HTML標(biāo)簽和結(jié)構(gòu),HTML標(biāo)簽對應(yīng)的DOM節(jié)點是從虛擬DOM節(jié)點構(gòu)建的,一個Vnode包含了渲染DOM節(jié)點需要的基本屬性。

那么,我們只需要了解到vnode上組件文件的哈希id的賦值過程,后面的問題就迎刃而解了。

// templateLoader.js
const { compileTemplate } = require('@vue/component-compiler-utils')

module.exports = function (source) {
 const { id } = query
 const options = loaderUtils.getOptions(loaderContext) || {}
 const compiler = options.compiler || require('vue-template-compiler')
 // 可以看見,scopre=true的template的文件會生成一個scopeId
 const compilerOptions = Object.assign({
  outputSourceRange: true
 }, options.compilerOptions, {
  scopeId: query.scoped ? `data-v-${id}` : null,
  comments: query.comments
 })
 // 合并compileTemplate最終參數(shù),傳入compilerOptions和compiler
 const finalOptions = {source, filename: this.resourcePath, compiler,compilerOptions}
 const compiled = compileTemplate(finalOptions)
 
 const { code } = compiled

 // finish with ESM exports
 return code + `\nexport { render, staticRenderFns }`
}

關(guān)于compileTemplate的實現(xiàn),我們不用去關(guān)心其細(xì)節(jié),其內(nèi)部主要是調(diào)用了配置參數(shù)compiler的編譯方法

function actuallyCompile(options) {
 const compile = optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
 const { render, staticRenderFns, tips, errors } = compile(source, finalCompilerOptions);
 // ...
}

在Vue源碼中可以了解到,template屬性會通過compileToFunctions編譯成render方法;在vue-loader中,這一步是可以通過vue-template-compiler提前在打包階段處理的。

vue-template-compiler是隨著Vue源碼一起發(fā)布的一個包,當(dāng)二者同時使用時,需要保證他們的版本號一致,否則會提示錯誤。這樣,compiler.compile實際上是Vue源碼中vue/src/compiler/index.js的baseCompile方法,追著源碼一致翻下去,可以發(fā)現(xiàn)

// elementToOpenTagSegments.js
// 對于單個標(biāo)簽的屬性,將拆分成一個segments
function elementToOpenTagSegments (el, state): Array<StringSegment> {
 applyModelTransform(el, state)
 let binding
 const segments = [{ type: RAW, value: `<${el.tag}` }]
 // ... 處理attrs、domProps、v-bind、style、等屬性
 
 // _scopedId
 if (state.options.scopeId) {
  segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
 }
 segments.push({ type: RAW, value: `>` })
 return segments
}

以前面的<div class="demo"></div>為例,解析得到的segments為

[
  { type: RAW, value: '<div' },
  { type: RAW, value: 'class=demo' },
  { type: RAW, value: 'data-v-27e4e96e' }, // 傳入的scopeId
  { type: RAW, value: '>' },
]

至此,我們知道了在templateLoader中,會根據(jù)單文件組件的id,拼接一個scopeId,并作為compilerOptions傳入編譯器中,被解析成vnode的配置屬性,然后在render函數(shù)執(zhí)行時調(diào)用createElement,作為vnode的原始屬性,渲染成到DOM節(jié)點上。

stylePostLoader

在stylePostLoader中,需要做的工作就是將所有選擇器都增加一個屬性選擇器的組合限制,

const { compileStyle } = require('@vue/component-compiler-utils')
module.exports = function (source, inMap) {
 const query = qs.parse(this.resourceQuery.slice(1))
 const { code, map, errors } = compileStyle({
  source,
  filename: this.resourcePath,
  id: `data-v-${query.id}`, // 同一個單頁面組件中的style,與templateLoader中的scopeId保持一致
  map: inMap,
  scoped: !!query.scoped,
  trim: true
 })
 this.callback(null, code, map)
}

我們需要了解compileStyle的邏輯

// @vue/component-compiler-utils/compileStyle.ts
import scopedPlugin from './stylePlugins/scoped'
function doCompileStyle(options) {
 const { filename, id, scoped = true, trim = true, preprocessLang, postcssOptions, postcssPlugins } = options;
 if (scoped) {
  plugins.push(scopedPlugin(id));
 }
 const postCSSOptions = Object.assign({}, postcssOptions, { to: filename, from: filename });
 // 省略了相關(guān)判斷
 let result = postcss(plugins).process(source, postCSSOptions);
}

最后讓我們在了解一下scopedPlugin的實現(xiàn),

export default postcss.plugin('add-id', (options: any) => (root: Root) => {
 const id: string = options
 const keyframes = Object.create(null)
 root.each(function rewriteSelector(node: any) {
  node.selector = selectorParser((selectors: any) => {
   selectors.each((selector: any) => {
    let node: any = null
    // 處理 '>>>' 、 '/deep/'、::v-deep、pseudo等特殊選擇器時,將不會執(zhí)行下面添加屬性選擇器的邏輯

    // 為當(dāng)前選擇器添加一個屬性選擇器[id],id即為傳入的scopeId
    selector.insertAfter(
     node,
     selectorParser.attribute({
      attribute: id
     })
    )
   })
  }).processSync(node.selector)
 })
})

由于我對于PostCSS的插件開發(fā)并不是很熟悉,這里只能大致整理,翻翻文檔了,相關(guān)API可以參考Writing a PostCSS Plugin。

至此,我們就知道了第二個問題的答案:通過selector.insertAfter為當(dāng)前styles下的每一個選擇器添加了屬性選擇器,其值即為傳入的scopeId。由于只有當(dāng)前組件渲染的DOM節(jié)點上上面存在相同的屬性,從而就實現(xiàn)了css scoped的效果。

小結(jié)

回過頭來整理一下vue-loader的工作流程

首先需要在webpack配置中注冊VueLoaderPlugin

  1. 在插件中,會復(fù)制當(dāng)前項目webpack配置中的rules項,當(dāng)資源路徑包含query.lang時通過resourceQuery匹配相同的rules并執(zhí)行對應(yīng)loader時
  2. 插入一個公共的loader,并在pitch階段根據(jù)query.type插入對應(yīng)的自定義loader

準(zhǔn)備工作完成后,當(dāng)加載*.vue時會調(diào)用vue-loader,

  • 一個單頁面組件文件會被解析成一個descriptor對象,包含template、script、styles等屬性對應(yīng)各個標(biāo)簽,
  • 對于每個標(biāo)簽,會根據(jù)標(biāo)簽屬性拼接src?vue&query引用代碼,其中src為單頁面組件路徑,query為一些特性的參數(shù),比較重要的有l(wèi)ang、type和scoped
    • 如果包含lang屬性,會匹配與該后綴相同的rules并應(yīng)用對應(yīng)的loaders
    • 根據(jù)type執(zhí)行對應(yīng)的自定義loader,template將執(zhí)行templateLoader、style將執(zhí)行stylePostLoader

 在templateLoader中,會通過vue-template-compiler將template轉(zhuǎn)換為render函數(shù),在此過程中,

  • 會將傳入的scopeId追加到每個標(biāo)簽的segments上,最后作為vnode的配置屬性傳遞給createElemenet方法,
  • 在render函數(shù)調(diào)用并渲染頁面時,會將scopeId屬性作為原始屬性渲染到頁面上

在stylePostLoader中,通過PostCSS解析style標(biāo)簽內(nèi)容,同時通過scopedPlugin為每個選擇器追加一個[scopeId]的屬性選擇器

由于需要Vue源碼方面的支持(vue-template-compiler編譯器),CSS Scoped可以算作為Vue定制的一個處理原生CSS全局作用域的解決方案。除了 css scoped之外,vue還支持css module,我打算在下一篇整理React中編寫CSS的博客中一并對比整理。

小結(jié)

最近一直在寫React的項目,嘗試了好幾種在React中編寫CSS的方式,包括CSS Module、Style Component等方式,感覺都比較繁瑣。相比而言,在Vue中單頁面組件中寫CSS要方便很多。

本文主要從源碼層面分析了Vue-loader,整理了其工作原理,感覺收獲頗豐

  1. webpack中Rules.resourceQuery和pitch loader的使用
  2. Vue單頁面文件中css scoped的實現(xiàn)原理
  3. PostCSS插件的作用

雖然一直在使用webpack和PostCSS,但也僅限于勉強(qiáng)會用的階段,比如我甚至從來沒有過編寫一個PostCSS插件的想法。盡管目前大部分項目都使用了封裝好的腳手架,但對于這些基礎(chǔ)知識,還是很有必要去了解其實現(xiàn)的。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Vue3+Canvas實現(xiàn)坦克大戰(zhàn)游戲(一)

    Vue3+Canvas實現(xiàn)坦克大戰(zhàn)游戲(一)

    這篇文章將利用Vue3和Canvas編寫一個童年經(jīng)典游戲—坦克大戰(zhàn),文中的示例代碼講解詳細(xì),感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)一下吧
    2022-03-03
  • vue .js綁定checkbox并獲取、改變選中狀態(tài)的實例

    vue .js綁定checkbox并獲取、改變選中狀態(tài)的實例

    今天小編就為大家分享一篇vue .js綁定checkbox并獲取、改變選中狀態(tài)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解vue-template-admin三級路由無法緩存的解決方案

    詳解vue-template-admin三級路由無法緩存的解決方案

    這篇文章主要介紹了vue-template-admin三級路由無法緩存的解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Vue實現(xiàn)批量注冊全局組件的示例代碼

    Vue實現(xiàn)批量注冊全局組件的示例代碼

    在項目開發(fā)中,我們經(jīng)常會封裝一些全局組件,然后在入口文件中統(tǒng)一導(dǎo)入,所以本文主要為大家詳細(xì)介紹了Vue如何批量注冊全局組件,感興趣的小伙伴可以了解下
    2024-01-01
  • element表格數(shù)據(jù)部分模糊的實現(xiàn)代碼

    element表格數(shù)據(jù)部分模糊的實現(xiàn)代碼

    這篇文章給大家介紹了element表格數(shù)據(jù)模糊的實現(xiàn)代碼,文中有詳細(xì)的效果展示和實現(xiàn)代碼供大家參考,具有一定的參考價值,需要的朋友可以參考下
    2024-01-01
  • Vue中的請求攔截器和響應(yīng)攔截器用法及說明

    Vue中的請求攔截器和響應(yīng)攔截器用法及說明

    這篇文章主要介紹了Vue中的請求攔截器和響應(yīng)攔截器用法及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • vue項目中實現(xiàn)緩存的最佳方案詳解

    vue項目中實現(xiàn)緩存的最佳方案詳解

    這篇文章主要給大家介紹了關(guān)于vue項目中實現(xiàn)緩存的最佳方案,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 談?wù)刅ue.js——vue-resource全攻略

    談?wù)刅ue.js——vue-resource全攻略

    本篇文章主要介紹了談?wù)刅ue.js——vue-resource全攻略,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • Vue使用Swiper封裝輪播圖組件的方法詳解

    Vue使用Swiper封裝輪播圖組件的方法詳解

    Swiper是一個很常用的用于實現(xiàn)各種滑動效果的插件,PC端和移動端都能很好的適配。本文將利用Swiper實現(xiàn)封裝輪播圖組件,感興趣的可以了解一下
    2022-09-09
  • Vue路由實現(xiàn)頁面跳轉(zhuǎn)的示例代碼

    Vue路由實現(xiàn)頁面跳轉(zhuǎn)的示例代碼

    根據(jù)登錄的角色不一樣,實現(xiàn)不同的路由展示,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-04-04

最新評論