12个TypeScript奇淫技巧你需要掌握😏😏😏

哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。当我们学习前端进阶的时候,TypeScript是重中之重。学会TypeScript将大大提高我们的开发效率,所以一定要掌握好TypeScript。

这篇文章让我们手写12个TypeScript奇淫技巧,并逐行分析,最近秋招就要来了,学会手写TypeScript例子就可以大胆和面试官畅所欲言和自信谈薪啦!😏😏😏 击败TypeScript并嚼碎了服用将会给我们带来大量经验,你们准备好了吗?话不多说,现在开启我们今天的前端丛林冒险之旅吧!


1. Partial 深度递归版

场景 更新一个嵌套对象,但只想传"任意深度"的片段
易错 原生 Partial 只擦一层,{ address: { city: undefined } } 会把外层 address 整个覆盖
进化 手写 DeepPartial,同时保留"数组"结构
ts 复制代码
// 工具
type DeepPartial<T> = T extends object
  ? T extends readonly any[]
    ? ReadonlyArray<DeepPartial<T[number]>>
    : { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

//  domain
interface User {
  id: number;
  profile: {
    name: string;
    avatar: { url: string; size: [number, number] };
  };
  tags: string[];
}

//  使用:只改头像宽度
const patch: DeepPartial<User> = {
  profile: { avatar: { size: [undefined, 128] } },
};

//  单元测试
function testDeepPartial() {
  const original: User = {
    id: 1,
    profile: { name: 'Tom', avatar: { url: 'a.png', size: [64, 64] } },
    tags: ['vip'],
  };
  const merged = merge(original, patch); // 手写 merge 或 lodash.merge
  console.assert(merged.profile.avatar.size[0] === 64); // 未被覆盖
  console.assert(merged.profile.avatar.size[1] === 128);
}

2. Required 带"运行时检查"

场景 后端返沪配置字段全部可选,但前端初始化时必须补齐
进化 Required 丢给 zod,自动生成"运行时校验 + 类型"
ts 复制代码
import { z } from 'zod';

const ConfigSchema = z.object({
  apiUrl: z.string().url(),
  timeout: z.number().int().positive(),
  retry: z.boolean(),
});

//  推导后全部是必填
type Config = z.infer<typeof ConfigSchema>; // Required<{apiUrl:string; timeout:number; retry:boolean}>

//  使用:本地 config.json 可能缺字段
function loadConfig(): Config {
  const raw = JSON.parse(readFileSync('./config.json', 'utf-8'));
  return ConfigSchema.parse(raw); // 缺字段会直接抛错
}

3. Readonly 冻结"React 状态"

场景 把 Redux Toolkit 的 state 彻底锁死,防止误赋值
进化 结合 immerDraft 类型,做到"写时复制、读时只读"
ts 复制代码
import type { Draft } from 'immer';

interface State {
  user: { name: string };
  list: number[];
}

const initialState: Readonly<State> = {
  user: { name: 'Alice' },
  list: [1, 2, 3],
};

//  在 reducer 里只能改 Draft,外面拿到的是 Readonly
function reducer(state: Readonly<State>, action: any) {
  return produce(state, (draft: Draft<State>) => {
    draft.list.push(4); // ✅ 允许
  });
}

//  组件里
const state: Readonly<State> = useSelector(s => s);
state.list.push(5); // ❌ TS 报错:Property 'push' does not exist on readonly number[]

4. Pick<T,K> 造"最小列"数据库查询

场景 ORM 只查需要用到的列,减少网络传输
进化 Pick 做成"通用返回类型",再配合 sql tag
ts 复制代码
//  通用工具:指定列查询
type PickCol<T, K extends keyof T> = Pick<T, K>;

interface UserTable {
  id: number;
  name: string;
  password: string;
  createdAt: Date;
}

//  只查两列
async function getUserNames(): Promise<PickCol<UserTable, 'id' | 'name'>[]> {
  return sql<{ id: number; name: string }[]>`SELECT id, name FROM user`;
}

//  调用方永远拿不到 password
const list = await getUserNames();
list[0].password; // ❌ TS 报错

5. Omit<T,K> 造"公有类型"同时保持单一代码源

场景 同一个 User 实体,内部服务需要 password,开放给前端的 DTO 不需要
进化 Omit 生成 PublicUser,但不手写第二份接口,保证"字段新增/删除"时两边同步
ts 复制代码
interface User {
  id: number;
  name: string;
  password: string;
  internalRemark: string;
}

//  所有对外接口都用 PublicUser
export type PublicUser = Omit<User, 'password' | 'internalRemark'>;

//  新增字段 email 时,只改 User,PublicUser 自动同步

6. Record<K,T> 做"字典"时给 value 加"默认值函数"

场景 权限字典初始化时,希望缺失 key 自动兜底
进化 封装 getWithDefault 高阶函数,保持 Record 类型安全
ts 复制代码
type Role = 'admin' | 'user' | 'guest';
const defaultPerms = {
  admin: { read: true, write: true },
  user: { read: true, write: false },
  guest: { read: false, write: false },
} satisfies Record<Role, { read: boolean; write: boolean }>;

function getPerm<R extends Role>(r: R): Record<Role, typeof defaultPerms[R]> {
  return defaultPerms[r] ?? defaultPerms.guest;
}

//  使用
const p = getPerm('user');
p.write; // false

7. Exclude & Extract 玩"可分配路线图"

场景 一个组件只接受部分颜色,需要把系统级颜色过滤出来
ts 复制代码
type SystemColor = 'red' | 'green' | 'blue' | 'yellow';
type AllowedColor = Extract<SystemColor, 'red' | 'green'>; // 'red' | 'green'
type DisallowedColor = Exclude<SystemColor, AllowedColor>; // 'blue' | 'yellow'

//  再进一步做运行时 map 检查
const COLOR_MAP: Record<AllowedColor, string> = {
  red: '#ff0000',
  green: '#00ff00',
};

function setColor(c: AllowedColor) {
  document.body.style.color = COLOR_MAP[c];
}

8. NonNullable 与"空值断言"组合拳

场景 数组查找后立刻使用,不想写一堆 if
进化 封装"查找 or 抛"函数,返回 NonNullable<T>
ts 复制代码
function findStrict<T, K extends keyof T>(
  arr: T[],
  key: K,
  value: T[K]
): NonNullable<T> {
  const item = arr.find(v => v[key] === value);
  if (!item) throw new Error(`Not found`);
  return item!; // ! 断言后类型擦除 null|undefined
}

//  使用
const users: ({ id: number; name: string } | null)[] = [
  { id: 1, name: 'A' },
  null,
];
const u = findStrict(users, 'id', 1); // 返回类型 {id:number; name:string},null 被排除

9. ReturnType + Parameters 做"函数装饰器"不改类型

场景 给任意 async 函数加"自动重试"包裹,但保持原类型
ts 复制代码
function withRetry<F extends (...args: any[]) => Promise<any>>(fn: F) {
  return async (...args: Parameters<F>): Promise<ReturnType<F>> => {
    let lastErr: unknown;
    for (let i = 0; i < 3; i++) {
      try {
        return (await fn(...args)) as ReturnType<F>;
      } catch (e) {
        lastErr = e;
        await new Promise(r => setTimeout(r, 100 * 2 ** i));
      }
    }
    throw lastErr;
  };
}

//  使用:类型完全不变
const fetchUser = async (id: number) => ({ id, name: 'A' });
const safeFetch = withRetry(fetchUser);
//  safeFetch 签名仍是 (id:number)=>Promise<{id:number; name:string}>

10. ConstructorParameters + InstanceType 造"通用工厂"

场景 写 DI 容器,需要根据"类"自动产生工厂函数
ts 复制代码
class Logger {
  constructor(public prefix: string) {}
  log(msg: string) {
    console.log(`[${this.prefix}] ${msg}`);
  }
}

//  通用工厂
function createFactory<T extends abstract new (...args: any) => any>(Cls: T) {
  return (...args: ConstructorParameters<T>): InstanceType<T> =>
    new Cls(...args);
}

//  使用
const makeLogger = createFactory(Logger);
const log = makeLogger('App'); // 类型自动推导为 Logger
log.log('started');

11. ThisParameterType 给"方法链"锁上下文

场景 jQuery 风格链式调用,确保 this 指向不被丢失
ts 复制代码
interface Query {
  where<K extends keyof User>(
    field: K,
    val: User[K]
  ): ThisParameterType<typeof where>;
}
function where(this: Query[], field: any, val: any) {
  return this.filter(u => u[field] === val);
}

const chain: Query & ThisParameterType<typeof where> = [] as any;
chain.where('name', 'Tom').where('age', 18); // 全程 this 被约束

12. 组合技:API 返回 → 隐藏敏感 → 局部更新 → 深度合并

把上面所有知识串一个完整案例:

ts 复制代码
//  1. 原始实体
interface User {
  id: number;
  name: string;
  password: string;
  profile: { avatar: { url: string }; bio: string };
}

//  2. 返回给前端的安全 DTO
type UserDto = Omit<User, 'password'>;

//  3. 更新时允许深度局部
type UserPatch = DeepPartial<UserDto>;

//  4. 实现:查 → 删敏感 → 合并 → 返
async function updateUser(id: number, patch: UserPatch): Promise<UserDto> {
  const raw = await db.user.findUnique({ where: { id } });
  if (!raw) throw new Error('404');
  const sanitized: UserDto = exclude(raw, ['password']); // 手写工具
  const merged = deepMerge(sanitized, patch);
  await db.user.update({ where: { id }, data: merged });
  return merged;
}

一键复制清单

技巧 一句话记忆
DeepPartial 更新嵌套对象,不丢兄弟字段
Required + zod 类型 & 运行时一次搞定
Readonly + immer 状态不可变,写时复制
Pick 列 查多少返多少,带宽省 70%
Omit 敏感 同一份实体,对外自动脱敏
Record 字典 枚举 key 全铺平,再配默认值函数
Exclude/Extract 联合类型做"白名单/黑名单"
NonNullable 去掉 null,后面代码不再 ?.
ReturnType + Parameters 装饰器、包裹器必备,类型零丢失
ConstructorParameters + InstanceType 工厂函数一行代码泛型化
ThisParameterType 方法链、库作者专用,防 this 丢失

把上面 12 组代码全部跑通,你就在项目中把 TypeScript 内置工具类型"吃干抹净"了。

需要哪一条再单独抽出来封装为 npm utils,就能让团队永远告别"手写重复类型"的悲剧。祝你编码愉快!

相关推荐
一个大苹果3 小时前
setTimeout延迟超过2^31立即执行?揭秘JavaScript定时器的隐藏边界
javascript
开源之眼3 小时前
React中,useState和useReducer有什么区别
前端
普郎特3 小时前
"不再迷惑!用'血缘关系'彻底搞懂JavaScript原型链机制"
前端·javascript
绝无仅有3 小时前
mysql性能优化实战与总结
后端·面试·github
可观测性用观测云3 小时前
前端错误可观测最佳实践
前端
恋猫de小郭3 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
一枚前端小能手3 小时前
「周更第3期」实用JS库推荐:Lodash
前端·javascript
艾小码3 小时前
Vue组件到底怎么定义?全局注册和局部注册,我踩过的坑你别再踩了!
前端·javascript·vue.js
ByteBlossom3 小时前
MySQL 面试场景题之如何处理 BLOB 和CLOB 数据类型?
数据库·mysql·面试