【面试题】 TypeScript 前端面试题 由浅到深(二)

简介: 【面试题】 TypeScript 前端面试题 由浅到深(二)

【面试题】 TypeScript 前端面试题 由浅到深(一):https://developer.aliyun.com/article/1414030

泛形

上面我们通过PropType这个类型工具,生成了我们的props类型。而通过一个类型生成另一个目标类型,就是我们泛形的主要目的。以最简单的Array类型来说。我们就可以通过传入一个类型string,Array<string>来生成一个string类型的数组。

Array<T>类比为js,他可能是这样一段代码

function createArray(T) {
    const array = new Array()
    array.type = T;
    return array;
}
复制代码

泛形可以类比为函数,通过一个入参,再通过函数内部的逻辑改造,我们就可以得到一个返回值。比如最简单的add函数。类型的实现与函数的实现,其实在使用上和定义上基本是一致的。区别就在于在类型上完成两数的加法具体实现可能不是那么容易。

const add = (num1, num2) => num1 + num2;
const res = add(1,1); // res = 2
type add<num1,num2> = /**一系列类型操作 */;
type res = add<1,2>; // type res = 3
复制代码

于此相似的还有接口泛形,区别不大

interface IRes<T> {
    code: number;
    msg?: string;
    data: T;
}
复制代码

函数泛形

我们有一个identity函数,将入参直接返回,那么我们将有如下实现。

function identity(arg) {
    return arg;
}
const res = identity(1); // res is any
复制代码

我们可以确定的是,我们输入的是1,返回的就是1。但是结果是any,这在我们预期的类型推导层面是不合理的。所以我们需要函数泛形,函数泛形的作用就是为函数提供一个类型的入参,在整个函数体内都可以拿到这个类型入参。用于函数的内变量的声明,函数用于返回类型声明。

function identity<T>(arg): T {
    let data: T;
    return arg;
}
const res = identity<1>(1); // res 1
复制代码

在泛形函数中,我们可以在变量处进行对泛形进行预赋值,将变量将来的值的类型赋值给泛形参数

function identity<T>(arg: T): T {
    let data: T;
    return arg;
}
const res = identity(1);// res 1 这样不用传入<1>这个类型参数
复制代码

对于上面讲到的defineProp其实也是通过泛形参数实现的,实现如下。

function defineProps<TypeProps>(): Readonly<TypeProps>
复制代码

于此相似的还有类泛形,作用同样是将泛形参数提供给类,在类里面可以进行使用该参数。

类型关键字

为了实现理想的类型约束和维护,我们就会使用到类型关键字,他可以帮助我们进行简单的类型转换,类似于js原生提供的api,比如array会有length属性可以拿到数组长度,Object.keys可以拿到一个对象的key组成的数组。在ts中,也会提供一些小api以完成对类型的裁剪拼接。

1.[]操作符

当我们需要从IUser拿到账户信息的部分

