加入收藏 | 设为首页 | 会员中心 | 我要投稿 源码门户网 (https://www.92codes.com/)- 云服务器、云原生、边缘计算、云计算、混合云存储!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

Vue3中的依赖注入与组件定义怎么很好实现

发布时间:2023-06-01 15:33:11 所属栏目:PHP教程 来源:互联网
导读:   本篇内容主要讲解“Vue3中的依赖注入与组件定义怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue3中的依赖
  本篇内容主要讲解“Vue3中的依赖注入与组件定义怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue3中的依赖注入与组件定义怎么实现”吧!
 
  provide() & inject()
 
  provide()
 
  提供一个值,可以被后代组件注入。
 
  function provide<T>(key: InjectionKey<T> | string, value: T): void
 
  接收两个参数:
 
  要注入的 key,字符串或者 Symbol;
 
  export interface InjectionKey<T> extends Symbol {}
 
  对应注入的值
 
  与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。
 
  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 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
 
  nextTick() 官网示例
 
  <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>
 
  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('node')}.${ns.is('active')}`) ||
 
            menuElement.querySelector(`.${ns.b('node')}.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['setup']
 
  ): ComponentConstructor
 
  第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。
 
  注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX 中用作标签时提供类型推导支持。
 
  const Foo = defineComponent(/* ... */)
 
  // 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
 
  type FooInstance = InstanceType<typeof Foo>
 
  参考:Vue3 - defineComponent 解决了什么?
 
  defineComponent() - ElementUI Plus 示例
 
  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() - 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 '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 组件打包成自定义元素。
 

(编辑:源码门户网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章