您好,登錄后才能下訂單哦!
本篇內容主要講解“Vue3中的依賴注入與組件定義怎么實現”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue3中的依賴注入與組件定義怎么實現”吧!
提供一個值,可以被后代組件注入。
function provide<T>(key: InjectionKey<T> | string, value: T): void
接收兩個參數:
要注入的 key
,字符串或者 Symbol
;
export interface InjectionKey<T> extends Symbol {}
對應注入的值
與注冊生命周期鉤子的 API
類似,provide()
必須在組件的 setup()
階段同步調用。
注入一個由祖先組件或整個應用 (通過 app.provide()
) 提供的值。
// 沒有默認值
function inject<T>(key: InjectionKey<T> | string): T | undefined
// 帶有默認值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
// 使用工廠函數
function inject<T>(
key: InjectionKey<T> | string,
defaultValue: () => T,
treatDefaultAsFactory: true
): T
第一個參數是注入的 key
。Vue
會遍歷父組件鏈,通過匹配 key
來確定所提供的值。如果父組件鏈上多個組件對同一個 key
提供了值,那么離得更近的組件將會“覆蓋”鏈上更遠的組件所提供的值。如果沒有能通過 key
匹配到值,inject()
將返回 undefined
,除非提供了一個默認值。
第二個參數是可選的,即在沒有匹配到 key
時使用的默認值。它也可以是一個工廠函數,用來返回某些創建起來比較復雜的值。如果默認值本身就是一個函數,那么你必須將 false
作為第三個參數傳入,表明這個函數就是默認值,而不是一個工廠函數。
// provide
<script setup>
import {(ref, provide)} from 'vue' import {fooSymbol} from
'./injectionSymbols' // 提供靜態值 provide('foo', 'bar') // 提供響應式的值
const count = ref(0) provide('count', count) // 提供時將 Symbol 作為 key
provide(fooSymbol, count)
</script>
// inject
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默認方式
const foo = inject('foo')
// 注入響應式的值
const count = inject('count')
// 通過 Symbol 類型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一個值,若為空則使用提供的默認值
const bar = inject('foo', 'default value')
// 注入一個值,若為空則使用提供的工廠函數
const baz = inject('foo', () => new Map())
// 注入時為了表明提供的默認值是個函數,需要傳入第三個參數
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'
/**
* 創建可以注入到組件中的全局狀態
*/
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>
說明:當你在 Vue
中更改響應式狀態時,最終的 DOM
更新并不是同步生效的,而是由 Vue
將它們緩存在一個隊列中,直到下一個“tick”
才一起執行。這樣是為了確保每個組件無論發生多少狀態改變,都僅執行一次更新。
nextTick()
可以在狀態改變后立即使用,以等待 DOM
更新完成。你可以傳遞一個回調函數作為參數,或者 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 此時已經更新
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,
})
})
}
}
}
)
}
當你需要在修改了某些數據后立即對 DOM
進行操作時,可以使用 nextTick
來確保 DOM
已經更新完畢。例如,在使用 $ref
獲取元素時,需要確保元素已經被渲染才能夠正確獲取。
在一些復雜頁面中,有些組件可能會因為條件渲染或動態數據而頻繁地變化。使用 nextTick
可以避免頻繁地進行 DOM
操作,從而提高應用程序的性能。
當需要在模板中訪問某些計算屬性或者監聽器中的值時,也可以使用 nextTick
來確保這些值已經更新完畢。這樣可以避免在視圖中訪問到舊值。
總之,nextTick
是一個非常有用的 API,可以確保在正確的時機對 DOM
進行操作,避免出現一些不必要的問題,并且可以提高應用程序的性能。
在定義 Vue
組件時提供類型推導的輔助函數。
function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
第一個參數是一個組件選項對象。返回值將是該選項對象本身,因為該函數實際上在運行時沒有任何操作,僅用于提供類型推導。
注意返回值的類型有一點特別:它會是一個構造函數類型,它的實例類型是根據選項推斷出的組件實例類型。這是為了能讓該返回值在 TSX
中用作標簽時提供類型推導支持。
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()
是一個函數調用,所以它可能被某些構建工具認為會產生副作用,如 webpack
。即使一個組件從未被使用,也有可能不被 tree-shake
。
為了告訴 webpack
這個函數調用可以被安全地 tree-shake
,我們可以在函數調用之前添加一個 /_#**PURE**_/
形式的注釋:
export default /*#__PURE__*/ defineComponent(/* ... */)
請注意,如果你的項目中使用的是 Vite
,就不需要這么做,因為 Rollup
(Vite
底層使用的生產環境打包工具) 可以智能地確定 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())
}
},
})
定義一個異步組件,它在運行時是懶加載的。參數可以是一個異步加載函數,或是對加載行為進行更具體定制的一個選項對象。
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(/* 從服務器獲取到的組件 */)
})
})
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AsyncComp />
<AdminPage />
</template>
ES
模塊動態導入也會返回一個 Promise
,所以多數情況下我們會將它和 defineAsyncComponent
搭配使用。類似 Vite
和 Webpack
這樣的構建工具也支持此語法 (并且會將它們作為打包時的代碼分割點),因此我們也可以用它來導入 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()
使用場景:
當你需要異步加載某些組件時,可以使用 defineAsyncComponent
來進行組件懶加載,這樣可以提高應用程序的性能。
在一些復雜頁面中,有些組件可能只有在用戶執行特定操作或進入特定頁面時才會被使用到。使用 defineAsyncComponent
可以降低初始頁面加載時的資源開銷。
當你需要動態地加載某些組件時,也可以使用 defineAsyncComponent
。例如,在路由中根據不同的路徑加載不同的組件。
除 Vue3
之外,許多基于 Vue 3
的庫和框架也開始使用 defineAsyncComponent
來實現組件的異步加載。例如:
VitePress: Vite
的官方文檔工具,使用 defineAsyncComponent
來實現文檔頁面的異步加載。
Nuxt.js: 基于 Vue.js 的靜態網站生成器,從版本 2.15 開始支持 defineAsyncComponent
。
Quasar Framework: 基于 Vue.js
的 UI 框架,從版本 2.0 開始支持 defineAsyncComponent
。
Element UI Plus: 基于 Vue 3
的 UI 庫,使用 defineAsyncComponent
來實現組件的異步加載。
總之,隨著 Vue 3 的普及,越來越多的庫和框架都開始使用 defineAsyncComponent
來提高應用程序的性能。
這個方法和 defineComponent
接受的參數相同,不同的是會返回一個原生自定義元素類的構造器。
function defineCustomElement(
component:
| (ComponentOptions & { styles?: string[] })
| ComponentOptions['setup']
): {
new (props?: object): HTMLElement
}
除了常規的組件選項,defineCustomElement()
還支持一個特別的選項 styles
,它應該是一個內聯 CSS
字符串的數組,所提供的 CSS
會被注入到該元素的 shadow root
上。
返回值是一個可以通過 customElements.define()
注冊的自定義元素構造器。
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 特有的:注入進 shadow root 的 CSS
styles: [`/* inlined css */`],
})
// 注冊自定義元素
// 注冊之后,所有此頁面中的 `<my-vue-element>` 標簽
// 都會被升級
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
開發的 Web Components
工具鏈,可以將任何框架的組件轉換為自定義元素,并支持使用 defineCustomElement
直接將 Vue
組件打包成自定義元素。
到此,相信大家對“Vue3中的依賴注入與組件定義怎么實現”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。