COM 学习(五-A) 组件创建过程(图示)

博客主要提及了创建过程和控制流程两方面内容,虽未详细阐述,但这两个信息技术相关要点是核心。

 

1. 创建过程

 


2. 控制流程 
 


 

 

依赖注入​ 此章节假设你已经看过了组件基础。若你还不了解组件是什么,请先阅读该章节。 Prop 逐级透传问题​ 通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦: Prop 逐级透传的过程图示 注意,虽然这里的 <Footer> 组件可能根本不关心这些 props,但为了使 <DeepChild> 能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。 provide 和 inject 可以帮助我们解决这一问题 [1]。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。 Provide/inject 模式 Provide (提供)​ 要为组件后代提供数据,需要使用到 provide() 函数: vue <script setup> import { provide } from 'vue' provide(/* 注入名 */ 'message', /* 值 */ 'hello!') </script> 如果不使用 <script setup>,请确保 provide() 是在 setup() 同步调用的: js import { provide } from 'vue' export default { setup() { provide(/* 注入名 */ 'message', /* 值 */ 'hello!') } } provide() 函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。 第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref: js import { ref, provide } from 'vue' const count = ref(0) provide('key', count) 提供的响应式状态使后代组件可以由此和提供者建立响应式的联系。 应用层 Provide​ 除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖: js import { createApp } from 'vue' const app = createApp({}) app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!') 在应用级别提供的数据在该应用内的所有组件中都可以注入。这在你编写插件时会特别有用,因为插件一般都不会使用组件形式来提供值。 Inject (注入)​ 要注入上层组件提供的数据,需使用 inject() 函数: vue <script setup> import { inject } from 'vue' const message = inject('message') </script> 如果有多个父组件提供了相同键的数据,注入将解析为组件链上最近的父组件所注入的值。 如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。 带有响应性的 provide + inject 完整示例 同样的,如果没有使用 <script setup>,inject() 需要在 setup() 内同步调用: js import { inject } from 'vue' export default { setup() { const message = inject('message') return { message } } } 注入默认值​ 默认情况下,inject 假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。 如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似: js // 如果没有祖先组件提供 "message" // `value` 会是 "这是默认值" const value = inject('message', '这是默认值') 在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值: js const value = inject('key', () => new ExpensiveClass(), true) 第三个参数表示默认值应该被当作一个工厂函数。 和响应式数据配合使用​ 当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。 有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数: vue <!-- 在供给方组件--> <script setup> import { provide, ref } from 'vue' const location = ref('North Pole') function updateLocation() { location.value = 'South Pole' } provide('location', { location, updateLocation }) </script> vue <!-- 在注入方组件 --> <script setup> import { inject } from 'vue' const { location, updateLocation } = inject('location') </script> <template> <button @click="updateLocation">{{ location }}</button> </template> 最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。 vue <script setup> import { ref, provide, readonly } from 'vue' const count = ref(0) provide('read-only-count', readonly(count)) </script> 使用 Symbol 作注入名​ 至此,我们已经了解了如何使用字符串作为注入名。但如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。 我们通常推荐在一个单独的文件中导出这些注入名 Symbol: keys.js js export const myInjectionKey = Symbol() js // 在供给方组件中 import { provide } from 'vue' import { myInjectionKey } from './keys.js' provide(myInjectionKey, { /* 要提供的数据 */ }) js // 注入方组件 import { inject } from 'vue' import { myInjectionKey } from './keys.js' const injected = inject(myInjectionKey) TypeScript 用户请参考:为 Provide / Inject 标注类型 译者注 [1] 在本章及后续章节中,“提供”将成为对应 Provide 的一个专有概念 根据以上内容做一份学习笔记,要详细,内容要丰富。要做总结,最好绘制图表方便学习
09-20
# 学习笔记:依赖注入(Provide & Inject)详解 ## 一、问题背景:Prop 逐级透传的痛点 在 Vue 的组件通信中,父组件通常通过 `props` 向子组件传递数据。然而,当组件层级较深时,若某个深层子组件需要祖先组件的数据,则必须将该数据通过中间组件 **逐层传递**,即使这些中间组件并不使用此数据。 ### 示例结构: ``` <App> └── <Header> └── <Navbar> └── <UserInfo> ← 需要 App 提供的 user 数据 ``` 此时,`user` 数据需从 `<App>` → `<Header>` → `<Navbar>` → `<UserInfo>`,造成代码冗余与维护困难。 > 这种现象被称为 **“prop 逐级透传”**,应尽量避免。 --- ## 二、解决方案:Provide 和 Inject Vue 提供了 **依赖注入机制** —— `provide()` 和 `inject()`,允许祖先组件向其任意后代组件直接提供数据,无需通过中间组件转发。 ### 模型图示: ```mermaid graph TD A[祖先组件] -- provide --> B[后代组件1] A -- provide --> C[后代组件2] A -- provide --> D[深层后代组件N] style A fill:#4CAF50, color:white style B fill:#2196F3, color:white style C fill:#2196F3, color:white style D fill:#2196F3, color:white ``` > ✅ 优势:打破组件层级限制,实现跨层级数据共享 > 🔄 响应式支持:可传递响应式数据(如 `ref`) --- ## 三、核心 API 使用方法 ### 1. `provide(注入名, 值)` 用于祖先组件中提供数据。 #### 语法格式: ```js import { provide } from 'vue' provide('message', 'hello!') ``` - 第一个参数为 **注入名**(字符串或 Symbol) - 第二个参数为 **提供的值**(任意类型,包括响应式对象) #### 示例:提供响应式数据 ```js <script setup> import { ref, provide } from 'vue' const count = ref(0) provide('count', count) </script> ``` > 注意:若提供的是 `ref`,接收方会收到原始 `ref` 对象,保持响应性链接。 #### 应用级别 Provide 可在应用初始化时全局提供: ```js import { createApp } from 'vue' const app = createApp(App) app.provide('globalMessage', 'Hello World') ``` 适用于插件开发等场景。 --- ### 2. `inject(注入名, 默认值?)` 用于后代组件中获取祖先提供的数据。 #### 基础用法: ```js <script setup> import { inject } from 'vue' const message = inject('message') </script> ``` #### 设置默认值: ```js // 若未找到提供者,使用默认值 const value = inject('message', '这是默认值') ``` #### 使用工厂函数生成默认值(延迟执行): ```js const expensiveValue = inject('key', () => new ExpensiveClass(), true) ``` > 第三个参数 `true` 表示第二个参数是 **工厂函数**,仅在需要时调用。 --- ## 四、最佳实践与注意事项 ### ✅ 推荐做法:提供状态 + 修改方法 为了保证数据流清晰,建议在提供方同时暴露变更逻辑: #### 提供方: ```js <script setup> import { ref, provide } from 'vue' const location = ref('North Pole') function updateLocation() { location.value = 'South Pole' } provide('location', { location, updateLocation }) </script> ``` #### 注入方: ```js <script setup> import { inject } from 'vue' const { location, updateLocation } = inject('location') </script> <template> <button @click="updateLocation">当前地点:{{ location }}</button> </template> ``` > ✔️ 数据变更仍由供给方控制,便于追踪和调试。 --- ### 🔒 防止修改:使用 `readonly()` 若希望防止后代组件修改提供的响应式数据,可用 `readonly()` 包装: ```js <script setup> import { ref, provide, readonly } from 'vue' const count = ref(0) provide('read-only-count', readonly(count)) </script> ``` > 注入方无法修改 `count` 的值,确保数据安全性。 --- ### 🏷️ 使用 Symbol 作为注入名(推荐大型项目) 为避免命名冲突,尤其是库开发者,推荐使用 **Symbol** 作为注入名。 #### 创建独立文件 `keys.js`: ```js // keys.js export const myInjectionKey = Symbol() ``` #### 提供方: ```js import { provide } from 'vue' import { myInjectionKey } from './keys.js' provide(myInjectionKey, { data: 'some value' }) ``` #### 注入方: ```js import { inject } from 'vue' import { myInjectionKey } from './keys.js' const injected = inject(myInjectionKey) ``` > 💡 Symbol 确保唯一性,杜绝命名污染。 --- ## 、完整响应式示例 ### 提供方组件: ```vue <!-- Provider.vue --> <script setup> import { ref, provide } from 'vue' const theme = ref('dark') function toggleTheme() { theme.value = theme.value === 'light' ? 'dark' : 'light' } provide('theme', { theme, toggleTheme }) </script> ``` ### 深层注入方组件: ```vue <!-- DeepChild.vue --> <script setup> import { inject } from 'vue' const { theme, toggleTheme } = inject('theme') </script> <template> <div :class="theme"> 当前主题:{{ theme }} <button @click="toggleTheme">切换主题</button> </div> </template> <style scoped> .dark { background: #333; color: white; } .light { background: white; color: black; } </style> ``` > ✅ 实现跨多层组件的主题切换功能,无需中间组件参与。 --- ## 六、总结对比表 | 特性 | Props 透传 | Provide/Inject | |------|-----------|----------------| | 数据流向 | 单向逐级向下 | 跨层级向下 | | 中间组件是否需参与 | 是(必须转发) | 否 | | 是否支持响应式 | 是 | 是(配合 `ref`) | | 是否可设默认值 | 是 | 是 | | 是否易维护 | 组件链越长越差 | 更优 | | 是否适合全局状态 | ❌ 不适合 | ✅ 适合 | --- ## 七、知识点详解 1. **依赖注入机制** `provide/inject` 实现跨层级数据传递,打破 props 必须逐级透传的限制。 2. **响应式数据共享** 提供 `ref` 对象可维持响应性,注入方可通过 `.value` 访问最新值。 3. **Symbol 作为唯一键** 使用 `Symbol()` 可避免命名冲突,特别适用于大型应用或第三方库。 --- ## 八、学习图表汇总 ### 流程图:Provide/Inject 工作流程 ```mermaid flowchart TB Start[开始] --> Provide[祖先组件调用 provide(key, value)] Provide --> Inject[后代组件调用 inject(key)] Inject --> Found{是否存在提供者?} Found ----> Return[返回对应值] Found ----> Default{是否有默认值?} Default ----> UseDefault[返回默认值] Default ----> Warn[运行时警告] Return --> End[完成注入] UseDefault --> End ``` --- ## 九、适用场景总结 ✅ 推荐使用场景: - 主题、语言等全局配置 - 插件提供的共享服务 - 多层嵌套菜单、表单、布局组件间通信 ❌ 不推荐滥用: - 替代 Vuex/Pinia 状态管理(复杂状态仍建议使用状态库) - 频繁变动且多方向通信的状态 --- ## 十、TypeScript 类型标注(扩展知识) 对于 TS 用户,可通过泛型标注类型: ```ts interface Theme { theme: Ref<string> toggleTheme: () => void } const theme = inject<Theme>('theme', { theme: ref('light'), toggleTheme: () => {} }) ``` 更严谨的方式是结合 `InjectionKey<T>` 类型: ```ts import { InjectionKey } from 'vue' export const themeKey = Symbol() as InjectionKey<{ theme: Ref<string> toggleTheme: () => void }> ``` 提供时自动推导类型,增强类型安全。 --- > 📝 **结语**:`provide/inject` 是 Vue 中强大而灵活的依赖注入工具,合理使用能显著提升组件解耦能力和开发效率。但在使用过程中应注意职责分离,避免过度依赖导致数据流混乱。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值