interface IUser {
    userId: number;
    userName: string;
    QRCode: string;
    age: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
interface PartOfIUser {
    userId: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
复制代码

如此操作,重复度极高,不利于统一类型来源,一改就要改两次。

bad taste:需要反着写,反直觉

interface PartOfIUser {
    userId: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
interface IUser extends PartOfIUser{
    userName: string;
    QRCode: string;
    age: number;
}
复制代码

good taste:

interface IUser {
    userId: number;
    userName: string;
    QRCode: string;
    age: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
interface PartOfIUser {
    userId: IUser['userId'];
    accountInfo: IUser['accountInfo'];
}
// 或者使用类型工具Pick
type PartOfIUser = Pick<IUser, 'userId' | 'accountInfo'>;
复制代码

2.typeof 操作符

获取某个值的类型属性

const shixin = { name: 'shixin', age: '22' };
type IShixin = typeof shixin;
// 等于
type IShixin = {
    name: string;
    age: string;
}
let num = 1
type n = typeof num
// 等于
type n = number
复制代码

3.keyof 操作符

TS版本的Object.keys() 可以获取interface的key,组成联合类型

type IShixin = {
    name: string;
    age: string;
};
type Key = keyof IShixin;
//等于
type Key = 'name' | 'age';
复制代码

4.in 操作符

用于遍历联合类型

type Keys = 'a' | 'b' | 'c';
type Obj = {
    [p in Keys]: unknown;
};
// 等于
type Obj = {
    a: unknown;
    b: unknown;
    c: unknown;
}
复制代码

5.组合使用操作符

有如下枚举值和函数,当传入的key为ERROR时,将会把相关信息log出来。

enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
function printImportant(key: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG', message: string) {
    const num = LogLevel[key];
    if (num <= LogLevel.WARN) {
        console.log('Log level key is:', key);
        console.log('Log level value is:', num);
        console.log('Log level message is:', message);
    }
}
复制代码

这个时候我们的printImportant函数的key参数其实是和enum强绑定的。而上面的demo,我们是手动帮key参数写出了所有的枚举值。但是有两个问题

