이번에는 Vue3의 종속성 주입 및 구성 요소 정의와 관련된 여러 API와 공통 라이브러리 ElementUI Plus 및 Vueuse에서의 사용법을 주로 공유하고 예제를 사용하여 사용 시나리오를 이해합니다.
Vue 3
의 종속성 주입 및 구성 요소 정의에 대해 이야기해 보겠습니다. Vue 3
中依赖注入与组件定义相关的那点事儿。
provide() & inject()
provide()
提供一个值,可以被后代组件注入。
function provide<T>(key: InjectionKey<T> | string, value: T): void
接收两个参数:
- 要注入的
key
,字符串或者Symbol
;
export interface InjectionKey<T> extends Symbol {}
- 对应注入的值
与注册生命周期钩子的 API
类似,provide()
必须在组件的 setup()
阶段同步调用。【相关推荐:vuejs视频教程、web前端开发】
inject()
注入一个由祖先组件或整个应用 (通过 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() & inject() - 官方示例
// 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>
provide() & inject() - ElementUI Plus 示例 Breadcrumb 组件
<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>
provide() & inject() - VueUse 示例
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] }
nextTick()
等待下一次 DOM 更新刷新的工具方法。
function nextTick(callback?: () => void): Promise<void>
说明:当你在 Vue
中更改响应式状态时,最终的 DOM
更新并不是同步生效的,而是由 Vue
将它们缓存在一个队列中,直到下一个“tick”
才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick()
可以在状态改变后立即使用,以等待 DOM
provide() & inject()
provide()할 수 있는 값을 제공하세요. 하위 구성 요소에 의해 주입됩니다. <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>
두 개의 매개변수를 받습니다: 삽입할
-
key
, 문자열 또는 Symbol
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)
}
})
}
......
})
해당 주입된 값
라이프 사이클 후크를 등록하는 API
와 유사하게 provide()
는 에 있어야 합니다. 구성 요소의 setup()
단계는 동기적으로 호출됩니다. [관련 권장사항: vuejs 비디오 튜토리얼, 웹 프론트엔드 개발】
injection 상위 구성 요소 또는 전체 앱에서 제공하는 값입니다( <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>두 개의 매개변수를 받습니다: 삽입할
key
, 문자열 또는 Symbol
API
와 유사하게 provide()
는 에 있어야 합니다. 구성 요소의 setup()
단계는 동기적으로 호출됩니다. [관련 권장사항: vuejs 비디오 튜토리얼, 웹 프론트엔드 개발】
app.provide()
를 통해). 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,
})
})
}
}
}
)
}
- 첫 번째 매개변수는 삽입된
key
입니다. Vue
는 상위 구성 요소 체인을 순회하고 키
를 일치시켜 제공된 값을 결정합니다. 상위 구성 요소 체인의 여러 구성 요소가 동일한 키
에 대한 값을 제공하는 경우 더 가까운 구성 요소가 체인 위쪽에 있는 구성 요소에서 제공한 값을 "덮어씁니다". key
와 일치하는 값이 없는 경우 기본값이 제공되지 않으면 inject()
는 undefine
을 반환합니다.
-
두 번째 매개변수는 선택사항이며, key
입니다. Vue
는 상위 구성 요소 체인을 순회하고 키
를 일치시켜 제공된 값을 결정합니다. 상위 구성 요소 체인의 여러 구성 요소가 동일한 키
에 대한 값을 제공하는 경우 더 가까운 구성 요소가 체인 위쪽에 있는 구성 요소에서 제공한 값을 "덮어씁니다". key
와 일치하는 값이 없는 경우 기본값이 제공되지 않으면 inject()
는 undefine
을 반환합니다. 키
가 일치하지 않을 때 사용되는 기본값입니다. 또한 생성하기 더 복잡한 일부 값을 반환하는 팩토리 함수일 수도 있습니다. 기본값 자체가 함수인 경우 세 번째 매개변수로 false
를 전달하여 해당 함수가 팩토리 함수가 아니라 기본값임을 나타내야 합니다.
provide() & inject() - 공식 예 function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
rrree🎜provide( ) & inject() - ElementUI 플러스 예 🎜🎜Breadcrumb 구성 요소🎜🎜🎜const Foo = defineComponent(/* ... */)
// 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
type FooInstance = InstanceType<typeof Foo>
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
🎜 제공() & 주입() - Vue 사용 예 🎜🎜🎜createInjectionState 소스 코드 🎜 / createInjectionState 사용 🎜🎜🎜package/core/computedInject 소스 코드🎜🎜 export default /*#__PURE__*/ defineComponent(/* ... */)
🎜nextTick()🎜
🎜다음 DOM 업데이트 새로 고침을 기다리는 도구 메서드입니다. 🎜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())
}
},
})
🎜참고: Vue
에서 반응 상태를 변경하면 최종 DOM
업데이트가 동기적으로 적용되지 않고 Vue
에 의해 수행됩니다. > 다음 "tick"
이 함께 실행될 때까지 대기열에 캐시합니다. 이는 상태 변경 횟수에 관계없이 각 구성 요소가 하나의 업데이트만 수행하도록 하기 위한 것입니다. 🎜🎜nextTick()
은 상태 변경 직후 DOM
업데이트가 완료될 때까지 기다리는 데 사용할 수 있습니다. 🎜콜백 함수🎜를 매개변수로 전달하거나 🎜await가 반환한 Promise🎜를 전달할 수 있습니다. 🎜🎜🎜nextTick() 공식 웹사이트 예시🎜🎜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
}
🎜🎜nextTick() - ElementUI Plus 예시🎜🎜🎜🎜ElCascaderPanel 소스 코드🎜🎜<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>
🎜🎜nextTick() - VueUse 예시🎜🎜🎜 🎜InfiniteScroll 소스 코드 사용🎜🎜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
进行操作,避免出现一些不必要的问题,并且可以提高应用程序的性能。
defineComponent()
在定义 Vue
组件时提供类型推导的辅助函数。
function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。
注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX
中用作标签时提供类型推导支持。
const Foo = defineComponent(/* ... */)
// 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
type FooInstance = InstanceType<typeof Foo>
参考:Vue3 - defineComponent 解决了什么?
defineComponent() - ElementUI Plus 示例
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() - Treeshaking
因为 defineComponent()
是一个函数调用,所以它可能被某些构建工具认为会产生副作用,如 webpack
。即使一个组件从未被使用,也有可能不被 tree-shake
。
为了告诉 webpack
这个函数调用可以被安全地 tree-shake
,我们可以在函数调用之前添加一个 /_#**PURE**_/
形式的注释:
export default /*#__PURE__*/ defineComponent(/* ... */)
请注意,如果你的项目中使用的是 Vite
,就不需要这么做,因为 Rollup
(Vite
底层使用的生产环境打包工具) 可以智能地确定 defineComponent()
实际上并没有副作用,所以无需手动注释。
defineComponent() - VueUse 示例
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())
}
},
})
defineAsyncComponent()
定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。
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
}
defineAsyncComponent() - 官网示例
<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
单文件组件。
defineAsyncComponent() - VitePress 示例
<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
来提高应用程序的性能。
defineCustomElement()
这个方法和 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)
使用 Vue 构建自定义元素
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
组件打包成自定义元素。
总之,随着 Web Components
的不断流行和发展,越来越多的库和框架都开始使用 defineCustomElement
来实现跨框架、跨平台的组件共享。
小结
本次我们围绕着 Vue3
中的依赖注入与组件定义相关的几个 API,学习其基本使用方法,并且结合着目前流行的库和框架分析了使用场景,以此来加深我们对它们的认识。
内容收录于github 仓库
const Foo = defineComponent(/* ... */) // 提取出一个组件的实例类型 (与其选项中的 this 的类型等价) type FooInstance = InstanceType<typeof Foo>
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
🎜 제공() & 주입() - Vue 사용 예 🎜🎜🎜createInjectionState 소스 코드 🎜 / createInjectionState 사용 🎜🎜🎜package/core/computedInject 소스 코드🎜🎜 export default /*#__PURE__*/ defineComponent(/* ... */)
🎜nextTick()🎜
🎜다음 DOM 업데이트 새로 고침을 기다리는 도구 메서드입니다. 🎜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())
}
},
})
🎜참고: Vue
에서 반응 상태를 변경하면 최종 DOM
업데이트가 동기적으로 적용되지 않고 Vue
에 의해 수행됩니다. > 다음 "tick"
이 함께 실행될 때까지 대기열에 캐시합니다. 이는 상태 변경 횟수에 관계없이 각 구성 요소가 하나의 업데이트만 수행하도록 하기 위한 것입니다. 🎜🎜nextTick()
은 상태 변경 직후 DOM
업데이트가 완료될 때까지 기다리는 데 사용할 수 있습니다. 🎜콜백 함수🎜를 매개변수로 전달하거나 🎜await가 반환한 Promise🎜를 전달할 수 있습니다. 🎜🎜🎜nextTick() 공식 웹사이트 예시🎜🎜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
}
🎜🎜nextTick() - ElementUI Plus 예시🎜🎜🎜🎜ElCascaderPanel 소스 코드🎜🎜<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>
🎜🎜nextTick() - VueUse 예시🎜🎜🎜 🎜InfiniteScroll 소스 코드 사용🎜🎜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
进行操作,避免出现一些不必要的问题,并且可以提高应用程序的性能。
defineComponent()
在定义 Vue
组件时提供类型推导的辅助函数。
function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。
注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX
中用作标签时提供类型推导支持。
const Foo = defineComponent(/* ... */)
// 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
type FooInstance = InstanceType<typeof Foo>
参考:Vue3 - defineComponent 解决了什么?
defineComponent() - ElementUI Plus 示例
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() - Treeshaking
因为 defineComponent()
是一个函数调用,所以它可能被某些构建工具认为会产生副作用,如 webpack
。即使一个组件从未被使用,也有可能不被 tree-shake
。
为了告诉 webpack
这个函数调用可以被安全地 tree-shake
,我们可以在函数调用之前添加一个 /_#**PURE**_/
形式的注释:
export default /*#__PURE__*/ defineComponent(/* ... */)
请注意,如果你的项目中使用的是 Vite
,就不需要这么做,因为 Rollup
(Vite
底层使用的生产环境打包工具) 可以智能地确定 defineComponent()
实际上并没有副作用,所以无需手动注释。
defineComponent() - VueUse 示例
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())
}
},
})
defineAsyncComponent()
定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。
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
}
defineAsyncComponent() - 官网示例
<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
单文件组件。
defineAsyncComponent() - VitePress 示例
<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
来提高应用程序的性能。
defineCustomElement()
这个方法和 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)
使用 Vue 构建自定义元素
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
组件打包成自定义元素。
总之,随着 Web Components
的不断流行和发展,越来越多的库和框架都开始使用 defineCustomElement
来实现跨框架、跨平台的组件共享。
小结
本次我们围绕着 Vue3
中的依赖注入与组件定义相关的几个 API,学习其基本使用方法,并且结合着目前流行的库和框架分析了使用场景,以此来加深我们对它们的认识。
内容收录于github 仓库
当你需要在修改了某些数据后立即对 DOM
进行操作时,可以使用 nextTick
来确保 DOM
已经更新完毕。例如,在使用 $ref
获取元素时,需要确保元素已经被渲染才能够正确获取。
在一些复杂页面中,有些组件可能会因为条件渲染或动态数据而频繁地变化。使用 nextTick
可以避免频繁地进行 DOM
操作,从而提高应用程序的性能。
当需要在模板中访问某些计算属性或者监听器中的值时,也可以使用 nextTick
来确保这些值已经更新完毕。这样可以避免在视图中访问到旧值。
nextTick
是一个非常有用的 API,可以确保在正确的时机对 DOM
进行操作,避免出现一些不必要的问题,并且可以提高应用程序的性能。Vue
组件时提供类型推导的辅助函数。TSX
中用作标签时提供类型推导支持。defineComponent()
是一个函数调用,所以它可能被某些构建工具认为会产生副作用,如 webpack
。即使一个组件从未被使用,也有可能不被 tree-shake
。webpack
这个函数调用可以被安全地 tree-shake
,我们可以在函数调用之前添加一个 /_#**PURE**_/
形式的注释:Vite
,就不需要这么做,因为 Rollup
(Vite
底层使用的生产环境打包工具) 可以智能地确定 defineComponent()
实际上并没有副作用,所以无需手动注释。ES
模块动态导入也会返回一个 Promise
,所以多数情况下我们会将它和 defineAsyncComponent
搭配使用。类似 Vite
和 Webpack
这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue
单文件组件。defineAsyncComponent()
使用场景:当你需要异步加载某些组件时,可以使用 defineAsyncComponent
来进行组件懒加载,这样可以提高应用程序的性能。
在一些复杂页面中,有些组件可能只有在用户执行特定操作或进入特定页面时才会被使用到。使用 defineAsyncComponent
可以降低初始页面加载时的资源开销。
当你需要动态地加载某些组件时,也可以使用 defineAsyncComponent
。例如,在路由中根据不同的路径加载不同的组件。
Vue3
之外,许多基于 Vue 3
的库和框架也开始使用 defineAsyncComponent
来实现组件的异步加载。例如:Vite
的官方文档工具,使用 defineAsyncComponent
来实现文档页面的异步加载。defineAsyncComponent
。Vue.js
的 UI 框架,从版本 2.0 开始支持 defineAsyncComponent
。Vue 3
的 UI 库,使用 defineAsyncComponent
来实现组件的异步加载。defineAsyncComponent
来提高应用程序的性能。defineComponent
接受的参数相同,不同的是会返回一个原生自定义元素类的构造器。defineCustomElement()
还支持一个特别的选项 styles
,它应该是一个内联 CSS
字符串的数组,所提供的 CSS
会被注入到该元素的 shadow root
上。
返回值是一个可以通过 customElements.define()
注册的自定义元素构造器。Vue 3
之外,一些基于 Vue 3
的库和框架也开始使用 defineCustomElement
来将 Vue
组件打包成自定义元素供其他框架或纯 HTML 页面使用。例如:Web Components
的移动端 UI 框架,从版本 6 开始支持使用 defineCustomElement
将 Ionic
组件打包成自定义元素。Web Components
库,提供类似 Vue
的模板语法,并支持使用 defineCustomElement
将 LitElement
组件打包成自定义元素。Ionic Team
开发的 Web Components
工具链,可以将任何框架的组件转换为自定义元素,并支持使用 defineCustomElement
直接将 Vue
组件打包成自定义元素。Web Components
的不断流行和发展,越来越多的库和框架都开始使用 defineCustomElement
来实现跨框架、跨平台的组件共享。Vue3
中的依赖注入与组件定义相关的几个 API,学习其基本使用方法,并且结合着目前流行的库和框架分析了使用场景,以此来加深我们对它们的认识。위 내용은 Vue3의 종속성 주입 및 구성 요소 정의에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

