上海新增 11 款已完成登记生成式 AI 服务
为进一步促进上海市生成式人工智能创新发展和规范应用,上海市网信办按照《生成式人工智能服务管理暂行办法》要求,有序开展上海市生成式人工智能服务备案工作。 同时,对通过API或其他方式直接调用已备案模型能力,且面向境内公众提供具有舆论属性或者社会动员能力的生成式人工智能服务开展登记工作,现将相关信息予以公告。 截至7月30日,上海市新增11款已完成登记的生成式人工智能服务,累计已完成20款生成式人工智能服务登记。
在处理可能以多种形式成立的条件时,我们通常会使用 switch case 语句,一个结合 TypeScript 的常见例子是处理枚举的各个成员值:
declare enum Color {Red,Yellow,Blue,}declare let color: Color;switch (color) {case Color.Red:// do somethingbreak;case Color.Yellow:// do somethingbreak;case Color.Blue:// do somethingbreak;default:break;}
Color.Pink 没有被处理,那使用粉色的在逃公主们很可能就直接卸载你的应用了。
declare enum Color {Red,Yellow,Blue,Pink,}declare let color: Color;switch (color) {case Color.Red:// do somethingbreak;case Color.Yellow:// do somethingbreak;case Color.Blue:// do somethingbreak;default:// 不能将类型“Color”分配给类型“never”。let exhaustiveCheck: never = color;break;}
throw new Error('Unhandled color') ,使用 never 类型进行检查,能够在开发阶段(最晚也就是构建阶段)就提前警示可能的错误。
let exhaustiveCheck: never = color;
实现原理:
在 switch case 或 if else 语句中,随着变量的类型成员不断被对应的分支认领,其类型会在后续的代码控制流中被移除,当所有类型成员都被移除时,TypeScript 会用 never 类型描述其类型,而 never 类型的变量无法被赋给除了 never 类型以外的值。
因此,在这个例子中,如果有遗漏的类型分支,那么 color 的类型就不会被描述为 never,就会导致类型不兼容的错误。
![]()
使用互斥类型替代联合类型
我们经常使用联合类型描述一组相近的实体类型,比如我们希望一个变量要么符合游客 Visitor 类型,要么符合注册用户 Registered 类型,不允许同时符合(即同时拥有 referer 与 email 这两个属性)。一般我们会想到使用联合类型 User:
interface Visitor {referer: string;}interface Registered {email: string;}type User = Visitor | Registered;
但这其实是个误区,因为联合类型不会约束「不能同时符合」这一点:
const user: User = {referer: 'www.google.com',email: 'linbudu@qq.com',};
这可能会导致后续的代码处理出现问题,比如可能有判断 user.email 存在就认为它是已注册用户的逻辑。
为了表示「不能同时拥有」,我们可以使用互斥类型 XOR:
type XORUser = XOR<Visitor, Registered>;// 属性“email”的类型不兼容。const user1: XORUser = {referer: 'www.google.com',};user1.email; // undefineduser1.email = 'linbudu@qq.com'; // X// 属性“email”的类型不兼容。const user2: XORUser = {referer: 'www.google.com',email: 'linbudu@qq.com',};
XOR 的两个类型参数表示这两个类型互斥,因此你也可以实现「要么同时存在,要么同时不存在」的属性绑定,只需要为其中一个参数指定 {} 类型即可。
interface Registered {email: string;registerTime: number;level: number;}type XORStruct = XOR<{}, Registered>;const val1: XORStruct = {}; // √// Xconst val2: XORStruct = {email: 'linbudu@qq.com',};// √const val3: XORStruct = {email: 'linbudu@qq.com',registerTime: Date.now(),level: 9999,};
公式:
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);
使用:
type XORType = XOR<Type1, Type2>;// 实现三个类型彼此互斥type XORType2 = XOR<XORType, Type3>;
实现原理:
![]()
保留联合类型的提示
在开发组件时,一个常见的场景是某个属性既可以有一组预设的值,又可以是任意的同类型值,如:
type Size = 'mini' | 'middle' | 'large';let size1: Size = 'mini';// 不能将类型“"200px"”分配给类型“Size”。let size2: Size = '200px';
这种时候怎么描述类型就有点矛盾了,我又想提供字面量联合类型的提示,又想支持任意的字符串类型,应该怎么做?如果直接 Size | string,那么 Size 中的联合类型会被合并进 string,导致最后类型描述为 string 类型。
这里有个小 trick,可以这么做:
type PresetSize = 'mini' | 'middle' | 'large';type Size = PresetSize | (string & {});let size1: Size = 'mini';let size2: Size = '200px';
公式:
请直接复制这个工具类型:
type SmartLiteral<T extends keyof any> = T | (string & {});
使用:
type SmartLiteral<T extends keyof any> = T | (string & {});type PresetSize = 'mini' | 'middle' | 'large';type Size = SmartLiteral<PresetSize>;let size1: Size = 'mini';let size2: Size = '200px';
string & {} 这个类型等价于 string 类型,可参考 TypeScript 4.8 版本中 - 交叉类型与联合类型的类型收窄增强 中的介绍,而与空对象进行交叉类型,又确保了它在联合类型中不会被视为其它字面量类型的父类型,从而避免了类型合并。
satisfies 关键字
satisfies 关键字引入于 TypeScript 4.9 版本,用于实现「使用类型约束值,但仍然使用值本身推导的类型」的效果。
type Colors = 'red' | 'green' | 'blue';type RGB = [number, number, number];type Palette = Record<Colors, string | RGB>;const palette = {red: [255, 0, 0],green: '#00ff00',blue: [0, 0, 255],} satisfies Palette;// stringpalette.green.startsWith('#'); // √// [number, number, number]palette.red.find(() => true); // √// [number, number, number];palette.blue.entries(); // √
在这个例子中,我们要求变量 palette 的类型满足 Palette 结构,同时没有像类型断言或类型标注的效果一样(标注为 Palette 类型,或断言到 Palette 类型),将变量类型修改为了 Palette 类型,而是继续保留了其原始推导出的字面量类型结构。
关于 satisfies 、类型标注、类型断言与隐式类型推导的差异,请阅读:TypeScript 4.9 beta: satisfies 操作符。
当你希望获得一组规律固定,可由排列组合得到的联合类型时,可以使用模板字符串类型的插槽组合特性:
type Software = 'WeChat' | 'AliPay' | 'LOLM';type Platform = 'Android' | 'iOS' | 'HarmonyOS';type VersionTag = 'debug' | 'stable' | 'nightly';type Products = `${Software}-${Platform}-${VersionTag}`
使用重映射快速修改接口
name | age | job 的接口类型,你希望能基于其派生出一个属性为 updatedName | updatedAge | updatedJob 的接口类型,这样一来在原接口属性发生变化时,你无需进行手动处理。interface User {name: string;age: number;job: string;}// {// updatedName: string;// updatedAge: number;// updatedJob: string;// }type UpdatedUser = {[K in keyof User as `updated${Capitalize<K>}`]: User[K];};
公式:
type DerivedStruct<Struct extends object> = {[K in keyof Struct as `updated${Capitalize<K & string>}`]: Struct[K];};type UpdatedUser2 = DerivedStruct<User>;
实现原理:
K & string ,通过交叉类型的结果同时满足其类型成员的定义,确保类型符合 Capitalize 的泛型类型约束。
In Deep:
如果接口中只有一部分属性需要进行处理,应该怎么办?当然可以实现一个 DerivedStructFromProperties ,然后再开放一个参数来确定需要处理的属性,但这样又变成需要手动处理了。更好的方式是拆分你的接口:
interface UserDetail {}interface UserRelation {}interface UserLevel {}interface User extends UserDetail, UserRelation, UserLevel {}interface UpdatedUserextends DerivedStruct<UserDetail>,UserRelation,UserLevel {}
其中 UserDetail 即为需要处理的属性集合。
提取类型
某些时候我们可能会遇到这么个情况,某个三方的 npm 包,导出了类型 A,其中引用了类型 B(但没有导出),而现在我们需要的就是类型 B。
这种时候我们自己使用 infer 关键字来从类型 A 提取类型 B,常见的有这么几种:
type ArrayElementType<T extends any[]> = T extends (infer U)[] ? U : never;type UserList = Array<{id: number;name: string;age: number;}>;// {// id: number;// name: string;// age: number;// }type User = ArrayElementType<UserList>;
type QueryUserResponse = Promise<{id: string;name: string;email: string;}>;// {// id: string;// name: string;// email: string;// }type User = Awaited<QueryUserResponse>;
Awaited 是 TypeScript 内置的工具类型,可以用于提取一个 Promise resolve 的值类型。
interface User {}interface UpdatedUser extends User {}// 仅导出了函数export function updateUser(input: User): UpdatedUser {}// Usertype InputUser = Parameters<typeof updateUser>[0];// UpdatedUsertype OutputUser = ReturnType<typeof updateUser>;
Parameters、ReturnType 都是内置的工具类型,分别用于提取函数的参数类型与返回类型值。
携带泛型的 React 组件
在 List / Waterfall 这一类组件中,常见的设计是由 dataSource 属性接受数据源,再由 renderItem 属性负责遍历 dataSource 生成内部子元素,这也就意味着应该让 dataSource 的类型能够传递到 renderItem 中,如:
import React from 'react';function foo() {return (<ScrollerdataSource={[5, 9, 9]}// item 为 number 类型renderItem={(item) => {return <div>{item.toFixed()}</div>;}}/>);}
要实现以上的效果,你可以使用携带泛型的接口结构,声明组件类型:
import React from 'react';interface ScrollerProps<TData = any> {dataSource: TData[];renderItem?: (item: TData) => React.ReactElement;}export interface GenericTypingWrapper {<TInputData = any>(props: ScrollerProps<TInputData>): React.ReactElement;}const List: GenericTypingWrapper = ({ dataSource }) => {return <></>;};
如果你希望在组件编写时使用 React.FC,那么可以在导出组件时使用类型断言修正类型:
const Scroller: React.FC<ScrollerProps> = ({ dataSource }) => {return <></>;};export default Scroller as GenericTypingWrapper;
结语
本文总结了在业务代码中实用的多种 TypeScript 技巧,涵盖了使用 never 类型检查 switch case 语句以确保枚举或联合类型分支处理的完整性,用互斥类型替代联合类型来避免类型冲突,保留联合类型提示的方法,satisfies 关键字的运用,模板字符串类型的排列组合特性,利用重映射快速修改接口,提取特定类型的方式,以及在 React 组件中实现携带泛型等内容。每个技巧都配有详细的代码示例和原理讲解,有助于开发者在实际编程中更高效、准确地使用 TypeScript 。
本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
微信关注我们
转载内容版权归作者及来源网站所有!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。
马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。
为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。
Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。