  1. 当LogLevel枚举值添加,我们需要在enum和函数的参数都进行修改。假设有N个函数依赖了此枚举,那么当枚举变动,相关类型就需要手动更改N次。
  2. 当枚举值很多的情况,手动声明就显得太蠢。

或许我们需要通过LogLevel自动生成一个枚举值的联合类型,而不是手写。这时候我们就可以使用上面说的typeof + keyof了。

enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
复制代码
// 第一步,通过typeof 拿到enum的类型属性
enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
type LogLevelType = typeof LogLevel;
// 等于
type LogLevelType = {
    [x: number]: string;
    readonly ERROR: LogLevel.ERROR;
    readonly WARN: LogLevel.WARN;
    readonly INFO: LogLevel.INFO;
    readonly DEBUG: LogLevel.DEBUG;
};
// 第二步,通过keyof 拿到上面这个枚举类型属性的key组成联合类型
type LogLevelStrings = keyof LogLevelType;
// 等于
type LogLevelStrings = number | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
复制代码

这个LogLevelStrings 就是我们最后需要的啦~整理整理

enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
/**
 * This is equivalent to:
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
type LogLevelStrings = keyof typeof LogLevel;
function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];
    if (num <= LogLevel.WARN) {
        console.log('Log level key is:', key);
        console.log('Log level value is:', num);
        console.log('Log level message is:', message);
    }
}
复制代码

类型断言

当TS没你聪明的时候,类型推断无法推断出正确的类型。需要你手动指定类型,就是类型断言的使用场景。

还是刚刚的User的例子,我们vue3可能会这样初始化user并且请求数据。

interface IUserRsp {
    userId: number;
    userName: string;
    QRCode: string;
    age: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
// 定义初始user对象
const user = ref({});
const requestUser = async () => {
    // 请求user信息
    const res: IUserRsp = await request('/api/user/userInfo');
    //给user赋值
    user.value = res;
    // 当我们想用user里面的accountInfo
    user.value.accountInfo; // error 类型“{}”上不存在属性“userName”。
};
复制代码

这个时候user必然是IUserRsp类型,那么这个user上面必然有accountInfo,但是却报错了。来吧,as!

这样as

1. (user as Ref<IUserRsp>).value.accountInfo; // ok
2. 复制代码

这样as

1. (user.value as IUserRsp).accountInfo; // ok
2. 复制代码

另一种as可能更加常见:as any。这是anyScript的重要来源。

1. (user as any).value.accountInfo; // ok
2. 复制代码

这样就强制把类型扭转啦,在前两种as,ts的推断系统就会在这一行把user判断成IUserRsp。换句话说,你在这一行代码接管了TS的类型推断。但是其实我们大多数的时候其实并不需要as,as在实践场景更多的是工程师无法(type 无法 = 能力不足|懒得去找报错源头|没时间)维护类型系统而使用的偷懒技巧,在大多数使用as的时候,大家为的并不是解决类型系统问题,而是报错问题。而上面的demo,尽管无数次出现在各种项目的代码中,但其实我们可以通过Partial类型工具来让IUserRsp变成可选IUserRsp。

interface IUserRsp {
    userId: number;
    userName: string;
    QRCode: string;
    age: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
// 定义初始user对象
const user = ref<Partial<IUserRsp>>({}); // 使用Partial
const requestUser = async () => {
    // 请求user信息
    const res: IUserRsp = await request('/api/user/userInfo');
    //给user赋值
    user.value = res;
    // 当我们想用user里面的accountInfo
    user.value.accountInfo; // OK
};
复制代码

依然是上面的代码,或许还有一种as

interface IUserRsp {
    userId: number;
    userName: string;
    QRCode: string;
    age: number;
    accountInfo: {
        No: number;
        passWord: number;
    };
}
// 定义初始user对象
const user = ref({} as IUserRsp);
const requestUser = async () => {
    // 请求user信息
    const res: IUserRsp = await request('/api/user/userInfo');
    //给user赋值
    user.value = res;
    // 当我们想用user里面的accountInfo
    user.value.accountInfo; // ok
};
复制代码

这种看起来是安全的,也是当前很多人的首选,因为当我们初始化的时候把user变成了可选的IUserRsp,就意味着在进行如下情况会报错。

 user.value.accountInfo
 // 可能为
 {
   No: number;
   passWord: number;
 }
 或者undefined
 //那么如下调用,可能会提示accountInfo没有password,undefined不能直接调用password
 user.value.accountInfo.passWord
复制代码

当然了,你可以为user制作一个类型守卫,在使用user的时候做一下控制流分析。

const isUser = (user: unknown): user is IUserRsp => !!user['userId'];
复制代码

但是在写这些麻烦的代码和在初始化的时候as,选择后者可能更加方便。但后者的风险在于如果有个小笨蛋在还没request的时候就调用了user.value.accountInfo,GG。

// 定义初始user对象
const user = ref({} as IUserRsp);
// 有个小笨蛋在还没request的时候就调用了accountInfo
user.value.accountInfo.passWord; // GG
const requestUser = async () => {
    // 请求user信息
    const res: IUserRsp = await request('/api/user/userInfo');
    //给user赋值
    user.value = res;
    // 当我们想用user里面的accountInfo
    user.value.accountInfo.passWord; // ok
};
复制代码

so,ban-as please!

类型工具

内置类型工具

完成了对泛形的理解,掌握了类型关键字了,我们就可以开始使用一些类型工具了。上面讲到了vue为我们提供的类型工具PropType。让我们方便的传入一个参数,就可以生成该参数对应props的类型。在TS内部也提供了很多类型工具。

属性修饰工具

Partial 可以将一个interface的所有属性都换成可选的,也就是对接口每一项都添加了可选修饰符 '?'

type Partial<T> = {
    [P in keyof T]?: T[P];
};
复制代码

Required 可以将一个interface的所有属性都换成必选的,也就对接口每一项都删除了可选修饰符 '?'

type Required<T> = {
    [P in keyof T]-?: T[P];
};
复制代码

Readonly 可以将一个interface的所有属性都换成仅读的,也就对接口每一项都添加了仅读修饰符 'readonly'

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
复制代码

结构工具

Record 常用的对象定义方式

type Record<K extends string | number | symbol, T> = {
  [P in K]: T; 
}
复制代码

Exclude 联合类型 T中不存在于 U 中的部分

type Exclude<T, U> = T extends U ? never : T;
type T1 = Exclude<1 | 2, 2>; // 1
复制代码

Pick 同lodash的Pick

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
复制代码

**Omit ** 同lodash的Omit

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
复制代码

怎么实践类型类型工具

在类型系统中对待类型工具就如同在项目系统中对待lodash,尽管类型体操恶心,但是不需要你完成所有具体实现。

推荐ts-toolbelt,该库是目前包含工具类型数量最多的。基本能满足你大多数的常规操作。以满足原生的类型工具不够用,自己又不太会写类型工具、或无法花大量时间去维护类型工具。

一个🌰:

例如我们有以下的Bar接口,并且想把所有的属性都变成可选的。就像最上面 “object、Object 以及 { }”场景一样,只不过这次的对象嵌套比较深了。

interface Boo{
  booA:Array<'3'>
  booB:string
}
interface Bar {
  a: number;
  b: {
    b1: boolean;
    b2: Array<Boo>;
  };
}
复制代码

在原生的Partial方法下,得到的结果好像不能满足我们的需求,

interface Boo {
    booA: Array<'3'>;
    booB: string;
}
interface Bar {
    a: number;
    b: {
        b1: boolean;
        b2: Array<Boo>;
    };
}
const foobar: Partial<Bar> = {
    b: {
        b1: true,
        b2: [ //类型 "{ booA: "3"[]; }" 中缺少属性 "booB",但类型 "Boo" 中需要该属性。
            {
                booA: ['3']
            }
        ]
    }
};
复制代码

因为原生的Partial只能转化第一层索引为可选的。所以我们需要深层的去转换为可选,也就是我们需要递归的去将每一项都转为可选的,具体的就是,如果该项为一个对象,我们就递归调用这个可选方法。

type Partial<T> = {
    [P in keyof T]?: T[P];
};
// to
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
// Ok
const foobar: DeepPartial<Bar> = {
  b:{
    b1:true,
    b2:[{
      booA:['3']
    }]
  }
};
复制代码

比起如此或许我们可以直接在ts-toolbelt找到 partial 在第二个泛形选择deep,然后我们就得到了一个现成DeepPartial。

import { O } from 'ts-toolbelt';
// OK
const foobar: O.Partial<Bar,'deep'> = {
    b: {
        b1: true,
        b2: [
            {
                booA: ['3']
            }
        ]
    }
};
复制代码

这里我们不再赘述ts-toolbelt的Partial实现逻辑,另一个值得关注的是ts-toolbelt的Partial单测。这里通过TDD的思想完成了目标效果的确立,来保障类型工具的安全。这点是我们值得借鉴的,比较不是每一次需要的类型工具都可以从ts-toolbelt找到答案,总有自己写的时候。

// https://github.com/millsp/ts-toolbelt/blob/master/tests/Object.ts
type PARTIAL_O_FLAT = {
    a?: string,
    b?: number;
    c?: {a: 'a'} & {b: 'b'};
    d?: 'string0';
    readonly e?: 'string1';
    readonly f?: 0;
    g?: O;
    h?: 1;
    j?: 'a';
    k?: {a: {b: string}};
    x?: () => 1;
};
checks([
    check<O.Partial<O, 'flat'>, PARTIAL_O_FLAT, Test.Pass>(),
    check<O.Path<O.Partial<O, 'deep'>, ['g', 'g']>, O.Partial<O, 'deep'> | undefined, Test.Pass>(),
])
复制代码

如何通过TDD完成一个类型工具

在TDD的开发模式下,我们会先完成测试用例(目标),再进行对应功能的代码开发(完成目标)。当所有的测试用例通过,就完成了所有的预期功能。当有一个测试用例没有完成,代码就会报错。相当于写了一群监督者检查你的每一个小功能,除了可以帮你检查是否完成预期任务,还可以避免按下葫芦浮起瓢的情况。

假如我们需要一个完成类型工具ObjPaths,拿到一个interface所有的Path路径,以下注释为预期。

type Cases1 = {
  Foo: string;
  Bar: number;
};
type caseRes1 = ObjPaths<Cases1> // ObjPaths需要把Case1转成"Foo" | "Bar"
interface Cases2 {
  Foo: {
    A: string;
    B: string;
  };
  Bar: {
    C: number;
  };
}
type caseRes2 = ObjPaths<Cases2> // ObjPaths需要把Case2转成"Foo" | "Bar" | "Foo.A" | "Foo.B" | "Bar.C"
type Cases3 = {
  Foo: {
    A: string;
    B: string;
  };
  Bar: {
    C: Array<{
      Foo: string;
      Bal: number;
    }>;
  };
};
// ObjPaths需要把Case2转成 "Foo" | "Foo.A" | "Foo.B" | "Bar" | "Bar.C"|"`Bar.C[${number}]`"|`Bar.C[${number}].Bal`|`Bar.C.${number}.Bal`
type caseRes3 = ObjPaths<Cases3> 
复制代码

基于我们的预期,我们可以根据上面的这些小想法,完成单元测试。js的单测我们通常会使用jest,那么TS我们可以自己写常用的判断方法,这里我们用了type-challenges的一些断言方法。

import type { Equal, Expect, ExpectExtends } from '@type-challenges/utils'
Equal<A,B> // 如果A和B在类型上是一样的,就回返回true,否则返回false
ExpectExtends<A,B>// 如果A和B在类型上是A包含B的,就回返回true,否则返回false
Expect<A> //如果A不是true就报错
复制代码

以此得到

type cases = [
  Expect<Equal<ObjPaths<Cases1>, "Foo" | "Bar">>,
  Expect<Equal<ObjPaths<Cases2>, "Foo" | "Bar" | "Foo.A" | "Foo.B" | "Bar.C">>,
  Expect<ExpectExtends<ObjPaths<Cases3>, `Bar.C[${number}]`>>,
  Expect<ExpectExtends<ObjPaths<Cases3>, `Bar.C[${number}].Bal`>>,
  Expect<ExpectExtends<ObjPaths<Cases3>, `Bar.C.${number}.Bal`>>,
  Expect<ExpectExtends<ObjPaths<Cases3>, "Foo" | "Foo.A" | "Foo.B" | "Bar" | "Bar.C">>
];
复制代码

接下来,我们要做的就是解决这个case所有的报错,case解决就算工具完成。TS playground链接

/*  -判断当前索引是否为字符串或数字
    -如果不是则返回 never
    -如果是则根据是否是第一层返回不同的结果的 key
    -并且联合上 K 索引的值
    -如果是对象则继续递归联合 */
type IsNumber<T> = T extends number ? `[${T}]` : never;
type ObjPaths<
  T extends object,
  Flag extends boolean = false,
  K extends keyof T = keyof T
> = K extends string | number
  ?
      | (Flag extends true ? `.${K}` | IsNumber<K> : `${K}`)
      | (T[K] extends object
          ? `${Flag extends true ? `.${K}` | IsNumber<K> : `${K}`}${ObjPaths<
              T[K],
              true
            >}`
          : never)
  : never;
复制代码

如何找到类型工具

对于类型编程,国内外的环境可能还是有比较大的差距。所以当你在使用百度去查找你想要的类型工具,基本是不如自己撸一个来的快。但是Google相对来说会很容易找到你需要的工具答案。如果内置函数,工具库都无法满足你的诉求时,建议去Google进行搜索引擎努力。另一个是 type-challenges 类型版本的leetCode,内置了非常多的类型问题,你也可以从这上面找到对应的答案或实现灵感。

总结

