鲜花网网站开发的意义,网站开发者工作内容,摄影网站论文,久久建材有限公司前言
自从使用了Provide/Inject代码的组织方式更加灵活了#xff0c;但是这个灵活性的增加伴随着代码容错性的降低。我相信只要是真的在项目中引入Provide/Inject的同学#xff0c;一定一定有过或者正在经历下面的状况#xff1a;
注入名#xff08;Injection key#x…前言
自从使用了Provide/Inject代码的组织方式更加灵活了但是这个灵活性的增加伴随着代码容错性的降低。我相信只要是真的在项目中引入Provide/Inject的同学一定一定有过或者正在经历下面的状况
注入名Injection key经常拼错又或者注入名太多导致注入名取名困难(程序员通病)为了弄清楚inject()注入的是啥不得不找到对应provide()另一种情况是重复provide()同一值导致Injection覆盖使用inject()时祖先链上未必存在对应的provide()不得不做空值处理或默认值处理在hook中使用provide()但是调用hook的组件无法inject()这个hook的provide()…
Provide/Inject解决了什么问题
依赖注入|Vue.js中提到Provide/Inject这两个API主要是用来解决Prop逐级透传问题就像下面这样 引入Provide/Inject后Prop就可以直接传入到后代组件就像下面这样 根组件中通过provide提供注入值示例代码如下
import { provide } from vue;provide(/* 注入名 */ account, /* 值 */ { name: youth });后代组件中通过inject获取祖先组件注入的值示例代码如下
import { inject } from vue;const message inject(account);当只是在项目中小范围的使用provide和inject时上面示例的写法没什么问题。但是如果项目工程较大代码量也多的情况下就会出现一些问题。
注入名冲突
问题是如何保证account不会被其他业务组件覆盖例如如果某个业务组件也提供了account的信息就像下面这样
中间层的ParentView组件可能是一个用户列表组件也提供了account数据这里的account可能是列表选中的用户而Main中提供的是当前用户。在DeepChild组件中可能即需要当前登录用户信息又需要列表选中的用户信息而目前DeepChild中只能获取到ParentView提供的选中用户信息。 当然这种业务场景有很多解决方案这里先认为只能通过provide/inject解决 当然我们完全可以在ParentView中将注入名改写为selectAccount来解决这个问题但是如果中间层还有其他的组件这些组件也有selectAccount呢
实践方案
在项目中创建一个名为injection-key.ts的文件我习惯将该文件创建为src/constants/injection-key.ts。这样在该文件中统一管理项目下的注入名并且使用Symbol来创建注入名来回避取名冲突.
export const CurAccountKey Symbol(account);export const AuthAccountKey Symbol(account);用法示例
Main.vue:
import { provide } from vue;
import { CurAccountKey } from /constants/injectionKeys;const user reactive({ id: 1, name: youth });
provide(CurAccountKey, user);ParentView.vue:
import { provide } from vue;
import { AuthAccountKey } from /constants/injectionKeys;const user reactive({ id: 1, name: John Doe });
provide(AuthAccountKey, user);DeepChild.vue:
import { inject } from vue;
import { AuthAccountKey, CurAccountKey } from /constants/injectionKeys;const curAccount inject(CurAccountKey);
const authAccount inject(AuthAccountKey);注入提示
但是使用inject(CurAccountKey)会代码什么样的数据这就不得不全局查找CurAccountKey的provide了。这种的使用体验十分不好这时Vue官方推荐我们使用TS。
import { inject } from vue;
import { AuthAccountKey, CurAccountKey } from /constants/injectionKeys;const curAccount inject(CurAccountKey);
curAccount.name; // curAccount存在name吗实践方案
Vue|为provide / inject 标注类型中提到了InjectionKey类型使用TS和InjectionKey可以有效解决类型提示问题
src/types.ts:
export interface Account {name: string;id: number;
};src/constants/injection-key.ts:
import { InjectionKey } from vue;
import { Account } from /types;export const CurAccountKey: InjectionKeyAccount Symbol(account)Main.vue:
import { provide } from vue;
import { CurAccountKey } from /constants/injectionKeys;const user reactive({ id: 1, name: youth });
provide(CurAccountKey, name: youth); // ❌
provide(CurAccountKey, user); // DeepChild.vue:
const curAccount inject(CurAccountKey);
curAccount?.age; // ❌
curAccount?.id; // 严格注入
默认情况下inject假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供则会抛出一个运行时警告
const curAccount inject(CurAccountKey);
curAccount?.id;当然有时候我们可能并不是要求必须在祖先链上提供这时候Vue官方推荐我们使用默认值来解决祖先链未提供值的情况这也仅仅是能解决inject值不是必要值的情况
但是有些情况下我们又要求祖先链上必须提供需要的inject这种情况更常见的是通用型组件开发中。例如和组件的祖先链上必须存在组件。如果单独使用是不合法的这时候应该抛出错误❌而不是警告⚠️
要解决上面的严格依赖问题我们当然可以在子组件中通过判断inject的值是否为undefined如果是则抛出异常。这种代码很简单
const curAccount inject(CurAccountKey);
if (!curAccount) {throw new Error(CurAccountKey必须提供对应的Provide);
}
curAccount.id;嗯不错是解决了问题如果严格依赖的很多呢难不成到处都是if判断
实践方案
创建一个严格注入工具函数当对应的注入名没有被提供时抛出异常。
export const injectStrict T(key: InjectionKeyT, defaultValue?: T | (() T), treatDefaultAsFactory?: false): T {const result inject(key, defaultValue, treatDefaultAsFactory); if (!result) { throw new Error(Could not resolve ${key.description}); } return result;
}使用injectStrict重写吧
const curAccount injectStrict(CurAccountKey);
curAccount.id;再谈逐级穿透
在Vue中Provide组件无法使用provide值
这个看着有点绕直观来看使用情况是这样的
const user reactive({ id: 1, name: youth });
provide(CurAccountKey, user);...inject(CurAccount); // 这里无法获取提供的user这时候有的同学肯定会说Provide组件使用provide的值有没有搞错啊怎么会有这种操作
const user reactive({ id: 1, name: youth });
provide(CurAccountKey, user);//这里需要user值的时候直接用不就好了
user;逐级透传问题又来了 但是别忘了自定义hook的情况啊如果provide(CurAccountKey, user);是在一个自定义的hook中的呢
useAccount.ts:
export const useAccount async () {const user await fetch(/**/*);provide(CurAccountKey, user);return { user };
}如果是直接调用useAccount还不是问题因为useAccount返回了user。在调用userAccount的地方可以直接解构出user这样很直观也很方便。
如果useAccount被其他的hook再次封装呢
useApp.ts:
export const useApp async () {const account await useAccount();...return {account}
}当然这也不是没有解决方法可以在useApp中解构account再返回
useApp.ts:
export const useApp async () {const account await useAccount();...return {...account}
}有没有觉得这种情况很熟悉我们把hook换成组件情况是不是就是这样 Provide/Inject的出现就是为了解决这样的问题但是当在hook中出现透传时却又成了最初的样子啊
实践方案
解决上面问题的方案也很简单就是获取当前组件实例然后从组件实例中找到provide的值就好了
既然Vue本身无法支持当前组件获取当前组件的provide那我们自己实现一个吧
import { getCurrentInstance, inject, InjectionKey } from vue;export const injectWithSelf T( key: InjectionKeyT): T | undefined { const vm getCurrentInstance() as any; return vm?.provides[key as any] || inject(key);
}这里我们从当前组件的实例中找到对应key的provide值如果不存在就走inject从祖先链组件中获取。
使用injectWithSelf重写一下吧
useAccount.ts:
export const useAccount async () {const user await fetch(/**/*);provide(CurAccountKey, user);return { user };
}useApp.ts:
export const useApp async () {const account await useAccount();...return {account}
}Main.vue:
useApp();// 必须在useApp()之后
const user injectWithSelf(CurAccountKey)最后
使用Symbol来创建注入名来回避取名冲突使用TS和InjectionKey可以有效解决类型提示问题使用自定义injectStrict可以解决严格注入问题使用自定义injectWithSelf可以解决hook嵌套时的返回值逐级穿透问题