vue.js는 프론트 엔드 프레임 워크이며 백엔드 프레임 워크는 서버 측 로직을 처리하는 데 사용됩니다. 1) vue.js는 사용자 인터페이스를 구축하는 데 중점을두고 구성 요소화 및 반응 형 데이터 바인딩을 통해 개발을 단순화합니다. 2) Express 및 Django와 같은 백엔드 프레임 워크는 HTTP 요청, 데이터베이스 작업 및 비즈니스 로직을 처리하고 서버에서 실행됩니다.

Vue.js는 개발 효율성과 사용자 경험을 향상시키기 위해 프론트 엔드 기술 스택과 밀접하게 통합되어 있습니다. 1) 건설 도구 : 모듈 식 개발을 달성하기 위해 웹 팩 및 롤업과 통합. 2) 주 관리 : Vuex와 통합하여 복잡한 응용 프로그램 상태를 관리합니다. 3) 라우팅 : vuerouter와 통합하여 단일 페이지 응용 프로그램 라우팅을 실현합니다. 4) CSS 전 처리기 : 스타일 개발 효율성을 향상시키기 위해 SASS를 지원하고 덜 지원합니다.

Netflix는 React의 구성 요소 설계 및 가상 DOM 메커니즘이 복잡한 인터페이스와 빈번한 업데이트를 효율적으로 처리 할 수 있기 때문에 REACT를 선택했습니다. 1) 구성 요소 기반 설계를 통해 Netflix는 인터페이스를 관리 가능한 위젯으로 분류하여 개발 효율성 및 코드 유지 관리를 향상시킬 수 있습니다. 2) 가상 DOM 메커니즘은 DOM 운영을 최소화하여 Netflix 사용자 인터페이스의 부드러움과 고성능을 보장합니다.

