TypeScript 高级类型 + CSS 三列布局:从类型体操到样式工程的进阶之路
读完这篇,你会彻底搞懂 Pick、Omit、Partial、Exclude、ReturnType、Record 等工具类型的实现原理,以及 Flex/Grid 布局背后的 BFC/GFC 底层机制。
前言:为什么把 TS 和 CSS 放一起讲?
很多前端同学在学习时,会把 TypeScript 和 CSS 割裂来看。但实际上,它们有一个共同的核心思想:约束与规则。
- TypeScript 通过类型系统约束变量的形状,让代码在编译阶段就发现错误。
- CSS 通过格式化上下文(Formatting Context) 约束元素的排列规则,让页面布局稳定可控。
本文会从这两个维度出发,带你一次性打通"类型体操"和"样式工程"的核心知识点。
第一部分:TypeScript 高级类型——类型的安全网
一、从接口定义开始
先定义一个用户信息的接口(Interface):
interface User {
id: number;
name: string;
age: number;
email: string;
}
这个接口描述了"用户"这个对象应有的形状——必须有 id、name、age、email 四个字段,且类型分别是 number、string、number、string。
但在实际业务中,我们并不是任何时候都需要全部字段。比如:
- 用户列表预览:只需要
id和name - 删除用户接口:需要
id,但不能暴露email - 编辑用户信息:所有字段都可选,因为用户可能只改其中一项
这时候就需要"类型体操"——从已有类型中挑选或排除部分字段,生成新的类型。
二、Pick<T, Keys>:从类型中挑选需要的字段
type UserPreview = Pick<User, 'id' | 'name'>;
const u: UserPreview = {
id: 1,
name: '张三',
};
// ✅ 合法,因为没有 age 和 email 也不会报错
底层原理:Pick 是 TypeScript 内置的工具类型,它的实现大致如下:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
K extends keyof T约束 K 必须是 T 的键之一[P in K]遍历 K 中的所有键T[P]取原类型中对应键的值类型
通俗理解:
Pick就像是一个"筛选器",你告诉它"我要哪些字段",它就帮你从原对象中抽出一份"复印件",只包含你指定的字段。
三、Omit<T, Keys>:排除不需要的字段
type UserSafe = Omit<User, 'email'>;
const safeUser: UserSafe = {
id: 2,
name: '李四',
age: 25,
// email 不能写,因为被排除了
};
Omit 和 Pick 是相反的操作:
Pick:只要指定的字段Omit:不要指定的字段,其他全要
底层原理:Omit 等价于 Pick<T, Exclude<keyof T, K>>。
这个公式拆解开来就是:
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
我们一步步分析:
| 步骤 | 表达式 | 含义 | |||
|---|---|---|---|---|---|
| 1 | keyof T | 获取 T 的所有键,得到 `'id' | 'name' | 'age' | 'email'` |
| 2 | Exclude<..., K> | 从所有键中剔除 K,剩下要保留的键 | |||
| 3 | Pick<T, ...> | 用剩下的键从 T 中挑选出新类型 |
实战对比:
// 2.ts 中的自定义实现
type UserKeys = keyof User; // 'id' | 'name' | 'age' | 'email'
type KeepKeys = Exclude<UserKeys, 'email'>; // 'id' | 'name' | 'age'
type MyOmitUser = Pick<User, KeepKeys>; // 等价于 Omit<User, 'email'>
这就验证了:Omit 本质上就是先剔除再挑选的组合操作。
四、Partial<T>:把所有字段变成可选
type PartialUser = Partial<User>;
// 可以只传部分字段
const partialUser: PartialUser = {
name: '王五',
age: 18
};
// 甚至可以传空对象
const emptyObj: PartialUser = {};
底层原理:
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
? 表示可选属性。Partial 就是把接口中所有字段都加上 ?。
使用场景:更新接口(PATCH 请求)—— 用户只修改了姓名,不需要把 id、age、email 都传一遍。
五、Record<Keys, Type>:构建字典/映射类型
type Dict = Record<string, number>;
const obj: Dict = { a: 1, b: 2 };
// 键可以是任意字符串,值必须是 number
Record 不限制具体的键名,只限制键的类型和值的类型。
实际应用:错误码映射表
type ErrorMsgMap = Record<number, string>;
const errorMessage: ErrorMsgMap = {
400: '请求参数错误',
401: '未登录,请重新登录',
403: '权限不足,禁止访问',
404: '资源找不到',
500: '服务器内部错误'
};
function getErrorMsg(code: number) {
return errorMessage[code] ?? '未知错误';
}
为什么用
Record而不用普通对象?
Record保证了所有值的类型一致(都是string),同时支持动态添加任意数字键,非常适合做错误码、配置项这类字典结构。
六、ReturnType<T>:提取函数返回值类型
function fn() {
return { x: 1, y: 2 };
}
type fnReturn = ReturnType<typeof fn>; // { x: number; y: number }
关键点:必须用 typeof fn 获取函数类型,再传入 ReturnType。
为什么这样设计?
// ❌ 错误:fn 是值,不是类型
type Wrong = ReturnType<fn>;
// ✅ 正确:先用 typeof 把值转成类型
type Correct = ReturnType<typeof fn>;
TypeScript 的类型层面和值层面是隔离的,ReturnType 只接受类型,不接受值。typeof 充当了"桥梁"。
七、Exclude<T, U>:从联合类型中剔除成员
type All = 'id' | 'name' | 'age' | 'email';
type AfterExclude = Exclude<All, 'email'>; // 'id' | 'name' | 'age'
底层原理:Exclude 是条件类型的应用:
type MyExclude<T, U> = T extends U ? never : T;
它的逻辑是:遍历联合类型 T,如果某个成员能赋值给 U,就剔除它(返回 never),否则保留。
Omit和Exclude的区别:
Exclude处理联合类型(如'a' | 'b' | 'c')Omit处理对象类型(如interface User),内部实际调用了Exclude
八、工具类型总结一览
| 工具类型 | 作用 | 适用场景 |
|---|---|---|
Pick<T, K> | 挑选部分字段 | 列表预览、精简数据 |
Omit<T, K> | 排除部分字段 | 隐藏敏感字段、API 脱敏 |
Partial<T> | 所有字段变可选 | PATCH 更新、表单校验 |
Record<K, T> | 构建字典/映射 | 错误码、配置项 |
ReturnType<T> | 提取函数返回类型 | 保持类型与实现同步 |
Exclude<T, U> | 从联合类型中剔除 | 配合 Pick 实现 Omit |
keyof T | 获取对象的所有键 | 遍历、约束、组合 |
第二部分:CSS 三列布局——从 BFC 到 Flex/Grid
说完了 TypeScript 的类型体操,我们切换到 CSS 布局。两者看似无关,但都有一个共同的主题:规则与隔离。
一、什么是三列布局?
经典的 PC 端三列布局:
┌──────────┬──────────────────┬──────────┐
│ 左侧栏 │ 主要内容区 │ 右侧栏 │
│ 固定宽度 │ 自适应宽度 │ 固定宽度 │
└──────────┴──────────────────┴──────────┘
- 左右两列固定宽度(如 200px)
- 中间列自适应剩余宽度
- 重要的内容优先加载:HTML 中先写中间列(main),两侧广告/导航后加载
这种布局在后台管理系统、博客、门户网站中极其常见。
二、格式化上下文(Formatting Context)是基石
要理解各种布局方案,必须先搞懂一个底层概念:格式化上下文(Formatting Context,简称 FC)。
什么是格式化上下文?
它是一个独立的渲染区域,规定了内部子元素的排列规则。
外层 FC 的规则不会影响内层 FC,内层 FC 也不会影响到外层。
这就实现了布局的隔离。
| 格式化上下文 | 触发方式 | 排列规则 |
|---|---|---|
| BFC(Block FC) | 根元素、float、position: absolute/fixed、overflow: hidden 等 | 块级元素从上到下垂直排列 |
| IFC(Inline FC) | display: inline / inline-block | 行内元素从左到右水平排列 |
| FFC(Flex FC) | display: flex / inline-flex | 弹性盒子排列(主轴/交叉轴) |
| GFC(Grid FC) | display: grid / inline-grid | 网格二维排列 |
关键理解:
display: flex并不是"没有 BFC",而是开启了一个新的 FC(FFC),这个 FC 有自己的排列规则,不受外部 BFC 的影响。
三、Flex 实现三列布局
<style>
.layout {
display: flex; /* 开启 FFC(Flex Formatting Context) */
height: 100vh;
}
.sidebar {
width: 200px;
background: #f0f0f0;
}
.content {
flex: 1; /* 占据剩余所有空间 */
background: #fff;
}
</style>
<div class="layout">
<aside class="sidebar left">Left</aside>
<main class="content">Main Content</main>
<aside class="sidebar right">Right</aside>
</div>
为什么 flex: 1 能让中间列自适应?
flex: 1是flex-grow: 1; flex-shrink: 1; flex-basis: 0%的缩写- 它告诉浏览器:"在分配剩余空间时,请给我 1 份"
- 左右固定宽度(200px),剩余空间全部给中间列
四、Grid 实现三列布局(更优雅)
<style>
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px; /* 左固定 + 中间自适应 + 右固定 */
height: 100vh;
}
</style>
<div class="layout">
<aside id="left">Left</aside>
<main class="content">Main Content</main>
<aside id="right">Right</aside>
</div>
Grid 的优势:
- 直接在容器层面定义列宽,无需在子元素上设置
flex - 更直观地表达"三列"的布局意图
- 支持二维布局(行 + 列),比 Flex 更强大
FFC vs GFC:Flex 是一维布局(只能控制行或列),Grid 是二维布局(同时控制行和列)。三列布局两者都能做,但 Grid 的代码更简洁。
五、为什么要开启新的格式化上下文?
.layout {
display: flex; /* 开启 FFC */
}
这段代码至少做了两件事:
- 改变了排列规则:子元素不再按照"块级元素从上到下"的 BFC 规则排列,而是按照 Flex 的"主轴方向排列"
- 创建了隔离区域:
.layout内部是一个独立的 FFC,它内部的浮动、边距等不会影响外部元素
这也是为什么 overflow: hidden 能清除浮动的原因——它触发了 BFC,让父元素包裹住内部浮动元素。
六、布局方案对比
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| Flex | display: flex + flex: 1 | 简单直观、兼容性好 | 一维布局,复杂场景需嵌套 |
| Grid | grid-template-columns: 200px 1fr 200px | 最简洁、二维布局能力 | 旧浏览器支持度稍差 |
| Float | float: left + 负边距 | 历史经典 | 需清除浮动、代码复杂 |
| 定位 | position: absolute + 左右定位 | 灵活 | 脱离文档流,需额外处理高度 |
现代推荐:优先用 Grid 或 Flex,除非有特殊的旧浏览器兼容需求。
第三部分:TypeScript × CSS——思想共鸣
把两部分内容放在一起,你会发现它们底层逻辑惊人地相似:
| 概念 | TypeScript | CSS |
|---|---|---|
| 约束规则 | 类型系统限制值的形状 | 格式化上下文限制布局规则 |
| 隔离机制 | 模块作用域、类型声明 | BFC/FFC/GFC 隔离内外布局 |
| 组合能力 | Pick/Omit/Partial 组合类型 | Flex/Grid 组合排列元素 |
| 默认行为 | any 类型(无约束) | BFC(块级元素默认排列) |
| 显式声明 | 标注 : User | 设置 display: flex |
一个类比:类型体操 = 布局体操
Pick就像从 DOM 中挑选特定元素进行操作Omit就像用display: none排除不想显示的元素Partial就像元素属性都是可选的,传了才生效Record就像 CSS 变量映射表,统一管理样式值
总结
TypeScript 工具类型核心速记
Pick<T, K>:只要 KOmit<T, K>:不要 K(等价于Pick<T, Exclude<keyof T, K>>)Partial<T>:全部可选Record<K, T>:键类型 K,值类型 T 的字典ReturnType<T>:提取函数返回类型Exclude<T, U>:从联合类型 T 中剔除 U
CSS 三列布局核心速记
- BFC/FFC/GFC:不同格式化上下文对应不同排列规则
- Flex 方案:
display: flex+flex: 1自适应 - Grid 方案:
display: grid+grid-template-columns: 200px 1fr 200px - 中间先加载:HTML 中 main 优先于两侧
延伸思考:
你在日常开发中,有没有遇到过"类型太宽松导致运行时 Bug"或者"布局被意外影响"的情况?欢迎在评论区分享你的故事。如果对Exclude和Omit的区别还有疑问,或者想了解更深层次的条件类型(infer、extends),也可以留言,我来继续为你拆解。