您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Vue3中的依賴注入與組件定義怎么實現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Vue3中的依賴注入與組件定義怎么實現(xiàn)”吧!
提供一個值,可以被后代組件注入。
function provide<T>(key: InjectionKey<T> | string, value: T): void
接收兩個參數(shù):
要注入的 key
,字符串或者 Symbol
;
export interface InjectionKey<T> extends Symbol {}
對應(yīng)注入的值
與注冊生命周期鉤子的 API
類似,provide()
必須在組件的 setup()
階段同步調(diào)用。
注入一個由祖先組件或整個應(yīng)用 (通過 app.provide()
) 提供的值。
// 沒有默認(rèn)值
function inject<T>(key: InjectionKey<T> | string): T | undefined
// 帶有默認(rèn)值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
// 使用工廠函數(shù)
function inject<T>(
key: InjectionKey<T> | string,
defaultValue: () => T,
treatDefaultAsFactory: true
): T
第一個參數(shù)是注入的 key
。Vue
會遍歷父組件鏈,通過匹配 key
來確定所提供的值。如果父組件鏈上多個組件對同一個 key
提供了值,那么離得更近的組件將會“覆蓋”鏈上更遠(yuǎn)的組件所提供的值。如果沒有能通過 key
匹配到值,inject()
將返回 undefined
,除非提供了一個默認(rèn)值。
第二個參數(shù)是可選的,即在沒有匹配到 key
時使用的默認(rèn)值。它也可以是一個工廠函數(shù),用來返回某些創(chuàng)建起來比較復(fù)雜的值。如果默認(rèn)值本身就是一個函數(shù),那么你必須將 false
作為第三個參數(shù)傳入,表明這個函數(shù)就是默認(rèn)值,而不是一個工廠函數(shù)。
// provide
<script setup>
import {(ref, provide)} from 'vue' import {fooSymbol} from
'./injectionSymbols' // 提供靜態(tài)值 provide('foo', 'bar') // 提供響應(yīng)式的值
const count = ref(0) provide('count', count) // 提供時將 Symbol 作為 key
provide(fooSymbol, count)
</script>
// inject
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默認(rèn)方式
const foo = inject('foo')
// 注入響應(yīng)式的值
const count = inject('count')
// 通過 Symbol 類型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一個值,若為空則使用提供的默認(rèn)值
const bar = inject('foo', 'default value')
// 注入一個值,若為空則使用提供的工廠函數(shù)
const baz = inject('foo', () => new Map())
// 注入時為了表明提供的默認(rèn)值是個函數(shù),需要傳入第三個參數(shù)
const fn = inject('function', () => {}, false)
</script>
<script setup>
import { onMounted, provide, ref } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { breadcrumbKey } from './constants'
import { breadcrumbProps } from './breadcrumb'
defineOptions({
name: 'ElBreadcrumb',
})
const props = defineProps(breadcrumbProps)
const ns = useNamespace('breadcrumb')
const breadcrumb = ref<HTMLDivElement>()
// 提供值
provide(breadcrumbKey, props)
onMounted(() => {
......
})
</script>
<script setup>
import { getCurrentInstance, inject, ref, toRefs } from 'vue'
import ElIcon from '@element-plus/components/icon'
import { useNamespace } from '@element-plus/hooks'
import { breadcrumbKey } from './constants'
import { breadcrumbItemProps } from './breadcrumb-item'
import type { Router } from 'vue-router'
defineOptions({
name: 'ElBreadcrumbItem',
})
const props = defineProps(breadcrumbItemProps)
const instance = getCurrentInstance()!
// 注入值
const breadcrumbContext = inject(breadcrumbKey, undefined)!
const ns = useNamespace('breadcrumb')
......
</script>
createInjectionState 源碼 / createInjectionState 使用
package/core/computedInject 源碼
import { type InjectionKey, inject, provide } from 'vue-demi'
/**
* 創(chuàng)建可以注入到組件中的全局狀態(tài)
*/
export function createInjectionState<Arguments extends Array<any>, Return>(
composable: (...args: Arguments) => Return
): readonly [
useProvidingState: (...args: Arguments) => Return,
useInjectedState: () => Return | undefined
] {
const key: string | InjectionKey<Return> = Symbol('InjectionState')
const useProvidingState = (...args: Arguments) => {
const state = composable(...args)
provide(key, state)
return state
}
const useInjectedState = () => inject(key)
return [useProvidingState, useInjectedState]
}
等待下一次 DOM 更新刷新的工具方法。
function nextTick(callback?: () => void): Promise<void>
說明:當(dāng)你在 Vue
中更改響應(yīng)式狀態(tài)時,最終的 DOM
更新并不是同步生效的,而是由 Vue
將它們緩存在一個隊列中,直到下一個“tick”
才一起執(zhí)行。這樣是為了確保每個組件無論發(fā)生多少狀態(tài)改變,都僅執(zhí)行一次更新。
nextTick()
可以在狀態(tài)改變后立即使用,以等待 DOM
更新完成。你可以傳遞一個回調(diào)函數(shù)作為參數(shù),或者 await 返回的 Promise。
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
// DOM 還未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此時已經(jīng)更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
ElCascaderPanel 源碼
export default defineComponent({
......
const syncMenuState = (
newCheckedNodes: CascaderNode[],
reserveExpandingState = true
) => {
......
checkedNodes.value = newNodes
nextTick(scrollToExpandingNode)
}
const scrollToExpandingNode = () => {
if (!isClient) return
menuList.value.forEach((menu) => {
const menuElement = menu?.$el
if (menuElement) {
const container = menuElement.querySelector(`.${ns.namespace.value}-scrollbar__wrap`)
const activeNode = menuElement.querySelector(`.${ns.b('node')}.${ns.is('active')}`) ||
menuElement.querySelector(`.${ns.b('node')}.in-active-path`)
scrollIntoView(container, activeNode)
}
})
}
......
})
useInfiniteScroll 源碼
export function useInfiniteScroll(
element: MaybeComputedRef<HTMLElement | SVGElement | Window | Document | null | undefined>
......
) {
const state = reactive(......)
watch(
() => state.arrivedState[direction],
async (v) => {
if (v) {
const elem = resolveUnref(element) as Element
......
if (options.preserveScrollPosition && elem) {
nextTick(() => {
elem.scrollTo({
top: elem.scrollHeight - previous.height,
left: elem.scrollWidth - previous.width,
})
})
}
}
}
)
}
當(dāng)你需要在修改了某些數(shù)據(jù)后立即對 DOM
進(jìn)行操作時,可以使用 nextTick
來確保 DOM
已經(jīng)更新完畢。例如,在使用 $ref
獲取元素時,需要確保元素已經(jīng)被渲染才能夠正確獲取。
在一些復(fù)雜頁面中,有些組件可能會因為條件渲染或動態(tài)數(shù)據(jù)而頻繁地變化。使用 nextTick
可以避免頻繁地進(jìn)行 DOM
操作,從而提高應(yīng)用程序的性能。
當(dāng)需要在模板中訪問某些計算屬性或者監(jiān)聽器中的值時,也可以使用 nextTick
來確保這些值已經(jīng)更新完畢。這樣可以避免在視圖中訪問到舊值。
總之,nextTick
是一個非常有用的 API,可以確保在正確的時機(jī)對 DOM
進(jìn)行操作,避免出現(xiàn)一些不必要的問題,并且可以提高應(yīng)用程序的性能。
在定義 Vue
組件時提供類型推導(dǎo)的輔助函數(shù)。
function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
第一個參數(shù)是一個組件選項對象。返回值將是該選項對象本身,因為該函數(shù)實際上在運(yùn)行時沒有任何操作,僅用于提供類型推導(dǎo)。
注意返回值的類型有一點特別:它會是一個構(gòu)造函數(shù)類型,它的實例類型是根據(jù)選項推斷出的組件實例類型。這是為了能讓該返回值在 TSX
中用作標(biāo)簽時提供類型推導(dǎo)支持。
const Foo = defineComponent(/* ... */)
// 提取出一個組件的實例類型 (與其選項中的 this 的類型等價)
type FooInstance = InstanceType<typeof Foo>
參考:Vue3 - defineComponent 解決了什么?
ConfigProvider 源碼
import { defineComponent, renderSlot, watch } from 'vue'
import { provideGlobalConfig } from './hooks/use-global-config'
import { configProviderProps } from './config-provider-props'
......
const ConfigProvider = defineComponent({
name: 'ElConfigProvider',
props: configProviderProps,
setup(props, { slots }) {
......
},
})
export type ConfigProviderInstance = InstanceType<typeof ConfigProvider>
export default ConfigProvider
因為 defineComponent()
是一個函數(shù)調(diào)用,所以它可能被某些構(gòu)建工具認(rèn)為會產(chǎn)生副作用,如 webpack
。即使一個組件從未被使用,也有可能不被 tree-shake
。
為了告訴 webpack
這個函數(shù)調(diào)用可以被安全地 tree-shake
,我們可以在函數(shù)調(diào)用之前添加一個 /_#**PURE**_/
形式的注釋:
export default /*#__PURE__*/ defineComponent(/* ... */)
請注意,如果你的項目中使用的是 Vite
,就不需要這么做,因為 Rollup
(Vite
底層使用的生產(chǎn)環(huán)境打包工具) 可以智能地確定 defineComponent()
實際上并沒有副作用,所以無需手動注釋。
OnClickOutside 源碼
import { defineComponent, h, ref } from 'vue-demi'
import { onClickOutside } from '@vueuse/core'
import type { RenderableComponent } from '../types'
import type { OnClickOutsideOptions } from '.'
export interface OnClickOutsideProps extends RenderableComponent {
options?: OnClickOutsideOptions
}
export const OnClickOutside = /* #__PURE__ */ defineComponent<OnClickOutsideProps>({
name: 'OnClickOutside',
props: ['as', 'options'] as unknown as undefined,
emits: ['trigger'],
setup(props, { slots, emit }) {
... ...
return () => {
if (slots.default)
return h(props.as || 'div', { ref: target }, slots.default())
}
},
})
定義一個異步組件,它在運(yùn)行時是懶加載的。參數(shù)可以是一個異步加載函數(shù),或是對加載行為進(jìn)行更具體定制的一個選項對象。
function defineAsyncComponent(
source: AsyncComponentLoader | AsyncComponentOptions
): Component
type AsyncComponentLoader = () => Promise<Component>
interface AsyncComponentOptions {
loader: AsyncComponentLoader
loadingComponent?: Component
errorComponent?: Component
delay?: number
timeout?: number
suspensible?: boolean
onError?: (
error: Error,
retry: () => void,
fail: () => void,
attempts: number
) => any
}
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(/* 從服務(wù)器獲取到的組件 */)
})
})
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AsyncComp />
<AdminPage />
</template>
ES
模塊動態(tài)導(dǎo)入也會返回一個 Promise
,所以多數(shù)情況下我們會將它和 defineAsyncComponent
搭配使用。類似 Vite
和 Webpack
這樣的構(gòu)建工具也支持此語法 (并且會將它們作為打包時的代碼分割點),因此我們也可以用它來導(dǎo)入 Vue
單文件組件。
<script setup>
import { defineAsyncComponent } from 'vue'
import type { DefaultTheme } from 'vitepress/theme'
defineProps<{ carbonAds: DefaultTheme.CarbonAdsOptions }>()
const VPCarbonAds = __CARBON__
? defineAsyncComponent(() => import('./VPCarbonAds.vue'))
: () => null
</script>
<template>
<div>
<VPCarbonAds :carbon-ads="carbonAds" />
</div>
</template>
defineAsyncComponent()
使用場景:
當(dāng)你需要異步加載某些組件時,可以使用 defineAsyncComponent
來進(jìn)行組件懶加載,這樣可以提高應(yīng)用程序的性能。
在一些復(fù)雜頁面中,有些組件可能只有在用戶執(zhí)行特定操作或進(jìn)入特定頁面時才會被使用到。使用 defineAsyncComponent
可以降低初始頁面加載時的資源開銷。
當(dāng)你需要動態(tài)地加載某些組件時,也可以使用 defineAsyncComponent
。例如,在路由中根據(jù)不同的路徑加載不同的組件。
除 Vue3
之外,許多基于 Vue 3
的庫和框架也開始使用 defineAsyncComponent
來實現(xiàn)組件的異步加載。例如:
VitePress: Vite
的官方文檔工具,使用 defineAsyncComponent
來實現(xiàn)文檔頁面的異步加載。
Nuxt.js: 基于 Vue.js 的靜態(tài)網(wǎng)站生成器,從版本 2.15 開始支持 defineAsyncComponent
。
Quasar Framework: 基于 Vue.js
的 UI 框架,從版本 2.0 開始支持 defineAsyncComponent
。
Element UI Plus: 基于 Vue 3
的 UI 庫,使用 defineAsyncComponent
來實現(xiàn)組件的異步加載。
總之,隨著 Vue 3 的普及,越來越多的庫和框架都開始使用 defineAsyncComponent
來提高應(yīng)用程序的性能。
這個方法和 defineComponent
接受的參數(shù)相同,不同的是會返回一個原生自定義元素類的構(gòu)造器。
function defineCustomElement(
component:
| (ComponentOptions & { styles?: string[] })
| ComponentOptions['setup']
): {
new (props?: object): HTMLElement
}
除了常規(guī)的組件選項,defineCustomElement()
還支持一個特別的選項 styles
,它應(yīng)該是一個內(nèi)聯(lián) CSS
字符串的數(shù)組,所提供的 CSS
會被注入到該元素的 shadow root
上。
返回值是一個可以通過 customElements.define()
注冊的自定義元素構(gòu)造器。
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
/* 組件選項 */
})
// 注冊自定義元素
customElements.define('my-vue-element', MyVueElement)
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// 這里是同平常一樣的 Vue 組件選項
props: {},
emits: {},
template: `...`,
// defineCustomElement 特有的:注入進(jìn) shadow root 的 CSS
styles: [`/* inlined css */`],
})
// 注冊自定義元素
// 注冊之后,所有此頁面中的 `<my-vue-element>` 標(biāo)簽
// 都會被升級
customElements.define('my-vue-element', MyVueElement)
// 你也可以編程式地實例化元素:
// (必須在注冊之后)
document.body.appendChild(
new MyVueElement({
// 初始化 props(可選)
})
)
// 組件使用
<my-vue-element></my-vue-element>
除了 Vue 3
之外,一些基于 Vue 3
的庫和框架也開始使用 defineCustomElement
來將 Vue
組件打包成自定義元素供其他框架或純 HTML 頁面使用。例如:
Ionic Framework: 基于 Web Components
的移動端 UI 框架,從版本 6 開始支持使用 defineCustomElement
將 Ionic
組件打包成自定義元素。
LitElement: Google 推出的 Web Components
庫,提供類似 Vue
的模板語法,并支持使用 defineCustomElement
將 LitElement
組件打包成自定義元素。
Stencil: 由 Ionic Team
開發(fā)的 Web Components
工具鏈,可以將任何框架的組件轉(zhuǎn)換為自定義元素,并支持使用 defineCustomElement
直接將 Vue
組件打包成自定義元素。
到此,相信大家對“Vue3中的依賴注入與組件定義怎么實現(xiàn)”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。