Vue.js는 사용하기 쉽고 강력하기 때문에 개발자에게 사랑을받습니다. 1) 반응 형 데이터 바인딩 시스템은 뷰를 자동으로 업데이트합니다. 2) 구성 요소 시스템은 코드의 재사용 성과 유지 관리를 향상시킵니다. 3) 컴퓨팅 속성 및 청취자는 코드의 가독성과 성능을 향상시킵니다. 4) vuedevtools를 사용하고 콘솔 오류를 확인하는 것이 일반적인 디버깅 기술입니다. 5) 성능 최적화에는 주요 속성, 계산 된 속성 및 유지 구성 요소 사용이 포함됩니다. 6) 모범 사례에는 명확한 구성 요소 이름 지정, 단일 파일 구성 요소 사용 및 수명주기 후크의 합리적인 사용이 포함됩니다.

vue.js는 효율적이고 유지 관리 가능한 프론트 엔드 애플리케이션을 구축하는 데 적합한 점진적인 JavaScript 프레임 워크입니다. 주요 기능은 다음과 같습니다. 1. 응답 데이터 바인딩, 2. 구성 요소 개발, 3. 가상 Dom. 이러한 기능을 통해 vue.js는 개발 프로세스를 단순화하고 응용 프로그램 성능과 유지 관리를 향상시켜 현대 웹 개발에서 매우 인기가 있습니다.

