Vue源碼探究之狀態(tài)初始化
繼續(xù)隨著核心類的初始化展開探索其他的模塊,這一篇來研究一下Vue的狀態(tài)初始化。這里的狀態(tài)初始化指的就是在創(chuàng)建實(shí)例的時(shí)候,在配置對(duì)象里定義的屬性、數(shù)據(jù)變量、方法等是如何進(jìn)行初始處理的。由于隨后的數(shù)據(jù)更新變動(dòng)都交給觀察系統(tǒng)來負(fù)責(zé),所以在事先弄明白了數(shù)據(jù)綁定的原理之后,就只需要將目光集中在這一部分。
來仔細(xì)看看在核心類中首先執(zhí)行的關(guān)于 state 部分的源碼:
initState
// 定義并導(dǎo)出initState函數(shù),接收參數(shù)vm export function initState (vm: Component) { // 初始化實(shí)例的私有屬性_watchers // 這就是在觀察系統(tǒng)里會(huì)使用到的存儲(chǔ)所有顯式監(jiān)視器的對(duì)象 vm._watchers = [] // 獲取實(shí)例的配置對(duì)象 const opts = vm.$options // 如果定義了props,則初始化props if (opts.props) initProps(vm, opts.props) // 如果定義了methods,則初始化methods if (opts.methods) initMethods(vm, opts.methods) // 如果定義了data,則初始化data if (opts.data) { initData(vm) } else { // 否則初始化實(shí)例的私有屬性_data為空對(duì)象,并開啟觀察 observe(vm._data = {}, true /* asRootData */) } // 如果定義了computed,則初始化計(jì)算屬性 if (opts.computed) initComputed(vm, opts.computed) // 如果定義了watch并且不是nativeWatch,則初始化watch // nativeWatch是火狐瀏覽器下定義的對(duì)象的原型方法 if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
這段代碼非常直白,主要用來執(zhí)行配置對(duì)象里定義的了狀態(tài)的初始化。這里分別有 props、data、methods、computed、watch 五個(gè)配置對(duì)象,分別有各自的初始化方法。在仔細(xì)研究它們的具體實(shí)現(xiàn)之前,先來看一段將在各個(gè)初始化函數(shù)里用到的輔助函數(shù)。
// 定義共享屬性定義描述符對(duì)象sharedPropertyDefinition // 描述符對(duì)象的枚舉和可配置屬性都設(shè)置為true // get、set方法設(shè)置為空函數(shù) const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } // 定義并導(dǎo)出proxy函數(shù),該函數(shù)用來為在目標(biāo)對(duì)象上定義并代理屬性 // 接收目標(biāo)對(duì)象target,路徑鍵名sourceKey,屬性鍵名三個(gè)參數(shù) export function proxy (target: Object, sourceKey: string, key: string) { // 設(shè)置屬性描述符對(duì)象的get方法 sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } // 設(shè)置屬性描述性對(duì)象的set犯法 sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } // 在目標(biāo)對(duì)象上定義屬性 Object.defineProperty(target, key, sharedPropertyDefinition) }
proxy 函數(shù)的定義非常重要,在下面要探究的各個(gè)初始化函數(shù)中它,它會(huì)將我們?cè)谂渲脤?duì)象中設(shè)置的屬性全部定義到實(shí)例對(duì)象中,但是我們對(duì)這些屬性的操作是通過各部分相應(yīng)的代理屬性上來執(zhí)行的。get 和 set 方法的實(shí)現(xiàn)非常明白的表示出這一過程,然后再將屬性定義到實(shí)例中。由這個(gè)函數(shù)作為基礎(chǔ),繼續(xù)來看看其他五個(gè)狀態(tài)的初始化函數(shù)的內(nèi)容。
initProps
// 定義initProps函數(shù),接收vm,propsOptions兩個(gè)參數(shù) function initProps (vm: Component, propsOptions: Object) { // 賦值propsData,propsData是全局?jǐn)U展傳入的賦值對(duì)象 // 在使用extend的時(shí)候會(huì)用到,實(shí)際開發(fā)里運(yùn)用較少 const propsData = vm.$options.propsData || {} // 定義實(shí)例的_props私有屬性,并賦值給props const props = vm._props = {} // 緩存prop鍵,以便將來props更新可以使用Array而不是動(dòng)態(tài)對(duì)象鍵枚舉進(jìn)行迭代。 // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] // 是否是根實(shí)例 const isRoot = !vm.$parent // 對(duì)于非根實(shí)例,關(guān)閉觀察標(biāo)識(shí) // root instance props should be converted if (!isRoot) { toggleObserving(false) } // 遍歷props配置對(duì)象 for (const key in propsOptions) { // 向緩存鍵值數(shù)組中添加鍵名 keys.push(key) // 驗(yàn)證prop的值,validateProp執(zhí)行對(duì)初始化定義的props的類型檢查和默認(rèn)賦值 // 如果有定義類型檢查,布爾值沒有默認(rèn)值時(shí)會(huì)被賦予false,字符串默認(rèn)undefined // 對(duì)propsOptions的比較也是在使用extend擴(kuò)展時(shí)才有意義 // 具體實(shí)現(xiàn)可以參考 src/core/util/props.js,沒有難點(diǎn)這里不詳細(xì)解釋 const value = validateProp(key, propsOptions, propsData, vm) // 非生產(chǎn)環(huán)境下進(jìn)行檢查和提示 /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // 進(jìn)行鍵名的轉(zhuǎn)換,將駝峰式轉(zhuǎn)換成連字符式的鍵名 const hyphenatedKey = hyphenate(key) // 對(duì)與保留變量名沖突的鍵名給予提示 if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } // 對(duì)屬性建立觀察,并在直接使用屬性時(shí)給予警告 defineReactive(props, key, value, () => { if (vm.$parent && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } }) } else { // 非生產(chǎn)環(huán)境下直接對(duì)屬性進(jìn)行存取器包裝,建立依賴觀察 defineReactive(props, key, value) } // 使用Vue.extend()方法擴(kuò)展屬性時(shí),已經(jīng)對(duì)靜態(tài)屬性進(jìn)行了代理 // 這里只需要針對(duì)實(shí)例化時(shí)的屬性執(zhí)行代理操作 // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 當(dāng)實(shí)例上沒有同名屬性時(shí),對(duì)屬性進(jìn)行代理操作 // 將對(duì)鍵名的引用指向vm._props對(duì)象中 if (!(key in vm)) { proxy(vm, `_props`, key) } } // 開啟觀察狀態(tài)標(biāo)識(shí) toggleObserving(true) }
initProps 函數(shù)的最主要內(nèi)容有兩點(diǎn),一是對(duì)定義的數(shù)據(jù)建立觀察,二是對(duì)數(shù)據(jù)進(jìn)行代理,這就是私有變量 _props 的作用,之后獲取和設(shè)置的變量都是作為 _props 的屬性被操作。
另外初始化 props 的過程中有針對(duì) extend 方法會(huì)使用到的 propsData 屬性的初始化。具體使用是在擴(kuò)展對(duì)象時(shí)定義一些 props,然后在創(chuàng)建實(shí)例的過程中傳入 propsData 配置對(duì)象,擴(kuò)展對(duì)象里相應(yīng)的props屬性會(huì)接收 propsData 傳入的值。與在父組件傳入 props 的值類似,只是這里要顯式的通過 propsData 配置對(duì)象來傳入值。
initData
// 定義initData函數(shù) function initData (vm: Component) { // 獲取配置對(duì)象的data屬性 let data = vm.$options.data // 判斷data是否是函數(shù) // 若是函數(shù)則將getData函數(shù)的返回賦值給data和實(shí)例私有屬性_data // 否則直接將data賦值給實(shí)例_data屬性,并在無data時(shí)賦值空對(duì)象 data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // 如果data不是對(duì)象則將data賦值為空對(duì)象 // 進(jìn)一步保證data是對(duì)象類型 if (!isPlainObject(data)) { data = {} // 在非生產(chǎn)環(huán)境下給出警告提示 process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // 實(shí)例對(duì)象代理data // proxy data on instance // 獲取所有data鍵值 const keys = Object.keys(data) // 獲取配置對(duì)象的props const props = vm.$options.props // 獲取配置對(duì)象的methods const methods = vm.$options.methods // 遍歷keys let i = keys.length while (i--) { const key = keys[i] // 非生產(chǎn)環(huán)境給出與methods定義的方法名沖突的警告 if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } // 檢測(cè)是否與props沖突 if (props && hasOwn(props, key)) { // 非生產(chǎn)環(huán)境給出沖突警告 process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) // 沒有與props沖突并且非保留字時(shí),代理鍵名到實(shí)例的_data對(duì)象上 } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // 觀察數(shù)據(jù) // observe data observe(data, true /* asRootData */) } // 定義并導(dǎo)出getData函數(shù),接受函數(shù)類型的data對(duì)象,和Vue實(shí)例對(duì)象 export function getData (data: Function, vm: Component): any { // pushTarget和popTarget是為了解決Vue依賴性檢測(cè)的缺陷可能導(dǎo)致冗余依賴性的問題 // 具體可參閱 https://github.com/vuejs/vue/issues/7573 // 此操作會(huì)設(shè)置Dep.target為undefined,在初始化option時(shí)調(diào)用dep.depend()也不會(huì)建立依賴 // #7573 調(diào)用數(shù)據(jù)getter時(shí)禁用dep集合 // #7573 disable dep collection when invoking data getters pushTarget() // 嘗試在vm上調(diào)用data函數(shù)并返回執(zhí)行結(jié)果 try { return data.call(vm, vm) } catch (e) { // 如果捕獲到錯(cuò)誤則處理錯(cuò)誤,并返回空對(duì)象 handleError(e, vm, `data()`) return {} } finally { popTarget() } }
與 props 的處理類似,initData 函數(shù)的作用也是為了對(duì)數(shù)據(jù)建立觀察的依賴關(guān)系,并且代理數(shù)據(jù)到私有變量 _data 上,另外包括了對(duì) data 與其他配置對(duì)象屬性的鍵名沖突的檢測(cè)。
initComputed
// 設(shè)置computedWatcherOptions對(duì)象 const computedWatcherOptions = { computed: true } // 定義initComputed函數(shù),接受實(shí)例vm,和computed對(duì)象 function initComputed (vm: Component, computed: Object) { // $flow-disable-line // 定義watchers和實(shí)例_computedWatchers屬性,初始賦值空對(duì)象 const watchers = vm._computedWatchers = Object.create(null) // 是否是服務(wù)器渲染,computed屬性在服務(wù)器渲染期間只能是getter // computed properties are just getters during SSR const isSSR = isServerRendering() // 遍歷computed for (const key in computed) { // 獲取用戶定義的值 const userDef = computed[key] // 如果用戶定義的是函數(shù)則賦值給getter否則j將userDef.get方法賦值給getter const getter = typeof userDef === 'function' ? userDef : userDef.get // 非生產(chǎn)環(huán)境拋出缺少計(jì)算屬性錯(cuò)誤警告 if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } // 非服務(wù)器渲染下 if (!isSSR) { // 為計(jì)算屬性創(chuàng)建內(nèi)部監(jiān)視器 // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // 組件定義的內(nèi)部計(jì)算屬性已經(jīng)在組件的原型上定義好了 // 所以這里只要關(guān)注實(shí)例初始化時(shí)用戶定義的計(jì)算屬性 // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. // 鍵名非實(shí)例根屬性時(shí),定義計(jì)算屬性,具體參照defineComputed函數(shù) if (!(key in vm)) { defineComputed(vm, key, userDef) // 非生產(chǎn)環(huán)境下,檢測(cè)與data屬性名的沖突并給出警告 } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } } } // 定義并導(dǎo)出defineComputed哈數(shù) // 接收實(shí)例target,計(jì)算屬性鍵名key,計(jì)算屬性值userDef參數(shù) export function defineComputed ( target: any, key: string, userDef: Object | Function ) { // 在非服務(wù)器渲染下設(shè)置緩存 const shouldCache = !isServerRendering() // 計(jì)算屬性值是函數(shù)時(shí) if (typeof userDef === 'function') { // 設(shè)置計(jì)算屬性的getter,setter為空函數(shù) sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef sharedPropertyDefinition.set = noop } else { // 當(dāng)計(jì)算屬性是對(duì)象時(shí),設(shè)置計(jì)算屬性的getter和setter sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } // 非生產(chǎn)環(huán)境下,如果沒喲定義計(jì)算屬性的setter // 想設(shè)置計(jì)算屬性時(shí)給出警告 if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } // 以重新設(shè)置的屬性描述符為基礎(chǔ)在實(shí)例對(duì)象上定義計(jì)算屬性 Object.defineProperty(target, key, sharedPropertyDefinition) } // 定義createComputedGetter,創(chuàng)建計(jì)算屬性getter // 目的是在非服務(wù)器渲染情況下建立計(jì)算屬性的觀察依賴, // 并根據(jù)其依賴屬性返回計(jì)算后的值 function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { watcher.depend() return watcher.evaluate() } } }
計(jì)算屬性的初始化相對(duì)復(fù)雜一些,首先要對(duì)計(jì)算屬性建立觀察,然后再在實(shí)例上重新定義計(jì)算屬性,并且執(zhí)行屬性代理。由于加入了服務(wù)器渲染的功能,在定義計(jì)算屬性的時(shí)候?qū)κ褂铆h(huán)境做判斷,是非服務(wù)器渲染會(huì)影響到計(jì)算屬性的定義,這是由于服務(wù)器渲染下使用框架時(shí),計(jì)算屬性是不提供 setter 的;另外也要根據(jù)用戶定義的值是函數(shù)或者對(duì)象來對(duì)計(jì)算屬性重新定義 getter 和 setter。從這段代碼里可以看出一個(gè)非常重要的程序,即在獲取計(jì)算屬性的時(shí)候才去計(jì)算它的值,這正是懶加載的實(shí)現(xiàn)。
initMethods
// 定義initMethods方法,接受實(shí)例vm,配置屬性methods function initMethods (vm: Component, methods: Object) { // 獲取實(shí)例的props const props = vm.$options.props // 遍歷methods對(duì)象 for (const key in methods) { // 非生產(chǎn)環(huán)境下給出警告 if (process.env.NODE_ENV !== 'production') { // 未賦值方法警告 if (methods[key] == null) { warn( `Method "${key}" has an undefined value in the component definition. ` + `Did you reference the function correctly?`, vm ) } // 與props屬性名沖突警告 if (props && hasOwn(props, key)) { warn( `Method "${key}" has already been defined as a prop.`, vm ) } // 與保留字沖突警告 if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } // 在實(shí)例上定義方法,賦值為用戶未定義函數(shù)或空函數(shù) vm[key] = methods[key] == null ? noop : bind(methods[key], vm) } }
initMethods 函數(shù)非常簡(jiǎn)單,除了一大段在非生產(chǎn)環(huán)境里報(bào)告檢查沖突的代碼,唯一的內(nèi)容就是在實(shí)例上定義相應(yīng)的方法并且把上下文綁定到實(shí)例對(duì)象上,這樣即便不是使用箭頭函數(shù),在方法內(nèi)也默認(rèn)用 this 指代了實(shí)例對(duì)象。
initWatch
// 定義initWatch函數(shù),接受實(shí)例vm和配置屬性watch function initWatch (vm: Component, watch: Object) { // 遍歷watch for (const key in watch) { // 暫存屬性的值 const handler = watch[key] // 如果handler是數(shù)組 if (Array.isArray(handler)) { // 遍歷數(shù)組為每一個(gè)元素創(chuàng)建相應(yīng)watcher for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { // 竇否則handler應(yīng)該是函數(shù),直接為key創(chuàng)建watcher createWatcher(vm, key, handler) } } } // 定義createWatcher函數(shù) // 接受實(shí)例vm、表達(dá)式或函數(shù)expOrFn,處理器handler,可選的options function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { // 如果handler是對(duì)象 if (isPlainObject(handler)) { // 將handler賦值給options. options = handler // 重新賦值handler handler = handler.handler } // 如果handler是字符串,在實(shí)例上尋找handler并賦值給handler if (typeof handler === 'string') { handler = vm[handler] } // 創(chuàng)建觀察并返回 return vm.$watch(expOrFn, handler, options) }
initWatcher 為傳入的觀察對(duì)象創(chuàng)建監(jiān)視器,比較簡(jiǎn)單。值得注意的是參數(shù)的傳入類型,觀察對(duì)象 expOrFn 可以有兩種方式,一種是字符串,一種是函數(shù),在 Watcher 類中對(duì)此參數(shù)進(jìn)行了檢測(cè),而在初始化的函數(shù)里不對(duì)它做任何處理。handler 對(duì)象也可以接受對(duì)象或字符串類型,在代碼中對(duì)這兩種傳入方式做判斷,最終找到handler引用的函數(shù)傳入 $watch。
stateMixin
探索完了 initState 函數(shù)之后,繼續(xù)來看看 state 混入的方法 stateMixin,在這個(gè)函數(shù)里會(huì)提供上面還未曾提到的 $watch 方法的具體實(shí)現(xiàn):
// 定義并導(dǎo)出stateMixin函數(shù),接收參數(shù)Vue export function stateMixin (Vue: Class<Component>) { // 使用 Object.defineProperty 方法直接聲明定義對(duì)象時(shí),flow會(huì)發(fā)生問題 // 所以必須在此程序化定義對(duì)象 // flow somehow has problems with directly declared definition object // when using Object.defineProperty, so we have to procedurally build up // the object here. // 定義dataDef對(duì)象 const dataDef = {} // 定義dataDef的get方法,返回Vue實(shí)例私有屬性_data dataDef.get = function () { return this._data } // 定義propsDef對(duì)象 const propsDef = {} // 定義propsDef的get方法,返回Vue實(shí)例私有屬性_props propsDef.get = function () { return this._props } // 非生產(chǎn)環(huán)境下,定義dataDef和propsDef的set方法 if (process.env.NODE_ENV !== 'production') { // dataDef的set方法接收Object類型的newData形參 dataDef.set = function (newData: Object) { // 提示避免傳入對(duì)象覆蓋屬性$data // 推薦使用嵌套的數(shù)據(jù)屬性代替 warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } // 設(shè)置propsDef的set方法為只讀 propsDef.set = function () { warn(`$props is readonly.`, this) } } // 定義Vue原型對(duì)象公共屬性$data,并賦值為dataDef Object.defineProperty(Vue.prototype, '$data', dataDef) // 定義Vue原型對(duì)象公共屬性$props,并賦值為propsDef Object.defineProperty(Vue.prototype, '$props', propsDef) // 定義Vue原型對(duì)象的$set方法,并賦值為從觀察者導(dǎo)入的set函數(shù) Vue.prototype.$set = set // 定義Vue原型對(duì)象的$delete方法,并賦值為從觀察者導(dǎo)入的del函數(shù) Vue.prototype.$delete = del // 定義Vue原型對(duì)象的$watch方法 // 接收字符串或函數(shù)類型的expOrFn,從命名中可看出希望為表達(dá)式或函數(shù) // 接收任何類型的cb,這里希望為回調(diào)函數(shù)或者是一個(gè)對(duì)象 // 接收對(duì)象類型的options // 要求返回函數(shù)類型 Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { // 把實(shí)例賦值給vm變量,類型需為Component const vm: Component = this // 如果cb是純粹的對(duì)象類型 if (isPlainObject(cb)) { // 返回createWatcher函數(shù) return createWatcher(vm, expOrFn, cb, options) } // 定義觀察目標(biāo)的options,大多數(shù)情況下為undefined options = options || {} // 定義options的user屬性值為true,標(biāo)識(shí)為用戶定義 options.user = true // 創(chuàng)建watcher實(shí)例 const watcher = new Watcher(vm, expOrFn, cb, options) // 如果options的immediate為真 if (options.immediate) { // 在vm上調(diào)用cb回調(diào)函數(shù),并傳入watcher.value作為參數(shù) cb.call(vm, watcher.value) } // 返回unwatchFn函數(shù) return function unwatchFn () { // 執(zhí)行watcher.teardown()方法清除觀察 watcher.teardown() } } }
stateMixin執(zhí)行的是關(guān)于狀態(tài)觀察的一系列方法的混入,主要是三個(gè)方面:
- 定義實(shí)例 $data 和 $props 屬性的存取器
- 定義實(shí)例的 $set、$delete 方法,具體實(shí)在定義在觀察者模塊中
- 定義實(shí)例的 $watch 方法
到這里,關(guān)于狀態(tài)初始化的部分就探索完畢了,接下來要繼續(xù)研究另一個(gè)與開發(fā)過程緊密關(guān)聯(lián)的部分——虛擬節(jié)點(diǎn)和模板渲染。
狀態(tài)初始化是與我們?cè)陂_發(fā)的時(shí)候最息息相關(guān)的部分,在創(chuàng)建實(shí)例對(duì)象的配置對(duì)象中,我們?cè)O(shè)置了這些屬性和方法,實(shí)例初始化的過程中對(duì)這些傳入的配置進(jìn)行了很多預(yù)先的處理,這就是狀態(tài)初始化背后的邏輯。在探索到這一部分的時(shí)候才真正的感到,終于與平時(shí)的開發(fā)關(guān)聯(lián)起來了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue?watch中監(jiān)聽值的變化,判斷后修改值方式
這篇文章主要介紹了Vue?watch中監(jiān)聽值的變化,判斷后修改值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Vuejs 組件——props數(shù)據(jù)傳遞的實(shí)例代碼
本篇文章主要介紹了Vuejs 組件——props數(shù)據(jù)傳遞的實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03關(guān)于vuex狀態(tài)刷新網(wǎng)頁時(shí)數(shù)據(jù)被清空問題及解決
這篇文章主要介紹了關(guān)于vuex狀態(tài)刷新網(wǎng)頁時(shí)數(shù)據(jù)被清空問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07詳解Vue2和Vue3的區(qū)別以及其鉤子函數(shù)的使用
Vue.js?3?和?Vue.js?2?是兩個(gè)主要版本的流行前端框架,它們之間有很多區(qū)別,包括性能優(yōu)化、新特性和改進(jìn)的API等,下面就跟隨小編一起來看看他們的使用區(qū)別吧2024-01-01Vue.js使用$.ajax和vue-resource實(shí)現(xiàn)OAuth的注冊(cè)、登錄、注銷和API調(diào)用
這篇文章主要介紹了 Vue.js使用$.ajax和vue-resource實(shí)現(xiàn)OAuth的注冊(cè)、登錄、注銷和API調(diào)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05vue中報(bào)錯(cuò)“error‘xxx‘?is?defined?but?never?used”問題及解決
介紹了兩種解決代碼導(dǎo)入問題的方法:?jiǎn)我淮a解決和全局解決,第一種方法是在代碼前面添加特定代碼并保存;第二種方法是在package.json中添加代碼后重啟項(xiàng)目,這些方法可以有效解決導(dǎo)包錯(cuò)誤提示,希望對(duì)大家有幫助2024-10-10