Vue編程三部曲之模型樹(shù)優(yōu)化示例
前言
對(duì)編譯過(guò)程的了解會(huì)讓我們對(duì) Vue 的指令、內(nèi)置組件等有更好的理解。不過(guò)由于編譯的過(guò)程是一個(gè)相對(duì)復(fù)雜的過(guò)程,我們只要求理解整體的流程、輸入和輸出即可,對(duì)于細(xì)節(jié)我們不必?fù)柑?xì)。由于篇幅較長(zhǎng),這里會(huì)用三篇文章來(lái)講 Vue 的編譯。這是第二篇,模型樹(shù)優(yōu)化。
在上一篇文章中,我們分析了 Vue 編譯三部曲的第一步,「如何將 template 編譯成 AST ?」
我們簡(jiǎn)單回顧一下,parse 的目的是將開(kāi)發(fā)者寫的 template 模板字符串轉(zhuǎn)換成抽象語(yǔ)法樹(shù) AST ,AST 就這里來(lái)說(shuō)就是一個(gè)樹(shù)狀結(jié)構(gòu)的 JavaScript 對(duì)象,描述了這個(gè)模板,這個(gè)對(duì)象包含了每一個(gè)元素的上下文關(guān)系。那么整個(gè) parse 的過(guò)程是利用很多正則表達(dá)式順序解析模板,當(dāng)解析到開(kāi)始標(biāo)簽、閉合標(biāo)簽、文本的時(shí)候都會(huì)分別執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù),來(lái)達(dá)到構(gòu)造 AST 樹(shù)的目的。
當(dāng)我們的 template 被轉(zhuǎn)換為 AST 之后,接下來(lái)我們需要對(duì)這棵 AST 語(yǔ)法樹(shù)做優(yōu)化。
為什么要做優(yōu)化?
在源碼的注釋中找到了下面這段話:
Goal of the optimizer: walk the generated template AST tree and detect sub-trees that are purely static, i.e. parts of the DOM that never needs to change. Once we detect these sub-trees, we can:
Hoist them into constants, so that we no longer need to create fresh nodes for them on each re-render;
Completely skip them in the patching process.
簡(jiǎn)單理解就是:
- 永遠(yuǎn)不需要變化的 DOM 就是靜態(tài)的。
- 重新渲染時(shí),作為常量,無(wú)需創(chuàng)建新節(jié)點(diǎn);
因?yàn)槲覀冎?Vue 是一個(gè)數(shù)據(jù)驅(qū)動(dòng)視圖的響應(yīng)式框架,但是在開(kāi)發(fā)者書寫的 template 中,也不是所有的數(shù)據(jù)都是響應(yīng)式的,有很多的數(shù)據(jù)在首屏渲染完之后就永遠(yuǎn)不在變化,數(shù)據(jù)不在變化也就意味著 DOM 不在變化,所以在后續(xù)的更新過(guò)程進(jìn)行 patch時(shí)完全可以直接跳過(guò)他們的比對(duì),從而來(lái)提升效率。
接下來(lái)我們開(kāi)始 optimize 源碼之旅!看看源碼中是如何去優(yōu)化模型樹(shù)的?
optimize
template 在經(jīng)過(guò)解析之后,就會(huì)進(jìn)行優(yōu)化操作。首先這里有一個(gè)小邏輯,會(huì)判斷是否需要進(jìn)行優(yōu)化?只有當(dāng)options.optimize !== false時(shí)才會(huì)進(jìn)行優(yōu)化。
這里拋出幾個(gè)小問(wèn)題?options.optimize為什么需要進(jìn)行這樣的判斷了?并且如何能關(guān)閉模型樹(shù)優(yōu)化的操作了?什么情況下會(huì)關(guān)閉模型樹(shù)的優(yōu)化?
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
在往下,進(jìn)入到optimize函數(shù),代碼很清楚,優(yōu)化主要做兩件事情:
- markStatic$1(root) 標(biāo)記靜態(tài)節(jié)點(diǎn)
- markStaticRoots(root, false) 標(biāo)記靜態(tài)根
function optimize (root, options) {
if (!root) { return }
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// 第一步:標(biāo)記所有靜態(tài)節(jié)點(diǎn)。
markStatic$1(root);
// 第二步:標(biāo)記靜態(tài)根
markStaticRoots(root, false);
}
在進(jìn)行優(yōu)化操作之前會(huì)有兩個(gè)變量的賦值。
isStaticKey
獲取 genStaticKeysCached 函數(shù)返回值, 獲取 makeMap 函數(shù)返回值引用 。
isStaticKey = genStaticKeysCached(options.staticKeys || '');
這里簡(jiǎn)單了解一下涉及到的 makeMap 函數(shù):
- makeMap 函數(shù)首先根據(jù)一個(gè)字符串生成一個(gè) map,然后根據(jù)該 map 產(chǎn)生一個(gè)新函數(shù),新函數(shù)接收一個(gè)字符串參數(shù)作為 key,如果這個(gè) key 在 map 中則返回 true,否則返回 undefined。
- str 一個(gè)以逗號(hào)分隔的字符串 、expectsLowerCase 是否小寫
- makeMap 函數(shù)返回值是一個(gè)根據(jù)生成的 map 產(chǎn)生的函數(shù)
function makeMap(str, expectsLowerCase) {
var map = Object.create(null);
var list = str.split(',');
for (var i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase ?
function(val) {
return map[val.toLowerCase()];
} :
function(val) {
return map[val];
}
}
function genStaticKeys$1 (keys) {
return makeMap(
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
(keys ? ',' + keys : '')
)
}
function cached (fn) {
var cache = Object.create(null);
return (function cachedFn (str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
var genStaticKeysCached = cached(genStaticKeys$1);
這里聊一個(gè)題外話,如果你認(rèn)真看上面這段代碼,你會(huì)發(fā)現(xiàn),這里大量的使用了閉包,保護(hù)和保存數(shù)據(jù)。這也告訴我們?cè)诘鸬目蚣?,其?shí)底層也是簡(jiǎn)單易懂的一些基礎(chǔ)思想。
isStaticKey 的值就是利用 makeMap 的返回引用做值的判斷。判斷節(jié)點(diǎn)的屬性是否在相對(duì)于的范圍內(nèi):例如有這樣一個(gè) template:
<div></div>
然后parse完之后變成這樣一個(gè)描述對(duì)象,所有屬性通過(guò) isStaticKey 判斷之后,都在上面列出的屬性范圍中,都是靜態(tài)屬性,所以這就是一個(gè)靜態(tài)節(jié)點(diǎn)。
{
"type": 1,
"tag": "div",
"attrsList": [],
"attrsMap": {},
"rawAttrsMap": {},
"children": [],
"start": 0,
"end": 11,
"plain": true
}
另外一個(gè)屬性是 isPlatformReservedTag。
isPlatformReservedTag
isPlatformReservedTag 用于獲取編譯器選項(xiàng) isReservedTag 的引用,檢查給定的字符是否是保留的標(biāo)簽。
isPlatformReservedTag = options.isReservedTag || no;
isReservedTag函數(shù)如下,用這個(gè)函數(shù)來(lái)判斷是否是保留標(biāo)簽,如果一個(gè)標(biāo)簽是 html標(biāo)簽或者是 svg標(biāo)簽,那么這個(gè)標(biāo)簽就是保留標(biāo)簽。
HTML 保留標(biāo)簽
'html,body,base,head,link,meta,style,title,'+ 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,'+ 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,'+ 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,'+ 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,'+ 'embed,object,param,source,canvas,script,noscript,del,ins,'+ 'caption,col,colgroup,table,thead,tbody,td,th,tr,'+ 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,'+ 'output,progress,select,textarea,'+
'details,dialog,menu,menuitem,summary,'+ 'content,element,shadow,template,blockquote,iframe,tfoot'
SVG 保留標(biāo)簽
'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,'+ 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,'+ 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
var isReservedTag = function(tag) {
return isHTMLTag(tag) || isSVG(tag)
};
并且在后續(xù)的節(jié)點(diǎn)標(biāo)記中會(huì)被用到。我們?cè)诮又驴矗攸c(diǎn)來(lái)了。
標(biāo)記靜態(tài)節(jié)點(diǎn)
function markStatic$1 (node) {
// ①
node.static = isStatic(node);
// ②
if (node.type === 1) {
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (var i = 0, l = node.children.length; i < l; i++) {
var child = node.children[i];
markStatic$1(child);
if (!child.static) {
node.static = false;
}
}
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
var block = node.ifConditions[i$1].block;
markStatic$1(block);
if (!block.static) {
node.static = false;
}
}
}
}
}
判斷節(jié)點(diǎn)狀態(tài)并標(biāo)記
第一步,判斷階段狀態(tài)并標(biāo)記。在這給 AST 元素節(jié)點(diǎn)擴(kuò)展了static屬性,通過(guò) isStatic方法調(diào)用后返回值,確認(rèn)哪些節(jié)點(diǎn)是靜態(tài)的,哪些是動(dòng)態(tài)的。
node.static = isStatic(node);
那在 Vue 中那些節(jié)點(diǎn)算是動(dòng)態(tài)的,那些階段算是靜態(tài)的了?我們先回顧一下上一篇文章在講生成 AST 時(shí),給每一個(gè)元素節(jié)點(diǎn)標(biāo)記type類型,一種有type類型幾種?
沒(méi)錯(cuò)是三種。
- type = 1的基礎(chǔ)元素節(jié)點(diǎn)
- type = 2含有expression和tokens的文本節(jié)點(diǎn)
- type = 3的純文本節(jié)點(diǎn)或者是注釋節(jié)點(diǎn)
child = {
type: 1,
tag:"div",
parent: null,
children: [],
attrsList: []
};
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text: text
};
child = {
type: 3,
text: text
};
child = {
type: 3,
text: text,
isComment: true
};
isStatic函數(shù)會(huì)根據(jù)元素的 type和元素的屬性進(jìn)行節(jié)點(diǎn)動(dòng)靜態(tài)的判斷。
如果type = 2說(shuō)明這一點(diǎn)是一個(gè)動(dòng)態(tài)節(jié)點(diǎn),因?yàn)榘磉_(dá)式
如果type = 3說(shuō)明可能是純文本節(jié)點(diǎn)或者是注釋節(jié)點(diǎn),可以標(biāo)記為靜態(tài)節(jié)點(diǎn)
如果元素節(jié)點(diǎn)有:
- pre 屬性,使用了 v-pre指令,標(biāo)記為靜態(tài)節(jié)點(diǎn)
- 如果沒(méi)有動(dòng)態(tài)綁定,沒(méi)有使用v-if、v-for,不是內(nèi)置標(biāo)簽(slot,component),是平臺(tái)保留標(biāo)簽(HTML 標(biāo)簽和 SVG 標(biāo)簽),不是 template 標(biāo)簽的直接子元素并且沒(méi)有包含在 for 循環(huán)中,節(jié)點(diǎn)包含的屬性只能有 isStaticKey 中指定的幾個(gè),那么就標(biāo)記為靜態(tài)節(jié)點(diǎn)。
現(xiàn)在就知道在什么情況下, Vue 會(huì)將一個(gè)節(jié)點(diǎn)標(biāo)記為動(dòng)態(tài)節(jié)點(diǎn),什么時(shí)候會(huì)將一個(gè)節(jié)點(diǎn)標(biāo)記為靜態(tài)節(jié)點(diǎn)。
并且在這里也利用到了上面初始賦值的兩個(gè)變量,isPlatformReservedTag和 isStaticKey,分別用來(lái)判斷是否是平臺(tái)保留標(biāo)簽(HTML 標(biāo)簽和 SVG 標(biāo)簽)和間距判斷節(jié)點(diǎn)的屬性只能有 isStaticKey 中指定的幾個(gè)。
function isStatic(node) {
if (node.type === 2) {
return false
}
if (node.type === 3) {
return true
}
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
標(biāo)記完節(jié)點(diǎn),我們接下往下看。
基礎(chǔ)元素節(jié)點(diǎn)的處理
來(lái)到第二步,這里處理的是節(jié)點(diǎn)類型 type = 1的幾點(diǎn)。也就是我們的元素節(jié)點(diǎn)。
對(duì)于我們的元素節(jié)點(diǎn),如果不是平臺(tái)保留標(biāo)簽(HTML 標(biāo)簽和 SVG 標(biāo)簽、不是 slot 標(biāo)簽、節(jié)點(diǎn)是 inline-template那么就會(huì)直接返回。
inline-template :內(nèi)聯(lián)模板,一般很少被用到,它是一個(gè)特殊的 attribute ,當(dāng)出現(xiàn)在一個(gè)子組件上時(shí),這個(gè)組件將會(huì)使用其里面的內(nèi)容作為模板,而不是將其作為被分發(fā)的內(nèi)容。這使得模板的撰寫工作更加靈活。但是,在 Vue 3.0 版本去掉了這個(gè)內(nèi)聯(lián)模板,原因在于 inline-template 會(huì)讓模板的作用域變得更加難以理解。所以作為最佳實(shí)踐,請(qǐng)?jiān)诮M件內(nèi)優(yōu)先選擇 template 選項(xiàng)或 .vue 文件里的一個(gè) <template> 元素來(lái)定義模板。
然后通過(guò) node.children 找到子節(jié)點(diǎn),遞歸子節(jié)點(diǎn)。如果子節(jié)點(diǎn)非靜態(tài),那么該節(jié)點(diǎn)也標(biāo)注非靜態(tài) 。這塊設(shè)計(jì)的不太合理有更多好的優(yōu)化方案,在 Vue3.0 做了優(yōu)化,編譯階段對(duì)靜態(tài)模板的分析,編譯生成了 Block tree。Block tree 是一個(gè)將模版基于動(dòng)態(tài)節(jié)點(diǎn)指令切割的嵌套區(qū)塊,每個(gè)區(qū)塊內(nèi)部的節(jié)點(diǎn)結(jié)構(gòu)是固定的,而且每個(gè)區(qū)塊只需要以一個(gè) Array 來(lái)追蹤自身包含的動(dòng)態(tài)節(jié)點(diǎn)。借助 Block tree,Vue.js 將 vnode 更新性能由與模版整體大小相關(guān)提升為與動(dòng)態(tài)內(nèi)容的數(shù)量相關(guān),這是一個(gè)非常大的性能突破。
if (!child.static) {
node.static = false;
}
最后判斷如果節(jié)點(diǎn)的 ifConditions 不為空,則遍歷 ifConditions拿到所有條件中的 block,block 其實(shí)也就是它們對(duì)應(yīng)的 AST 節(jié)點(diǎn),遞歸執(zhí)行 markStatic。在這些遞歸過(guò)程中,一旦子節(jié)點(diǎn)有不是 static 的情況,則它的父節(jié)點(diǎn)的 static 均變成 false。
ifConditions 是撒?
ifConditions 其實(shí)是 if 條件的集合,例如有一個(gè)模板如下:
<div>
<div v-if={show}>hello, {{ text }},{{ message }}</div>
<div v-else-if={show1}>hello, world</div>
<div v-else>撒也沒(méi)有!</div>
</div>
那在 parse階段就會(huì)在的 AST 節(jié)點(diǎn)中就會(huì)給相對(duì)于元素的ifConditions添加關(guān)聯(lián)的所有判斷集合。

并且每一個(gè)ifConditions元素 的block描述就是判斷的節(jié)點(diǎn)內(nèi)容。

接下來(lái)看下 markStaticRoots。
標(biāo)記靜態(tài)根
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
標(biāo)記靜態(tài)根節(jié)點(diǎn),整體邏輯大致分為三步:
- 第一步,已經(jīng)是 static 的節(jié)點(diǎn)或者是 v-once 指令的節(jié)點(diǎn),設(shè)置 node.staticInFor = isInFor。
- 第二步,對(duì)于 staticRoot 的判斷邏輯。
- 第三步,遍歷 children 以及 ifConditions,遞歸執(zhí)行 markStaticRoots。
注意這里的根節(jié)點(diǎn)不一定就是 template 最外層的節(jié)點(diǎn),也可能是內(nèi)部的節(jié)點(diǎn)。
什么節(jié)點(diǎn)會(huì)成為靜態(tài)根?
從源碼來(lái)看,一個(gè)節(jié)點(diǎn)要想成為靜態(tài)根,必須滿足以下幾個(gè)條件:
- 自生是一個(gè)靜態(tài)節(jié)點(diǎn)
- 包含子元素
- 子節(jié)點(diǎn)不能僅為一個(gè)文本節(jié)點(diǎn)(排除注釋節(jié)點(diǎn),原因在于除非手動(dòng)開(kāi)啟保留注釋,否則注釋節(jié)點(diǎn)不會(huì)存在)
為什么子節(jié)點(diǎn)不能僅為一個(gè)文本節(jié)點(diǎn)?
當(dāng)只有純文本的子節(jié)點(diǎn)時(shí),它是一個(gè)靜態(tài)節(jié)點(diǎn),但是不是一個(gè)靜態(tài)根節(jié)點(diǎn)。這是為什么了?Vue 官方說(shuō)明是,如果子節(jié)點(diǎn)只有一個(gè)純文本節(jié)點(diǎn),如果優(yōu)化的話,帶來(lái)的成本就比好處多了,所以就不優(yōu)化。
具體為什么不優(yōu)化了,大家可以思考一下?
標(biāo)記靜態(tài)節(jié)點(diǎn)和靜態(tài)根節(jié)點(diǎn)有什么區(qū)別?
回顧之前這兩個(gè)標(biāo)記函數(shù),發(fā)現(xiàn)是先將每一個(gè)節(jié)點(diǎn)都處理了,給每一個(gè)節(jié)點(diǎn)都加上標(biāo)記之后,然后利用節(jié)點(diǎn)的狀態(tài)來(lái)判斷根節(jié)點(diǎn)的狀態(tài)。這樣可以利用子節(jié)點(diǎn)反推根節(jié)點(diǎn)。這就好比:「一個(gè)組內(nèi)部大家都是前端開(kāi)發(fā),那么間接可以推斷,這個(gè)組的小組長(zhǎng)也是前端開(kāi)發(fā)(當(dāng)然不是絕對(duì)的哈,只是比方)」。
靜態(tài)根節(jié)點(diǎn)和靜態(tài)節(jié)點(diǎn)有一種大包小感覺(jué),利用靜態(tài)節(jié)點(diǎn)的標(biāo)記函數(shù),間接給靜態(tài)根節(jié)點(diǎn)的標(biāo)記函數(shù)服務(wù)。并且通過(guò)靜態(tài)節(jié)點(diǎn)的標(biāo)記函數(shù)添加的 static 屬性,并不會(huì)在后續(xù) DOM 的處理和 render 上使用。但是通過(guò)靜態(tài)根節(jié)點(diǎn)的標(biāo)記函數(shù)添加的 staticRoot 屬性會(huì)在 render中使用。
總結(jié)
至此分析完了 optimize 的過(guò)程。
optimize前 AST 是這樣的:

optimize后 AST 多了static和staticRoot標(biāo)記:

整個(gè)optimize 的過(guò)程,就是深度遍歷這個(gè) AST 樹(shù),去檢測(cè)它的每一顆子樹(shù)是不是靜態(tài)節(jié)點(diǎn),如果是靜態(tài)節(jié)點(diǎn)表示生成的 DOM 永遠(yuǎn)不需要改變,這對(duì)運(yùn)行時(shí)對(duì)模板的更新起到極大的優(yōu)化作用,提升了運(yùn)行效率。
參考
Vue原理Compile - 源碼版 之 optimize 標(biāo)記靜態(tài)節(jié)點(diǎn)
以上就是Vue編程三部曲之模型樹(shù)優(yōu)化示例的詳細(xì)內(nèi)容,更多關(guān)于Vue編程模型樹(shù)優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue跨域問(wèn)題:Access?to?XMLHttpRequest?at‘httplocalhost解決
在前端發(fā)出Ajax請(qǐng)求的時(shí)候,有時(shí)候會(huì)產(chǎn)生跨域問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于vue跨域問(wèn)題:Access?to?XMLHttpRequest?at‘httplocalhost的解決辦法,需要的朋友可以參考下2023-01-01
vue3 el-table 如何通過(guò)深度選擇器::v-deep修改組件內(nèi)部樣式(默認(rèn)樣式)
在Vue3中,通過(guò)使用深度選擇器::v-deep可以有效修改element-plus中el-table組件的內(nèi)部樣式,這種方法允許開(kāi)發(fā)者覆蓋默認(rèn)的樣式,實(shí)現(xiàn)自定義的視覺(jué)效果,本文給大家介紹vue3 el-table 通過(guò)深度選擇器::v-deep修改組件內(nèi)部樣式,感興趣的朋友一起看看吧2024-10-10
vue3使用reactive包裹數(shù)組正確賦值問(wèn)題
這篇文章主要介紹了vue3使用reactive包裹數(shù)組正確賦值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
vue - props 聲明數(shù)組和對(duì)象操作
這篇文章主要介紹了vue - props 聲明數(shù)組和對(duì)象操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
教你如何開(kāi)發(fā)Vite3插件構(gòu)建Electron開(kāi)發(fā)環(huán)境
這篇文章主要介紹了如何開(kāi)發(fā)Vite3插件構(gòu)建Electron開(kāi)發(fā)環(huán)境,文中給大家提到了如何讓 Vite 加載 Electron 的內(nèi)置模塊和 Node.js 的內(nèi)置模塊,需要的朋友可以參考下2022-11-11
vue如何通過(guò)插槽組件之間數(shù)據(jù)傳遞
這篇文章主要介紹了vue如何通過(guò)插槽組件之間數(shù)據(jù)傳遞問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
vue編寫的功能強(qiáng)大的swagger-ui頁(yè)面及使用方式
swagger是一種標(biāo)準(zhǔn)的數(shù)據(jù)格式的定義,對(duì)于不同語(yǔ)言進(jìn)行實(shí)現(xiàn)一些注解API式的東西,能快速生成這種描述restful格式的api信息的json串,本文給大家詳細(xì)介紹vue編寫的功能強(qiáng)大的swagger-ui頁(yè)面,感興趣的朋友跟隨小編一起看看吧2022-02-02