vue.js와 반응은 각각 고유 한 장점과 단점이 있으며, 선택은 프로젝트 요구 사항 및 팀 조건에 따라 다릅니다. 1) vue.js는 단순하고 사용하기 쉬운 소규모 프로젝트 및 초보자에게 적합합니다. 2) REACT는 풍부한 생태계와 구성 요소 설계로 인해 대규모 프로젝트 및 복잡한 UI에 적합합니다.

vue.js는 여러 기능을 통해 사용자 경험을 향상시킵니다. 1. 응답 시스템은 실시간 데이터 피드백을 실현합니다. 2. 구성 요소 개발은 코드 재사용 성을 향상시킵니다. 3. Vuerouter는 원활한 내비게이션을 제공합니다. 4. 동적 데이터 바인딩 및 전환 애니메이션은 상호 작용 효과를 향상시킵니다. 5. 오류 처리 메커니즘은 사용자 피드백을 보장합니다. 6. 성능 최적화 및 모범 사례는 응용 프로그램 성능을 향상시킵니다.

웹 개발에서 vue.js의 역할은 개발 프로세스를 단순화하고 효율성을 향상시키는 점진적인 JavaScript 프레임 워크 역할을하는 것입니다. 1) 개발자는 반응 형 데이터 바인딩 및 구성 요소 개발을 통해 비즈니스 로직에 집중할 수 있습니다. 2) vue.js의 작동 원리는 반응 형 시스템 및 가상 DOM에 의존하여 성능을 최적화합니다. 3) 실제 프로젝트에서는 Vuex를 사용하여 글로벌 상태를 관리하고 데이터 대응 성을 최적화하는 것이 일반적입니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

mPDF
mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

Eclipse용 SAP NetWeaver 서버 어댑터
Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음