  1. 本篇文档讲述了一些基本的类型操作,和一些小的知识点,讲了一些实际场景的类型问题以及如何完成一个类型工具,并介绍了一些常用的类型工具轮子。类型收敛、类型工具非常重要,优化类型收敛、类型工具都是TS官方近两年迭代的主题,比如关键词satisfies,Awaited类型工具、模版字符串、优化控制流分析等等。
  2. 一篇文章无法覆盖所有的类型API,对于类型的维护,并不需要知道所有的API,但需要清晰将类型收敛到什么程度,才能够满足你的需求。ban了as,ban了any,学会基本的类型操作,知道2022年的类型系统可以做到什么,能够找到你想要的类型工具,积极的使用内置和封装的类型工具合理的去维护项目的类型系统。或许用类型系统写一个国际象棋回报相对较低,但是用用Omit,Google找个ObjPaths难度并不大。

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:web前端面试题库


相关文章
|
7天前
|
前端开发 JavaScript 开发者
前端项目代码规范工具 (ESLint. Prettier. Stylelint. TypeScript)
前端项目代码规范工具 (ESLint. Prettier. Stylelint. TypeScript)
|
29天前
|
JavaScript 前端开发 安全
2024年前端开发新趋势:TypeScript、Deno与性能优化
2024年前端开发迎来新趋势:TypeScript 5.0引入装饰器正式支持、const类型参数及枚举改进;Deno 1.42版推出JSR包注册表、增强Node.js兼容性并优化性能;性能优化策略涵盖代码分割、懒加载及现代构建工具的应用。这些变化推动前端开发向更高效率和安全性发展。
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
66 1
|
3月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
2月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
4月前
|
开发者 自然语言处理 存储
语言不再是壁垒:掌握 JSF 国际化技巧,轻松构建多语言支持的 Web 应用
【8月更文挑战第31天】JavaServer Faces (JSF) 框架提供了强大的国际化 (I18N) 和本地化 (L10N) 支持,使开发者能轻松添加多语言功能。本文通过具体案例展示如何在 JSF 应用中实现多语言支持,包括创建项目、配置语言资源文件 (`messages_xx.properties`)、设置 `web.xml`、编写 Managed Bean (`LanguageBean`) 处理语言选择,以及使用 Facelets 页面 (`index.xhtml`) 显示多语言消息。通过这些步骤,你将学会如何配置 JSF 环境、编写语言资源文件,并实现动态语言切换。
47 1
|
4月前
|
JavaScript 前端开发 编译器
TypeScript:一场震撼前端开发的效率风暴!颠覆想象,带你领略前所未有的编码传奇!
【8月更文挑战第22天】TypeScript 凭借其强大的静态类型系统和丰富的工具支持,已成为前端开发的优选语言。它通过类型检查帮助开发者早期发现错误,显著提升了代码质量和维护性。例如,定义函数时明确参数类型,能在编译阶段捕获类型不匹配的问题。TypeScript 还提供自动补全功能,加快编码速度。与 Angular、React 和 Vue 等框架的无缝集成进一步提高了开发效率,使 TypeScript 成为现代前端开发中不可或缺的一部分。
46 1
|
4月前
|
前端开发 JavaScript 安全
【前端开发新境界】React TypeScript融合之路:从零起步构建类型安全的React应用,全面提升代码质量和开发效率的实战指南!
【8月更文挑战第31天】《React TypeScript融合之路:类型安全的React应用开发》是一篇详细教程,介绍如何结合TypeScript提升React应用的可读性和健壮性。从环境搭建、基础语法到类型化组件、状态管理及Hooks使用,逐步展示TypeScript在复杂前端项目中的优势。适合各水平开发者学习,助力构建高质量应用。
70 0
|
4月前
|
JavaScript 前端开发 安全
【技术革新】Vue.js + TypeScript:如何让前端开发既高效又安心?
【8月更文挑战第30天】在使用Vue.js构建前端应用时,结合TypeScript能显著提升代码质量和开发效率。TypeScript作为JavaScript的超集,通过添加静态类型检查帮助早期发现错误,减少运行时问题。本文通过具体案例展示如何在Vue.js项目中集成TypeScript,并利用其类型系统提升代码质量。首先,使用Vue CLI创建支持TypeScript的新项目,然后构建一个简单的待办事项应用,通过定义接口描述数据结构并在组件中使用类型注解,确保代码符合预期并提供更好的编辑器支持。
91 0
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
189 2