TypeScript 高级类型 + CSS 三列布局:从类型体操到样式工程的进阶之路

TypeScript 高级类型 + CSS 三列布局:从类型体操到样式工程的进阶之路

读完这篇,你会彻底搞懂 Pick、Omit、Partial、Exclude、ReturnType、Record 等工具类型的实现原理,以及 Flex/Grid 布局背后的 BFC/GFC 底层机制。


前言:为什么把 TS 和 CSS 放一起讲?

很多前端同学在学习时,会把 TypeScript 和 CSS 割裂来看。但实际上,它们有一个共同的核心思想:约束与规则

  • TypeScript 通过类型系统约束变量的形状,让代码在编译阶段就发现错误。
  • CSS 通过格式化上下文(Formatting Context) 约束元素的排列规则,让页面布局稳定可控。

本文会从这两个维度出发,带你一次性打通"类型体操"和"样式工程"的核心知识点。


第一部分:TypeScript 高级类型------类型的安全网

一、从接口定义开始

先定义一个用户信息的接口(Interface):

typescript 复制代码
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
}

这个接口描述了"用户"这个对象应有的形状------必须有 idnameageemail 四个字段,且类型分别是 numberstringnumberstring

但在实际业务中,我们并不是任何时候都需要全部字段。比如:

  • 用户列表预览:只需要 idname
  • 删除用户接口:需要 id,但不能暴露 email
  • 编辑用户信息:所有字段都可选,因为用户可能只改其中一项

这时候就需要"类型体操"------从已有类型中挑选排除部分字段,生成新的类型。


二、Pick<T, Keys>:从类型中挑选需要的字段

typescript 复制代码
type UserPreview = Pick<User, 'id' | 'name'>;

const u: UserPreview = {
  id: 1,
  name: '张三',
};
// ✅ 合法,因为没有 age 和 email 也不会报错

底层原理Pick 是 TypeScript 内置的工具类型,它的实现大致如下:

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>:排除不需要的字段

typescript 复制代码
type UserSafe = Omit<User, 'email'>;

const safeUser: UserSafe = {
  id: 2,
  name: '李四',
  age: 25,
  // email 不能写,因为被排除了
};

OmitPick 是相反的操作:

  • Pick只要指定的字段
  • Omit不要指定的字段,其他全要

底层原理Omit 等价于 Pick<T, Exclude<keyof T, K>>

这个公式拆解开来就是:

typescript 复制代码
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 中挑选出新类型 |

实战对比

typescript 复制代码
// 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>:把所有字段变成可选

typescript 复制代码
type PartialUser = Partial<User>;

// 可以只传部分字段
const partialUser: PartialUser = {
  name: '王五',
  age: 18
};

// 甚至可以传空对象
const emptyObj: PartialUser = {};

底层原理

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

? 表示可选属性。Partial 就是把接口中所有字段都加上 ?

使用场景:更新接口(PATCH 请求)------ 用户只修改了姓名,不需要把 id、age、email 都传一遍。


五、Record<Keys, Type>:构建字典/映射类型

typescript 复制代码
type Dict = Record<string, number>;
const obj: Dict = { a: 1, b: 2 };
// 键可以是任意字符串,值必须是 number

Record 不限制具体的键名,只限制键的类型值的类型

实际应用:错误码映射表

typescript 复制代码
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>:提取函数返回值类型

typescript 复制代码
function fn() {
  return { x: 1, y: 2 };
}

type fnReturn = ReturnType<typeof fn>;  // { x: number; y: number }

关键点 :必须用 typeof fn 获取函数类型,再传入 ReturnType

为什么这样设计?

typescript 复制代码
// ❌ 错误:fn 是值,不是类型
type Wrong = ReturnType<fn>;

// ✅ 正确:先用 typeof 把值转成类型
type Correct = ReturnType<typeof fn>;

TypeScript 的类型层面值层面 是隔离的,ReturnType 只接受类型 ,不接受typeof 充当了"桥梁"。


七、Exclude<T, U>:从联合类型中剔除成员

typescript 复制代码
type All = 'id' | 'name' | 'age' | 'email';
type AfterExclude = Exclude<All, 'email'>;  // 'id' | 'name' | 'age'

底层原理Exclude 是条件类型的应用:

typescript 复制代码
type MyExclude<T, U> = T extends U ? never : T;

它的逻辑是:遍历联合类型 T,如果某个成员能赋值给 U,就剔除它(返回 never),否则保留。

OmitExclude 的区别

  • 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) 根元素、floatposition: absolute/fixedoverflow: 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 实现三列布局

html 复制代码
<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: 1flex-grow: 1; flex-shrink: 1; flex-basis: 0% 的缩写
  • 它告诉浏览器:"在分配剩余空间时,请给我 1 份"
  • 左右固定宽度(200px),剩余空间全部给中间列

四、Grid 实现三列布局(更优雅)

html 复制代码
<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 的代码更简洁。


五、为什么要开启新的格式化上下文?

css 复制代码
.layout {
  display: flex;  /* 开启 FFC */
}

这段代码至少做了两件事:

  1. 改变了排列规则:子元素不再按照"块级元素从上到下"的 BFC 规则排列,而是按照 Flex 的"主轴方向排列"
  2. 创建了隔离区域.layout 内部是一个独立的 FFC,它内部的浮动、边距等不会影响外部元素

这也是为什么 overflow: hidden 能清除浮动的原因------它触发了 BFC,让父元素包裹住内部浮动元素。


六、布局方案对比

方案 实现方式 优点 缺点
Flex display: flex + flex: 1 简单直观、兼容性好 一维布局,复杂场景需嵌套
Grid grid-template-columns: 200px 1fr 200px 最简洁、二维布局能力 旧浏览器支持度稍差
Float float: left + 负边距 历史经典 需清除浮动、代码复杂
定位 position: absolute + 左右定位 灵活 脱离文档流,需额外处理高度

现代推荐 :优先用 GridFlex,除非有特殊的旧浏览器兼容需求。


第三部分: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>只要 K
  • Omit<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"或者"布局被意外影响"的情况?欢迎在评论区分享你的故事。如果对 ExcludeOmit 的区别还有疑问,或者想了解更深层次的条件类型(inferextends),也可以留言,我来继续为你拆解。


相关推荐
Scabbards_3 小时前
面试Leetcode - Heap 堆
java·leetcode·面试
黄敬峰4 小时前
🐳 Docker 从入门到实战:一次搞定容器化 + Nginx 反向代理
面试
程序员清风4 小时前
专业再升级!程序员专属显示器明基RD280UG上手实测!
java·后端·面试
six_5 小时前
C# 上位机(持续更新中)
前端·面试·c#
moMo5 小时前
深入 NestJS:从工厂模式到装饰器模式的架构之美
typescript·nestjs
码匠许师傅6 小时前
【C++ 面试真题】聊聊 C++ 的序列容器
java·c++·面试
东华万里7 小时前
第40篇C++核心基础与工程实践:从底层逻辑到避坑指南
开发语言·c++·面试·大学生专区
wangjialelele10 小时前
动态规划DP经典题型总结(Java、C++):路径、子数组、子序列、背包问题详解
java·c++·算法·面试·动态规划·代理模式
一只叫煤球的猫11 小时前
Spring AI 2.0 源码解析(一):一次 ChatClient 调用到底经历了什么?
后端·面试·ai编程