淺談Vue頁面級緩存解決方案feb-alive(上)
feb-alive
使用理由
- 開發(fā)者無需因為動態(tài)路由或者普通路由的差異而將數(shù)據(jù)初始化邏輯寫在不同的鉤子里beforeRouteUpdate或者activated
- 開發(fā)者無需手動緩存頁面狀態(tài),例如通過localStorage或者sessionStorage緩存當(dāng)前頁面的數(shù)據(jù)
- feb-alive會幫你處理路由meta信息的存儲與恢復(fù)
為什么開發(fā)feb-laive?
當(dāng)我們通過Vue開發(fā)項目時候,是否會有以下場景需求?
- /a跳轉(zhuǎn)到/b
- 后退到/a時候,希望從緩存中恢復(fù)頁面
- 再次跳轉(zhuǎn)到/b時,分兩種情況
- 情況一: 通過鏈接或者push跳轉(zhuǎn),則希望重新創(chuàng)建/b頁面,而不是從緩存中讀取
- 情況二: 如果點擊瀏覽器自帶前進(jìn)按鈕,則還是從緩存中讀取頁面。
這個場景需求著重強(qiáng)調(diào)了緩存,緩存帶來的好處是,我上次頁面的數(shù)據(jù)及狀態(tài)都被保留,無需在從服務(wù)器拉取數(shù)據(jù),使用戶體驗大大提高。
嘗試用keep-alive實現(xiàn)頁面緩存
<keep-alive> <router-view></router-view> </keep-alive>
so easy但是理想很完美,現(xiàn)實很殘酷
存在問題
-/a跳到/b,再跳轉(zhuǎn)到/a 的時候,頁面中的數(shù)據(jù)是第一次訪問的/a頁面,明明是鏈接跳轉(zhuǎn),確出現(xiàn)了緩存的效果,而我們期望的是像app一樣開啟一個新的頁面。
- 同理動態(tài)路由跳轉(zhuǎn)/page/1->/page/2因為兩個頁面引用的是同一個組件,所以跳轉(zhuǎn)時頁面就不會有任何改變,因為keep-alive的緩存的key是根據(jù)組件來生成的(當(dāng)然Vue提供了beforeRouteUpdate鉤子供我們刷新數(shù)據(jù))
- 總結(jié):keep-alive的緩存是==組件級別==的,而不是==頁面級別==的。
舉個應(yīng)用場景
例如瀏覽文章頁面,依次訪問3篇文章
- /artical/1
- /artical/2
- /artical/3
當(dāng)我從/artical/3后退到/artical/2時候,由于組件緩存,此時頁面還是文章3的內(nèi)容,所以必須通過beforeRouteUpdate來重新拉取頁面2的數(shù)據(jù)。(注意此處后退不會觸發(fā)組件的activated鉤子,因為兩個路由都渲染同個組件,所以實例會被復(fù)用,不會執(zhí)行reactivateComponent)
如果你想從/artical/3后退到/artical/2時,同時想恢復(fù)之前在/artical/2中的一些狀態(tài),那么你還需要自己針對/artical/2中的所有狀態(tài)數(shù)據(jù)進(jìn)行存儲和恢復(fù)。
綜上:keep-alive實現(xiàn)的組件級別的緩存和我們想象中的緩存還是有差距的,keep-alive并不能滿足我們的需求。
==針對這些問題,所以feb-alive插件誕生了==
由于feb-alive是基于keep-alive實現(xiàn)的,所以我們先簡單分析一下keep-alive是如何實現(xiàn)緩存的
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
// 獲取默認(rèn)插槽
const slot = this.$slots.default
// 獲取第一個組件,也就和官方說明的一樣,keep-alive要求同時只有一個子元素被渲染,如果你在其中有 v-for 則不會工作。
const vnode: VNode = getFirstComponentChild(slot)
// 判斷是否存在組件選項,也就是說只對組件有效,對于普通的元素則直接返回對應(yīng)的vnode
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// 檢測include和exclude
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
// 如果指定了子組件的key則使用,否則通過cid+tag生成一個key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
// 判斷是否存在緩存
if (cache[key]) {
// 直接復(fù)用組件實例,并更新key的位置
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
// 此處存儲的vnode還沒有實例,在之后的流程中通過在createComponent中會生成實例
cache[key] = vnode
keys.push(key)
// 當(dāng)緩存數(shù)量大于閾值時,刪除最早的key
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 設(shè)置keepAlive屬性,createComponent中會判斷是否已經(jīng)生成組件實例,如果是且keepAlive為true則會觸發(fā)actived鉤子。
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
keep-alive是一個抽象組件,組件實例中維護(hù)了一份cache,也就是以下代碼部分
created () {
// 存儲組件緩存
this.cache = Object.create(null)
this.keys = []
}
由于路由切換并不會銷毀keep-alive組件,所以緩存是一直存在的(嵌套路由中,子路由外層的keep-alive情況會不一樣,后續(xù)會提到)
繼續(xù)看下keep-alive在緩存的存儲和讀取的具體實現(xiàn),先用一個簡單的demo來描述keep-alive對于組件的緩存以及恢復(fù)緩存的過程
let Foo = {
template: '<div class="foo">foo component</div>',
name: 'Foo'
}
let Bar = {
template: '<div class="bar">bar component</div>',
name: 'Bar'
}
let gvm = new Vue({
el: '#app',
template: `
<div id="#app">
<keep-alive>
<component :is="renderCom"></component>
</keep-alive>
<button @click="change">切換組件</button>
</div>
`,
components: {
Foo,
Bar
},
data: {
renderCom: 'Foo'
},
methods: {
change () {
this.renderCom = this.renderCom === 'Foo' ? 'Bar': 'Foo'
}
}
})
上面例子中,根實例的template會被編譯成如下render函數(shù)
function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"#app"}},[_c('keep-alive',[_c(renderCom,{tag:"component"})],1),_c('button',{on:{"click":change}})],1)}
}
可使用在線編譯:https://cn.vuejs.org/v2/guide/render-function.html#模板編譯
根據(jù)上面的render函數(shù)可以知道,vnode生成的過程是深度遞歸的,先創(chuàng)建子元素的vnode再創(chuàng)建父元素的vnode。
所以首次渲染的時候,在生成keep-alive組件vnode的時候,F(xiàn)oo組件的vnode已經(jīng)生成好了,并且作為keep-alive組件vnode構(gòu)造函數(shù)(_c)的參數(shù)傳入。
_c('keep-alive',[_c(renderCom,{tag:"component"})
生成的keep-alive組件的vnode如下
{
tag: 'vue-component-2-keep-alive',
...
children: undefined,
componentInstance: undefined,
componentOptions: {
Ctor: f VueComponent(options),
children: [Vnode],
listeners: undefined,
propsData: {},
tag: 'keep-alive'
},
context: Vue {...}, // 調(diào)用 $createElement/_c的組件實例, 此處是根組件實例對象
data: {
hook: {
init: f,
prepatch: f,
insert: f,
destroy: f
}
}
}
此處需要注意組件的vnode是沒有children的,而是將原本的children作為vnode的componentOptions的children屬性,componentOptions在組件實例化的時候會被用到,同時在初始化的時候componentOptions.children最終會賦值給vm.$slots,源碼部分如下
// createComponent函數(shù)
function createComponent (Ctor, data, context, children, tag) {
// 此處省略部分代碼
...
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
return vnode
}
Vue最后都會通過patch函數(shù)進(jìn)行渲染,將vnode轉(zhuǎn)換成真實的dom,對于組件則會通過createComponent進(jìn)行渲染
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
接下去分兩步介紹
- keep-alive組件本身的渲染
- keep-alive包裹組件的渲染,本例中的Foo組件和Bar組件
先講講本例中針對keep-alive組件本身的渲染
- 根組件實例化
- 根組件$mount
- 根組件調(diào)用mountComponent
- 根組件生成renderWatcher
- 根組件調(diào)用updateComponent
- 根組件調(diào)用vm.render()生成根組件vnode
- 根組件調(diào)用vm.update(vnode)
- 根組件調(diào)用vm.patch(oldVnode, vnode)
- 根組件調(diào)用createElm(vnode)
- 在children渲染的時候,如果遇到組件類型的vnode則調(diào)用createComponent(vnode),而正是在這個過程中,進(jìn)行了子組件的實例化及掛載($mount)
所以在執(zhí)行createElm(keepAliveVnode)的過程中會對keep-alive組件的實例化及掛載,而在實例化的過程中,keep-alive包裹的子組件的vnode會賦值給keep-alive組件實例的$slot屬性,所以在keep-alive實例調(diào)用render函數(shù)時,可以通過this.$slot拿到包裹組件的vnode,在demo中,就是Foo組件的vnode,具體分析下keep-alive組件的render函數(shù)
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
上面分析到,在執(zhí)行createElm(keepAliveVnode)的過程中,會執(zhí)行keep-alive組件的實例化及掛載($mount),而在掛載的過程中,會執(zhí)行keep-alive的render函數(shù),之前分析過,在render函數(shù)中,可以通過this.$slot獲取到子組件的vnode,從上面源碼中,可以知道,keep-alive只處理默認(rèn)插槽的第一個子組件,言外之意如果在keep-alive中包裹多個組件的話,剩下的組件會被忽略,例如:
<keep-alive> <Foo /> <Bar /> </keep-alive> // 只會渲染Foo組件
繼續(xù)分析,在拿到Foo組件vnode后,判斷了componentOptions,由于我們的Foo是一個組件,所以這里componentOptions是存在的,進(jìn)到if邏輯中,此處include 表示只有匹配的組件會被緩存,而 exclude 表示任何匹配的組件都不會被緩存,demo中并沒有設(shè)置相關(guān)規(guī)則,此處先忽略。
const { cache, keys } = this
cache, keys是在keep-alive組件的create鉤子中生成的,用來存儲被keep-alive緩存的組件的實例以及對應(yīng)vnode的key
created () {
this.cache = Object.create(null)
this.keys = []
}
繼續(xù)下面
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
首先,取出vnode的key,如果vnode.key存在則使用vnode.key,不存在則用componentOptions.Ctor.cid + (componentOptions.tag ?::${componentOptions.tag}: '')作為存儲組件實例的key,據(jù)此可以知道,如果我們不指定組件的key的話,對于相同的組件會匹配到同一個緩存,這也是為什么最開始在描述keep-alive的時候強(qiáng)調(diào)它是一個組件級的緩存方案。
那么首次渲染的時候,cache和keys都是空的,這里就會走else邏輯
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
以key作為cache的健進(jìn)行存儲Foo組件vnode(注意此時vnode上面還沒有componentInstance),這里利用了對象存儲的原理,之后進(jìn)行Foo組件實例化的時候會將其實例賦值給vnode.componentInstance,那么在下次keep-alive組件render的時候就可以獲取到vnode.componentInstance。
所以首次渲染僅僅是在keep-alive的cache上面,存儲了包裹組件Foo的vnode。
針對包裹組件的渲染
上面已經(jīng)講到執(zhí)行了keep-alive的render函數(shù),根據(jù)上面的源碼可以知道,render函數(shù)返回了Foo組件的vnode,那么在keep-alive執(zhí)行patch的時候,會創(chuàng)建Foo組件的實例,然后再進(jìn)行Foo組件的掛載,這個過程與普通組件并沒有區(qū)別,在此不累述。
當(dāng)組件從Foo切換到Bar時
本例中由于renderCom屬性的變化,會觸發(fā)根組件的renderWatcher,之后會執(zhí)行patch(oldVnode, vnode)
在進(jìn)行child vnode比較的時候,keep-alive的新老vnode比較會被判定為sameVnode,之后會進(jìn)入到patchVnode的邏輯
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) {
return
}
// 此處省略代碼
...
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
// 此處省略代碼
...
}
由于我們的keep-alive是組件,所以在vnode創(chuàng)建的時候,會注入一些生命周期鉤子,其中就包含prepatch鉤子,其代碼如下
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
由此可知,keep-alive組件的實例在此次根組件重渲染的過程中會復(fù)用,這也保證了keep-alive組件實例上面之前存儲cache還是存在的
var child = vnode.componentInstance = oldVnode.componentInstance;
下面的updateChildComponent這個函數(shù)非常關(guān)鍵,這個函數(shù)擔(dān)任了Foo組件切換到Bar組件的關(guān)鍵任務(wù)。我們知道,由于keep-alive組件是在此處是復(fù)用的,所以不會再觸發(fā)initRender,所以vm.$slot不會再次更新。所以在updateChildComponent函數(shù)擔(dān)起了slot更新的重任
function updateChildComponent (
vm,
propsData,
listeners,
parentVnode,
renderChildren
) {
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = true;
}
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
var hasChildren = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
);
// ...
// resolve slots + force update if has children
if (hasChildren) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context);
vm.$forceUpdate();
}
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = false;
}
}
updateChildComponent函數(shù)主要更新了當(dāng)前組件實例上的一些屬性,這里包括props,listeners,slots。我們著重講一下slots更新,這里通過resolveSlots獲取到最新的包裹組件的vnode,也就是demo中的Bar組件,之后通過vm.$forceUpdate強(qiáng)制keep-alive組件進(jìn)行重新渲染。(小提示:當(dāng)我們的組件有插槽的時候,該組件的父組件re-render時會觸發(fā)該組件實例$fourceUpdate,這里會有性能損耗,因為不管數(shù)據(jù)變動是否對slot有影響,都會觸發(fā)強(qiáng)制更新,根據(jù)vueConf上尤大的介紹,此問題在3.0會被優(yōu)化),例如
// Home.vue <template> <Artical> <Foo /> </Artical> </tempalte>
此例中當(dāng)Home組件更新的時候,會觸發(fā)Artical組件的強(qiáng)制刷新,而這種刷新是多余的。
繼續(xù),在更新了keep-alive實例的forceUpdate,之后再次進(jìn)入到keep-alive的render函數(shù)中
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
// ...
}
此時render函數(shù)中獲取到vnode就是Bar組件的vnode,接下去的流程和Foo渲染一樣,只不過也是把Bar組件的vnode緩存到keep-alive實例的cache對象中。
當(dāng)組件從Bar再次切換到Foo時
針對keep-alive組件邏輯還是和上面講述的一樣
- 執(zhí)行prepatch
- 復(fù)用keep-alive組件實例
- 執(zhí)行updateChildComponent,更新$slots
- 觸發(fā)vm.$forceUpdate
- 觸發(fā)keep-alive組件render函數(shù)
再次進(jìn)入到render函數(shù),這時候cache[key]就會匹配到Foo組件首次渲染時候緩存的vnode了,看下這部分邏輯
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
由于keep-alive包裹的組件是Foo組件,根據(jù)規(guī)則,此時生成的key和第一此渲染Foo組件時生成的key是一樣的,所以本次keep-alive的render函數(shù)進(jìn)入到了第一個if分支,也就是匹配到了cache[key],把緩存的componentInstance賦值給當(dāng)前vnode,然后更新keys(當(dāng)存在max的時候,能夠保證被刪除的是比較老的緩存)。
很多同學(xué)可能會問,這里設(shè)置vnode.componentInstance會有什么作用。這里涉及到vue的源碼部分。
由于是從Bar組件切換到Foo組件,所以在patch的時候,比對到此處,并不會被判定為sameVnode,所以自然而然走到createElm,由于Foo是Vue組件,所以會進(jìn)入到createComponent,所以最終進(jìn)入到下面函數(shù)片段
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
可以根據(jù)上面對于keep-alive源碼的分析,此處isReactivated為true,接下去會進(jìn)入到vnode生成的時候掛在的生命周期init函數(shù)
var componentVNodeHooks = {
init: function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
},
...
}
此時由于實例已經(jīng)存在,且keepAlive為true,所以會走第一個if邏輯,會執(zhí)行prepatch,更新組件屬性及一些監(jiān)聽器,如果存在插槽的話,還會更新插槽,并執(zhí)行$forceUpdate,此處在前面已經(jīng)分析過,不做累述。
繼續(xù)createComponent,在函數(shù)內(nèi)部會執(zhí)行initComponent和insert
if (isDef(vnode.componentInstance)) {
// 將實例上的dom賦值給vnode
initComponent(vnode, insertedVnodeQueue);
// 插入dom
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
至此,當(dāng)組件從Bar再次切換到Foo時,實例與dom都得到了復(fù)用,達(dá)到一個很高的體驗效果!而我們之后要實現(xiàn)的feb-alive就是基于keep-alive實現(xiàn)的。
參考文檔
vue-navigation
Vue.js 技術(shù)揭秘
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue使用el-table篩選tree樹形結(jié)構(gòu)的數(shù)據(jù)問題
這篇文章主要介紹了vue使用el-table篩選tree樹形結(jié)構(gòu)的數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
解決vue+element 鍵盤回車事件導(dǎo)致頁面刷新的問題
今天小編就為大家分享一篇解決vue+element 鍵盤回車事件導(dǎo)致頁面刷新的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
vue學(xué)習(xí)教程之帶你一步步詳細(xì)解析vue-cli
這篇文章的主題是vue-cli的理解。或許,很多人在開發(fā)vue的時候,我們會發(fā)現(xiàn)一個問題——只會去用,而不明白它的里面的東西?,F(xiàn)在的框架可以說是足夠的優(yōu)秀,讓開發(fā)者不用為搭建開發(fā)環(huán)境而煩惱。但是有時候,我們還是得回到原始生活體驗一下,才能夠讓自己更上層樓。2017-12-12
Vue3實現(xiàn)通過axios來讀取本地json文件
這篇文章主要介紹了Vue3實現(xiàn)通過axios來讀取本地json文件方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
vue axios調(diào)用接口方法報錯500 internal server err
前端使用axios 訪問后端接口時報錯,在瀏覽器中點擊紅色的報錯數(shù)據(jù),本文給大家分享vue axios調(diào)用接口方法報錯500 internal server error的兩種解決方法,感興趣的朋友一起看看吧2023-10-10
用VueJS寫一個Chrome瀏覽器插件的實現(xiàn)方法
這篇文章主要介紹了用VueJS寫一個Chrome瀏覽器插件的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02
vue學(xué)習(xí)筆記之slot插槽基本用法實例分析
這篇文章主要介紹了vue學(xué)習(xí)筆記之slot插槽基本用法,結(jié)合實例形式分析了vue slot插槽基本使用方法與操作注意事項,需要的朋友可以參考下2020-02-02

