Rumah >hujung hadapan web >View.js >Mari kita bincangkan tentang suntikan pergantungan dan definisi komponen dalam Vue3

Mari kita bincangkan tentang suntikan pergantungan dan definisi komponen dalam Vue3

青灯夜游
青灯夜游ke hadapan
2023-03-21 18:46:371799semak imbas

Kali ini terutamanya kami berkongsi beberapa API yang berkaitan dengan suntikan pergantungan dan definisi komponen dalam Vue3, serta penggunaannya dalam perpustakaan biasa ElementUI Plus dan Vueuse, dan menggunakan contoh untuk memahami senario penggunaan.

Mari kita bincangkan tentang suntikan pergantungan dan definisi komponen dalam Vue3

Mari kita bincangkan tentang suntikan pergantungan dan definisi komponen dalam Vue 3.

provide() & inject()

provide()

Memberikan nilai yang boleh disuntik oleh komponen keturunan .

function provide<T>(key: InjectionKey<T> | string, value: T): void

menerima dua parameter:

  • key untuk disuntik, bertali atau Symbol; Nilai suntikan yang sepadan
export interface InjectionKey<T> extends Symbol {}
adalah serupa dengan
    yang mendaftarkan cangkuk kitaran hayat
  • mesti dipanggil serentak dalam fasa
  • komponen. [Cadangan berkaitan:
tutorial video vuejs

, APIpembangunan bahagian hadapan webprovide()]setup()inject()

Suntikan nenek moyang komponen atau nilai yang disediakan oleh keseluruhan apl (melalui

).

app.provide()Parameter pertama disuntik

.
// 没有默认值
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
akan merentasi rantai komponen induk, memadankan
    untuk menentukan nilai yang disediakan. Jika berbilang komponen dalam rantai komponen induk memberikan nilai untuk
  • yang sama, maka komponen yang lebih dekat akan "menimpa" nilai yang disediakan oleh komponen lebih jauh ke atas rantai. Jika tiada nilai yang dipadankan dengan

    , key akan mengembalikan Vue melainkan nilai lalai diberikan. keykeykeyinject()Parameter kedua adalah pilihan, iaitu nilai lalai yang digunakan apabila tiada undefined dipadankan. Ia juga boleh menjadi fungsi kilang yang mengembalikan beberapa nilai yang lebih kompleks untuk dibuat. Jika nilai lalai itu sendiri ialah fungsi, maka anda mesti lulus

    sebagai parameter ketiga, menunjukkan bahawa fungsi ini ialah nilai lalai, bukan fungsi kilang.
  • keyfalseprovide() & inject() - Contoh rasmi

provide() & inject() - Contoh ElementUI Plus

// 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>
Komponen Breadcrumb

provide() & inject() - Contoh VueUse

<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>

createInjectionState kod sumber / createInjectionState menggunakan

pakej/teras/computedInject kod sumber

nextTick()

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]
}

Kaedah alat yang menunggu kemas kini DOM seterusnya.

Nota: Apabila anda menukar keadaan responsif dalam

, kemas kini

terakhir tidak disegerakkan, tetapi dicache dalam baris gilir oleh
function nextTick(callback?: () => void): Promise<void>
Ia tidak akan dilaksanakan sehingga seterusnya

. Ini adalah untuk memastikan setiap komponen hanya melakukan satu kemas kini tanpa mengira berapa banyak perubahan keadaan berlaku. VueDOMVue boleh digunakan serta-merta selepas perubahan status untuk menunggu kemas kini “tick” selesai. Anda boleh lulus

fungsi panggil balik

sebagai parameter, atau PromisenextTick() dikembalikan oleh DOMmenunggu. contoh laman web rasmi nextTick()

nextTick() - contoh ElementUI Plus

<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>

ElCascaderPanel kod sumber

nextTick() - contoh VueUse

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)
      }
    })
  }
  ......
})

gunakan kod sumberInfiniteScroll

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)

使用 Vue 构建自定义元素

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入门教程编程基础视频

Atas ialah kandungan terperinci Mari kita bincangkan tentang suntikan pergantungan dan definisi komponen dalam Vue3. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam