搜索
首页web前端Vue.js聊聊Vue3中的依赖注入与组件定义

本次主要分享Vue3中依赖注入以及组件定义相关的几个API,以及在常用库ElementUI Plus和Vueuse中的使用情况,通过示例来理解使用场景。

聊聊Vue3中的依赖注入与组件定义

让我们聊聊 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
  • 第一个参数是注入的 keyVue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。

  • 第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。

provide() & inject() - 官方示例

// provide
<script setup>
  import {(ref, provide)} from &#39;vue&#39; import {fooSymbol} from
  &#39;./injectionSymbols&#39; // 提供静态值 provide(&#39;foo&#39;, &#39;bar&#39;) // 提供响应式的值
  const count = ref(0) provide(&#39;count&#39;, count) // 提供时将 Symbol 作为 key
  provide(fooSymbol, count)
</script>
// inject
<script setup>
import { inject } from &#39;vue&#39;
import { fooSymbol } from &#39;./injectionSymbols&#39;

// 注入值的默认方式
const foo = inject(&#39;foo&#39;)

// 注入响应式的值
const count = inject(&#39;count&#39;)

// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)

// 注入一个值,若为空则使用提供的默认值
const bar = inject(&#39;foo&#39;, &#39;default value&#39;)

// 注入一个值,若为空则使用提供的工厂函数
const baz = inject(&#39;foo&#39;, () => new Map())

// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject(&#39;function&#39;, () => {}, false)
</script>

provide() & inject() - ElementUI Plus 示例 Breadcrumb 组件

<script setup>
import { onMounted, provide, ref } from &#39;vue&#39;
import { useNamespace } from &#39;@element-plus/hooks&#39;
import { breadcrumbKey } from &#39;./constants&#39;
import { breadcrumbProps } from &#39;./breadcrumb&#39;

defineOptions({
  name: &#39;ElBreadcrumb&#39;,
})

const props = defineProps(breadcrumbProps)
const ns = useNamespace(&#39;breadcrumb&#39;)
const breadcrumb = ref<HTMLDivElement>()
// 提供值
provide(breadcrumbKey, props)

onMounted(() => {
  ......
})
</script>
<script setup>
import { getCurrentInstance, inject, ref, toRefs } from &#39;vue&#39;
import ElIcon from &#39;@element-plus/components/icon&#39;
import { useNamespace } from &#39;@element-plus/hooks&#39;
import { breadcrumbKey } from &#39;./constants&#39;
import { breadcrumbItemProps } from &#39;./breadcrumb-item&#39;

import type { Router } from &#39;vue-router&#39;

defineOptions({
  name: &#39;ElBreadcrumbItem&#39;,
})

const props = defineProps(breadcrumbItemProps)

const instance = getCurrentInstance()!
// 注入值
const breadcrumbContext = inject(breadcrumbKey, undefined)!
const ns = useNamespace(&#39;breadcrumb&#39;)
 ......
</script>

provide() & inject() - VueUse 示例

createInjectionState 源码 / createInjectionState 使用

package/core/computedInject 源码

import { type InjectionKey, inject, provide } from &#39;vue-demi&#39;

/**
 * 创建可以注入到组件中的全局状态
 */
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(&#39;InjectionState&#39;)
  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 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise

nextTick() 官网示例

<script setup>
import { ref, nextTick } from &#39;vue&#39;

const count = ref(0)

async function increment() {
  count.value++

  // DOM 还未更新
  console.log(document.getElementById(&#39;counter&#39;).textContent) // 0

  await nextTick()
  // DOM 此时已经更新
  console.log(document.getElementById(&#39;counter&#39;).textContent) // 1
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

nextTick() - ElementUI Plus 示例

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(&#39;node&#39;)}.${ns.is(&#39;active&#39;)}`) ||
          menuElement.querySelector(`.${ns.b(&#39;node&#39;)}.in-active-path`)
        scrollIntoView(container, activeNode)
      }
    })
  }
  ......
})

nextTick() - VueUse 示例

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 进行操作,避免出现一些不必要的问题,并且可以提高应用程序的性能。

defineComponent()

在定义 Vue 组件时提供类型推导的辅助函数。

function defineComponent(
  component: ComponentOptions | ComponentOptions[&#39;setup&#39;]
): ComponentConstructor

第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。

注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX 中用作标签时提供类型推导支持。

const Foo = defineComponent(/* ... */)
// 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
type FooInstance = InstanceType<typeof Foo>

参考:Vue3 - defineComponent 解决了什么?

defineComponent() - ElementUI Plus 示例

ConfigProvider 源码

import { defineComponent, renderSlot, watch } from &#39;vue&#39;
import { provideGlobalConfig } from &#39;./hooks/use-global-config&#39;
import { configProviderProps } from &#39;./config-provider-props&#39;
......
const ConfigProvider = defineComponent({
  name: &#39;ElConfigProvider&#39;,
  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 示例

OnClickOutside 源码

import { defineComponent, h, ref } from &#39;vue-demi&#39;
import { onClickOutside } from &#39;@vueuse/core&#39;
import type { RenderableComponent } from &#39;../types&#39;
import type { OnClickOutsideOptions } from &#39;.&#39;
export interface OnClickOutsideProps extends RenderableComponent {
  options?: OnClickOutsideOptions
}
export const OnClickOutside = /* #__PURE__ */ defineComponent<OnClickOutsideProps>({
    name: &#39;OnClickOutside&#39;,
    props: [&#39;as&#39;, &#39;options&#39;] as unknown as undefined,
    emits: [&#39;trigger&#39;],
    setup(props, { slots, emit }) {
      ... ...

      return () => {
        if (slots.default)
          return h(props.as || &#39;div&#39;, { 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 &#39;vue&#39;

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    resolve(/* 从服务器获取到的组件 */)
  })
})

const AdminPage = defineAsyncComponent(() =>
  import(&#39;./components/AdminPageComponent.vue&#39;)
)
</script>
<template>
  <AsyncComp />
  <AdminPage />
</template>

ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。类似 ViteWebpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件。

defineAsyncComponent() - VitePress 示例

<script setup>
import { defineAsyncComponent } from &#39;vue&#39;
import type { DefaultTheme } from &#39;vitepress/theme&#39;
defineProps<{ carbonAds: DefaultTheme.CarbonAdsOptions }>()
const VPCarbonAds = __CARBON__
  ? defineAsyncComponent(() => import(&#39;./VPCarbonAds.vue&#39;))
  : () => 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[&#39;setup&#39;]
): {
  new (props?: object): HTMLElement
}

除了常规的组件选项,defineCustomElement() 还支持一个特别的选项 styles,它应该是一个内联 CSS 字符串的数组,所提供的 CSS 会被注入到该元素的 shadow root 上。 返回值是一个可以通过 customElements.define() 注册的自定义元素构造器。

import { defineCustomElement } from &#39;vue&#39;
const MyVueElement = defineCustomElement({
  /* 组件选项 */
})
// 注册自定义元素
customElements.define(&#39;my-vue-element&#39;, MyVueElement)
import { defineCustomElement } from &#39;vue&#39;

const MyVueElement = defineCustomElement({
  // 这里是同平常一样的 Vue 组件选项
  props: {},
  emits: {},
  template: `...`,
  // defineCustomElement 特有的:注入进 shadow root 的 CSS
  styles: [`/* inlined css */`],
})
// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签
// 都会被升级
customElements.define(&#39;my-vue-element&#39;, 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 开始支持使用 defineCustomElementIonic 组件打包成自定义元素。
  • LitElement: Google 推出的 Web Components 库,提供类似 Vue 的模板语法,并支持使用 defineCustomElementLitElement 组件打包成自定义元素。
  • Stencil: 由 Ionic Team 开发的 Web Components 工具链,可以将任何框架的组件转换为自定义元素,并支持使用 defineCustomElement 直接将 Vue 组件打包成自定义元素。

总之,随着 Web Components 的不断流行和发展,越来越多的库和框架都开始使用 defineCustomElement 来实现跨框架、跨平台的组件共享。

小结

本次我们围绕着 Vue3 中的依赖注入与组件定义相关的几个 API,学习其基本使用方法,并且结合着目前流行的库和框架分析了使用场景,以此来加深我们对它们的认识。

内容收录于github 仓库

(学习视频分享:vuejs入门教程编程基础视频

以上是聊聊Vue3中的依赖注入与组件定义的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:掘金社区。如有侵权,请联系admin@php.cn删除
为什么Vue.js使用虚拟DOM而不是直接DOM操作?为什么Vue.js使用虚拟DOM而不是直接DOM操作?May 16, 2025 am 12:05 AM

Vue.js使用虚拟DOM而不是直接操作DOM,是为了提升性能和开发效率。1)虚拟DOM通过diff算法计算最小化DOM操作,提高性能。2)简化开发,开发者无需处理DOM复杂性。3)组件重用和组合更高效。虚拟DOM的工作原理是生成新树与旧树比较,只更新差异部分,减少DOM操作次数。

当vue.js虚拟DOM检测变化时会发生什么?当vue.js虚拟DOM检测变化时会发生什么?May 14, 2025 am 12:12 AM

whenthevue.jsvirtualdomdetectschange,itupdatesthevirlualdom,diffsit和appliesminimalchangeStothereAldom.thisprocessensuresrocessensureshighhighpperformance byformance byavoidingunnnnnnnnnnneclastory dommaniplastions。

将Vue.js的虚拟DOM视为真实DOM的镜像是多么准确?将Vue.js的虚拟DOM视为真实DOM的镜像是多么准确?May 13, 2025 pm 04:05 PM

Vue.js的VirtualDOM既是真实DOM的镜像,又不完全是。1.创建和更新:Vue.js基于组件定义创建VirtualDOM树,状态变化时先更新VirtualDOM。2.差异和修补:通过diff操作比较新旧VirtualDOM,仅将最小变化应用到真实DOM。3.效率:VirtualDOM允许批量更新,减少直接DOM操作,优化渲染过程。VirtualDOM是Vue.js优化UI更新的战略工具。

vue.js vs.反应:可伸缩性和可维护性vue.js vs.反应:可伸缩性和可维护性May 10, 2025 am 12:24 AM

Vue.js和React在可扩展性和可维护性上的表现各有优势。1)Vue.js易于上手,适合小型项目,CompositionAPI提升了大型项目可维护性。2)React适用于大型复杂项目,Hooks和虚拟DOM提高了性能和可维护性,但学习曲线较陡峭。

vue.js和React的未来:趋势和预测vue.js和React的未来:趋势和预测May 09, 2025 am 12:12 AM

Vue.js和React的未来趋势和预测分别是:1)Vue.js将在企业级应用中广泛应用,并在服务端渲染和静态站点生成方面有突破;2)React将在服务器组件和数据获取方面创新,并进一步优化并发模式。

Netflix的前端:深入研究其技术堆栈Netflix的前端:深入研究其技术堆栈May 08, 2025 am 12:11 AM

Netflix的前端技术栈主要基于React和Redux。1.React用于构建高性能的单页面应用,通过组件化开发提升代码重用性和维护性。2.Redux用于状态管理,确保状态变化可预测和可追踪。3.工具链包括Webpack、Babel、Jest和Enzyme,确保代码质量和性能。4.性能优化通过代码分割、懒加载和服务端渲染实现,提升用户体验。

vue.js和前端:构建交互式用户界面vue.js和前端:构建交互式用户界面May 06, 2025 am 12:02 AM

Vue.js是一种渐进式框架,适用于构建交互性强的用户界面。其核心功能包括响应式系统、组件化开发和路由管理。1)响应式系统通过Object.defineProperty或Proxy实现数据监听,自动更新界面。2)组件化开发允许将界面拆分为可复用的模块。3)VueRouter支持单页面应用,提升用户体验。

Vuejs的缺点是什么?Vuejs的缺点是什么?May 05, 2025 am 12:06 AM

Vue.js的主要缺点包括:1.生态系统相对较新,第三方库和工具不如其他框架丰富;2.学习曲线在复杂功能上变得陡峭;3.社区支持与资源不如React和Angular广泛;4.大型应用中可能遇到性能问题;5.版本升级与兼容性挑战较大。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

北端:融合系统,解释
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
4 周前By尊渡假赌尊渡假赌尊渡假赌
<🎜>掩盖:探险33-如何获得完美的色度催化剂
2 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。