跳到主要内容

从模糊搜索到 AI RAG:商品搜索的三种演进思路

· 阅读需 9 分钟

商品搜索表面上是“用户输入一句话,系统返回一些商品”,但真实场景会复杂很多。

用户可能输入关键词:

苹果 手机

也可能直接表达需求:

推荐 5 款 2000 元以内的大存储苹果手机

这里面既有品牌、品类、价格、数量,也有“大存储”这种偏好。如果只靠关键词匹配,很容易漏掉用户真正想要的东西。

商品搜索通常可以分成三个阶段:

  • 模糊搜索:提供稳定、可控的检索和筛选能力
  • AI 结构化搜索:降低用户表达成本,提升搜索效率
  • AI 结构化 + 向量库搜索:解决语义召回和 RAG 回答

一、模糊搜索:稳定可控的基础搜索

模糊搜索不只是简单的关键词包含匹配。一个成熟的模糊搜索系统,本身也可以支持筛选、排序、分页、价格区间、品牌过滤、类目过滤等能力。

比如用户可以先搜索:

苹果 手机

再通过筛选条件缩小范围:

品牌:苹果
价格:2000 元以内
排序:价格从低到高
规格:大存储

这些能力并不依赖 AI。传统搜索系统完全可以通过规则、表单、筛选器和排序器完成,而且通常更稳定、更快、更可控。

模糊搜索的优点是:

  • 简单
  • 成本低
  • 可解释
  • 行为稳定
  • 适合承接确定性过滤和排序

它真正的问题不是“不能筛选排序”,而是用户必须用系统能理解的方式表达需求。

比如:

  • 用户要知道去哪里点价格筛选
  • 用户要把多个条件拆开输入
  • 用户要理解“排序”和“筛选”的区别
  • 用户要不断调整关键词,才能逼近想要的结果

所以模糊搜索不是能力弱,而是交互成本高。它适合做底层检索能力,但不一定是最高效的用户入口。

二、AI 结构化搜索:提升搜索效率

引入 AI 结构化搜索,主要不是为了证明传统搜索不行,而是为了让用户少操作几步。

用户不用再手动拆条件、点筛选、调排序,而是可以直接说完整需求。

例如:

推荐 5 款便宜点的苹果手机

可以被理解成:

信息含义
意图推荐
品牌苹果
商品类型手机
数量5
价格偏好便宜

这样,“5 款”就不再是关键词,而是返回数量;“便宜点”也不再是普通文本,而是排序偏好。

AI 在这里更像一个自然语言入口。它把用户一句话拆成系统可以执行的条件,再交给搜索、过滤和排序模块处理。

它提升的是:

  • 输入效率:用户一句话表达多个条件
  • 理解效率:系统自动识别数量、品牌、价格和偏好
  • 交互效率:用户可以继续追问和修改条件

比如用户上一轮说:

推荐几款苹果手机

下一轮只说:

便宜点

系统如果能理解上下文,就应该在上一轮“苹果手机”的基础上调整排序或价格偏好,而不是把“便宜点”当成一次全新的搜索。

这就是 AI 搜索真正有价值的地方:它让搜索从“用户操作筛选器”变成“用户和系统对话”。

但它仍然有边界:AI 负责理解和转译,不应该替代底层搜索逻辑。

比如用户说:

apple 手机

模型可能理解出品牌是 Apple。但如果商品数据里品牌存的是“苹果”,普通文本匹配仍然可能搜不到。

所以 AI 结构化解决的是“查询理解”,不是“语义召回”。

三、向量搜索:解决字面不同但意思接近

向量搜索关注的不是两个文本是否完全一样,而是语义是否接近。

它依赖 embedding。embedding 可以把文本转换成一组数字,也就是向量。语义越接近的文本,向量距离通常越近。

比如在商品搜索语境下:

  • 苹果手机
  • Apple 手机
  • iPhone 手机
  • 苹果品牌智能手机

这些表达字面不同,但语义接近,所以可能被映射到相近的向量空间。

向量库负责存储商品向量。搜索时,用户问题也会被转换成查询向量,然后系统去找距离最近的商品。

这就是为什么“Apple 手机”有机会召回“苹果手机”。不是因为系统建立了 Apple 等于苹果的硬规则,而是因为 embedding 模型认为它们在当前语境下相似。

四、RAG:让模型基于真实数据回答

当向量搜索和生成式 AI 结合起来,就形成了 RAG。

RAG 可以拆成三步:

阶段作用
Retrieval先召回相关商品
Augmented把商品作为上下文交给模型
Generation模型基于上下文生成回答

在商品搜索里,RAG 的重点不是让模型凭空推荐,而是让模型基于真实商品结果回答。

这点很重要。否则模型可能会编造不存在的商品、价格或规格。

RAG 的价值在于:召回由数据负责,表达由模型负责。

五、三种方案对比

方案核心能力优点局限适合场景
模糊搜索检索、筛选、排序快、便宜、可控、稳定用户交互成本较高基础搜索和硬条件过滤
AI 结构化搜索自然语言转查询条件输入效率高,支持追问依赖底层召回和规则执行多条件搜索、对话式搜索
AI + 向量搜索语义召回能处理同义词、跨语言、模糊表达不适合单独做硬过滤推荐、相似商品、开放式需求

可以简单理解为:

  • 模糊搜索解决“怎么稳定地查、筛、排”
  • AI 结构化解决“怎么让用户更快表达需求”
  • 向量搜索解决“意思像不像”

六、向量搜索不是万能的

向量搜索适合处理:

  • 相似商品
  • 模糊需求
  • 口语表达
  • 中英文混合
  • 同义词和近义词

但它不适合单独处理强约束:

  • 价格必须小于 3000
  • 品牌必须是苹果
  • 存储必须是 512GB
  • 只返回 5 条
  • 只看有库存的商品

这些条件应该交给确定性过滤,而不是完全依赖向量相似度。

更稳的方式是分层:

层次处理内容
硬过滤品牌、价格、库存、规格
软召回拍照好、轻薄办公、学生党
排序低价优先、旗舰优先、综合排序
生成推荐理由、差异解释、下一步建议

七、一个更完整的购物助手还缺什么

搜索只是第一步。真正好用的购物助手还需要补齐几类能力。

1. 完整意图处理

用户不只会搜索,还会筛选、排序、对比、追问、解释和重置。

比如:

便宜点

这不是一个全新的搜索,而是对上一轮结果继续筛选。

第二款和第四款比一下

这也不是重新搜索,而是基于上一轮结果做对比。

系统要能判断用户是在开启新任务,还是继续修改当前任务。

2. 上下文记忆

多轮购物对话需要记住三类信息:

  • 当前查询条件:品牌、价格、规格、数量
  • 上一轮结果:哪些商品排在第几位
  • 用户偏好:预算、品牌倾向、是否偏好大存储

有了这些记忆,系统才能理解“这个”“第二款”“再便宜点”“换个品牌”这类表达。

3. 条件继承和覆盖

用户连续输入时,系统要判断条件是新增、覆盖还是删除。

例如:

推荐苹果手机
2000 以内

这里应该继承“苹果手机”,追加“价格小于 2000”。

但如果用户说:

算了,看看小米

这更像是把品牌从苹果切换成小米。

4. 标准化

用户表达和商品数据经常不一致:

  • Apple、iPhone、苹果
  • 512G、512GB
  • 2k、2000 元、两千
  • 手机、智能手机

如果系统内部没有标准词表,查询会不稳定。

标准化的目标是:用户可以用不同方式表达,但系统内部尽量落到统一的品牌、类目、规格和价格结构。

5. 无结果降级

没有结果时,不应该只说“没找到”。

更好的回答是指出可能原因,并给出放宽建议:

没有找到 2000 元以内的大存储苹果手机。可以放宽到 3000 元以内,或者保留预算但改看其他品牌。

这比单纯报空更像助手。

6. 结果质量评估

AI 搜索不能只看回答是否流畅,还要看结果是否满足约束。

可以关注:

  • 价格有没有超预算
  • 品牌有没有跑偏
  • 规格是否满足
  • 排序是否合理
  • 推荐理由是否来自真实商品信息

RAG 的质量很大程度取决于召回和过滤,而不是最后那段文字。

八、推荐的演进路径

如果从零构建,可以按这个顺序推进:

  1. 先做模糊搜索,保证基础搜索可用。
  2. 加 AI 结构化,理解自然语言里的条件。
  3. 把品牌、价格、规格、库存交给硬过滤。
  4. 引入 embedding 和向量库,增强语义召回。
  5. 加上下文记忆,支持连续追问、筛选和对比。
  6. 补齐标准化、降级策略和结果评估。
  7. 最后用 RAG 把结果组织成自然语言回答。

每一步都解决一个明确问题,不要一开始就把所有复杂度堆在一起。

总结

商品搜索的演进,本质上不是简单地用 AI 替代传统搜索,而是在传统搜索的稳定能力上,补上自然语言交互和语义召回。

模糊搜索让系统稳定地完成检索、筛选和排序。

AI 结构化搜索让用户更快表达需求,并支持连续追问。

向量搜索让系统能找到语义相近的商品。

RAG 让模型基于真实商品结果组织回答。

职责边界要清楚:

  • 数据库和过滤器负责确定性条件
  • 向量库负责语义召回
  • 生成式模型负责理解意图和组织表达

这样系统才会既聪明,又可控。

React 服务端组件 (RSC)

· 阅读需 14 分钟

简介

RSC(React Server Components)是 React 官方提出的一种组件模式,它允许一部分组件在服务器上运行,而不是在浏览器里运行。

它的目标是更快的加载、更小的 JS 体积,同时保留 React 的组合式开发体验。

回顾 CSR(客户端渲染)

<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
</body>
</html>

这个 bundle.js 脚本包含我们安装和运行应用程序所需的全部代码,包括 React、其他第三方依赖项以及自己编写的所有代码。

一旦 JS 被下载并解析,React 就会开始执行,为我们的整个应用程序生成所有的 DOM 节点,并放在那个空的<div id="root">.

这种方法的问题在于,执行期间需要很多时间。所以在此期间,用户只能盯着一片空白的屏幕。这个问题会随着时间的推移而变得更加严重:我们发布的每个新功能都会给 JavaScript 包增加更多构建 size,从而延长用户等待的时间。

回顾 SSR (服务端渲染)

服务器端渲染旨在提升用户体验。其核心就是,所有的渲染相关的js(期望上,某些第三方包并未支持需强制使用客户端渲染)都是在服务端先渲染好静态html模板(通过React/Vue 对应的各自 server 端的工具库),然后再统一发送到浏览器客户端,用户收到的是一个完整的 HTML 文档,而不需要等待js加载的白屏过程。

<!DOCTYPE html>
<html>
<body>
<div id="root">
<button>Click me</button>
</div>
<script src="/static/js/bundle.js"></script>
</body>
</html>

但同时由于服务端返回的是纯html字符串,服务器无法处理绑定 onClick 等交互事件,因此此时整个页面是无法交互的。

所以 HTML 文件依然包含 <script> 标签,因为我们仍需要在客户端运行 React 来处理这些交互。但它不再从头生成所有 DOM 节点,而是采用现有的 HTML。这个过程称为**“水合”** (hydration)[/hai'dreiʃən/]。

完整水合过程

  1. 重建虚拟 DOM → 浏览器执行 React/Vue 代码,生成虚拟 DOM 树。
  2. 对比现有 HTML → 检查 SSR 的 HTML 跟虚拟 DOM 一致不一致。(不一致则发出警告)
  3. 绑定事件 → 给按钮、输入框等加上交互能力。

*Hydration 就像用交互性和事件处理程序的“水”来浇灌“干燥的” HTML。

这就是 SSR 的精髓。服务器端生成初始 HTML,这样用户在下载和解析 JS 包时就不必面对空白页面。然后,客户端 React 会接续服务器端 React 的进度,采用 DOM 并添加一些交互功能。

SSG(静态站点生成)

当我们谈论服务器端渲染时,我们通常会想象这样的流程:

  1. 用户访问 test.com
  2. Node.js 服务器接收请求,并立即呈现 React 应用程序,生成 HTML。
  3. 刚刚生成的 HTML 被发送到客户端。

这是实现服务器端渲染的一种可能方法,但不是唯一的方法。另一种选择是在构建应用程序时生成 HTML。

通常,React 应用程序需要编译,将 JSX 转换为纯 JavaScript,并打包所有模块。如果在同一过程中,我们为所有不同的路由“预渲染”所有 HTML,会怎么样?

这通常被称为 静态站点生成(SSG) 。它是服务器端渲染的另一种方式。

与 SSR 不同的是,SSG 在构建阶段就生成静态 HTML,用户访问时直接拿到现成文件,不需要服务器每次渲染,如果有数据请求,则在构建阶段就提前获取并渲染好。

因此也无法实现动态数据了,如需变更数据,需要重新构建。适合数据变更不太频繁的站点,例如博客,文档,官网,营销页面等。

CSR/SSR 反复横跳

csr

客户端优化方式,数据获取之前,添加加载 Loading,直到请求完成并且 React 重新渲染,用真实内容替换加载 UI。

ssr

在这个新流程中,我们在服务器上执行第一次渲染。用户收到的 HTML 文件并非完全为空。

这算是一种改进——有壳总比空白页好——但最终,它并没有带来什么显著的改变。用户访问我们的应用不是为了看加载页面,而是为了查看内容(商品列表、搜索结果、消息等等)。

通过在服务器上进行初始渲染,我们可以更快地绘制初始“外壳”。这可以让加载体验感觉更快一些,因为它提供了一种进度感,让玩家感觉事情正在发生。

在某些情况下,这将是一个有意义的改进。例如,用户可能只是在等待标题加载,以便他们可以点击导航链接。

但是这个流程是不是感觉有点繁琐? SSR 图表里,仔细发现会注意到请求是从服务器开始的。与其要求第二次往返网络请求,为什么不在初始请求期间就完成数据库查询操作呢?

换句话说,为什么不能这样呢?

不在客户端和服务器之间来回跳转,而是将数据库查询作为初始请求的一部分,将完全填充的 UI 直接发送给用户。

但是,我们究竟该怎么做呢?

生态系统已经针对这个问题提出了很多解决方案。 元框架?像 Next.js 和 Gatsby 已经创建了自己的方式,可以在服务器上专门运行代码。

例如,使用 Next.js(使用旧版 "Page" 路由)时如下所示:

import db from 'mysql';

// 这段代码只运行在服务端
export async function getServerSideProps() {
const link = db.connect('localhost', 'root', 'passw0rd');
const data = await db.query(link, 'SELECT * FROM products');

return {
props: { data },
};
}

// 这段代码在服务端和客户端都会运行
export default function Homepage({ data }) {
return (
<>
<h1>Trending Products</h1>

{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}

分解一下: 当服务器收到请求时, getServerSideProps 函数会被调用。它返回一个props对象。这些 props 随后被传入组件,组件首先在服务器上渲染,然后在客户端进行 水合

这里的巧妙之处在于它 getServerSideProps 不会在客户端重新运行。事实上,这个函数甚至没有包含在我们的 JavaScript 包中!

这种方法在当时非常超前。但它也有一些缺点:

  1. 此策略仅适用于路由级别,适用于树最顶端的组件。我们无法在任何组件中执行此操作。
  2. 每个元框架都提出了自己的方法。Next.js 有一种方法,ReactRouter 有另一种方法,Remix 又有另一种方法。它们尚未标准化。
  3. 我们的所有 React 组件都将始终*在客户端上进行水合,就算它们不需要这样做(无需交互页面)。

多年来,React 团队一直在默默地研究这个问题,试图找到一个官方的解决方案。他们的解决方案叫做 React Server Components。

React 服务器组件简介

从高层次上讲,React 服务器组件代表着一种全新的规范。在这个环境下,我们可以创建专门在服务器上运行的组件。这使我们能够在 React 组件内部执行诸如编写数据库查询之类的操作!

以下是“服务器组件”的简单示例:

import db from 'mysql';

async function Homepage() {
const link = db.connect('localhost', 'root', 'passw0rd');
const data = await db.query(link, 'SELECT * FROM products');

return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}

export default Homepage;

这段代码一开始对一个前端开发者来说看起来应该很疯狂

**需要理解的关键点是:**服务器组件永远不会重新渲染。它们在服务器上运行一次以生成 UI。渲染后的值会发送到客户端并锁定在原地。就 React 而言,此输出是不可变的,永远不会改变。

这意味着React 的很大一部分 API 与服务器组件不兼容。例如,我们不能使用 state,因为 state 可以改变,但服务器组件无法重新渲染。我们也不能使用 effect,因为 effect 只能在渲染之后在客户端运行,而服务器组件永远不会运行在 React 客户端环境中。

这也意味着我们在规则方面拥有更大的灵活性。例如,在传统的 React 中,我们需要将副作用放在 useEffect 回调或事件函数之类的东西中,这样它们就不会在每次渲染时重复出现。但如果组件只运行一次,我们就不用考虑这些!

服务器组件本身出奇的简单,但 “React 服务器组件” 规范却要复杂得多。这是因为我们也需要使用常规的组件,而它们的组合方式可能相当混乱。

在这个新规范中,我们熟悉的“传统” React 组件被称为 客户端组件

“客户端组件”这个名称暗示这些组件在客户端上渲染,但事实并非如此。客户端组件在客户端和服务器上都会渲染。

使用 react-server-component 需要特殊的编译器,例如 react-server-dom-webpack ,或者直接使用 Nextjs/Remix 这类已经集成好的框架。下面以 NextJS 为例

指定客户端组件

'use client';

import React from 'react';

function Counter() {
const [count, setCount] = React.useState(0);

return (
<button onClick={() => setCount(count + 1)}>
Current value: {count}
</button>
);
}

export default Counter;

顶部的独立字符串 'use client' 是我们向 React 发出信号的方式,表明该文件中的组件是客户端组件,它们应该包含在我们的 JS 包中,以便它们可以在客户端上重新渲染。

这似乎一种非常奇怪的方式来指定我们正在创建的组件的类型,不过有个类似的先例:“use strict”在 JavaScript 中选择进入“严格模式”的指令。

在服务器组件中我们不会声明 'use server' ;在 React 服务器组件规范中,组件默认被视为服务器组件。事另外,'use server' 它用于服务器操作,这是一个完全不同的功能,下面会提及。

哪些组件应该是客户端组件?

您可能想知道:我应该如何决定给定组件应该是服务器组件还是客户端组件?

一般来说,如果一个组件不需要交互,那么它就应该作为服务器组件。服务器组件往往更简单,更容易理解。此外,它还具有性能优势:由于服务器组件不在客户端运行,因此其代码不会包含在 JavaScript 包中。React 服务器组件规范的优势之一是它有潜力提升页面交互性(TTI) 指标。

当你开始使用 React 服务器组件时,你可能会发现这非常直观。我们的一些组件需要在客户端运行,因为它们使用状态变量或效果。你可以在这些组件上添加 'use client' 指令。否则,你可以将它们作为服务器组件。

客户端组件不能嵌套服务器组件

如图所示,Article 重新渲染时,所有拥有的组件也会重新渲染。但如果这些是服务器组件,则它们无法重新渲染。

为了避免这种不可能的情况,React 团队添加了一条规则:客户端组件只能导入其他客户端组件'use client' 下面这些组件都默认设定成为客户端组件。

这意味着我们不用在每个需要在客户端运行的文件中都添加 'use client'。只需要在创建新的顶部客户端组件时添加。

也因此,你的每个路由页面默认应该是服务器组件,对于需要交互的子组件,就添加 'use client' 将其设定为客户端组件,对于顶部服务端组件,我们可以在里面进行数据获取,或者渲染一些静态列表

TODO LIST //

优势

React 服务器组件是第一个在 React 中运行服务器专用代码的“官方”方法。不过,之前也提到的,这在更广泛的 React 生态系统中并不算是什么新鲜事;自 2016 年以来,就能够在 Next.js 中运行服务器专用代码

最大的区别是,以前从来没有办法在组件内部运行服务器专用代码。

最明显的好处是性能。服务器组件不会包含在我们的 JS 包中,这减少了需要下载的 JavaScript 数量,以及需要加载的组件数量

例如,大多数博客都需要某种语法高亮库来展示代码块。

一个支持所有流行编程语言的语法高亮库,其体积将达到几兆大小,这实在太大,根本无法塞进 JS 包里。因此,我们不得不做出妥协,删减那些非关键的语言和功能。

但是,假设我们在服务器组件中实现语法高亮。在这种情况下,库代码实际上不会包含在浏览器需要下载的 JS 包中。这样一来,我们就不需要任何妥协,可以使用所有的功能。

以前那些因为包体积过高的功能,现在可以直接在服务器上运行,不增加任何 JS 空间,还能带来更佳的用户体验。

Server Action(服务器操作)

另一项值得注意的新特性是 Server Action。这些是用来处理用户交互触发的服务端逻辑,如表单提交或数据变更的异步函数。

使用方式非常简单:你只需要在函数体内(或者文件顶部)添加一个特殊指令 “use server”,React 就会将其识别为服务端操作,并自动处理 RPC(远程过程调用)无需手写 API 接口即可调用该服务器端函数

action.ts

'use server'

async function createTodo(title: string) {
const title = formData.get('title')?.toString() ?? '';

// 验证
if (!title) throw new Error('title 不能为空');

await db.todo.create({ data: { title } });
}

create-todo.tsx

'use client';

import { useState } from "react";
import { addTodo } from "../action";

export default function CreateTodo() {
const [title, setTitle] = useState("");

const handleAddTodo = async () => {
await addTodo(title);
setTitle("");
};
return (
<div className="flex items-center justify-between">
<input
className="border-2 border-gray-300 rounded-md p-2"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button
className="bg-blue-500 text-white rounded-md p-2"
onClick={handleAddTodo}
>
Add Todo
</button>
</div>
);
}

数据更新

revalidatePath()

强制让指定页面路径的缓存失效并立即渲染最新数据

'use server';

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/prisma';

export async function createTodo(todo: Todo) {
await db.post.create({ data });

// 让 `/` 页面缓存失效
revalidatePath('/');
}
revalidateTag()

指定某个函数缓存失效

import { unstable_cache } from 'next/cache';
import { db } from '@/lib/prisma';

export const getTodos = unstable_cache(
async () => db.todo.findMany(),
['todo'], // 缓存 Key
{ tags: ['todo_list'] } // 缓存标签
);


export async function addTodo(todo: Todo) {
await db.todo.create({ data: todo });
revalidateTag('todo_list'); // 所有用到这个 tag 的缓存失效
}

结尾

React 服务器组件(RSC)为 React 带来了一个新的思路——让部分组件只在服务器运行,从而减少客户端的 JS 体积、提升加载速度、优化交互体验。

与传统的 CSR、SSR、SSG 相比,RSC 最大的亮点是可以在组件内部直接编写服务器端逻辑,并与客户端组件无缝组合,这在过去的 React 生态里是无法做到的。

RSC 并不是替代 SSR 或 CSR 的“万能钥匙”,而是给了开发者更多自由度去按需选择组件运行位置。合理的组件拆分与运行策略,才能最大化发挥它的性能优势与开发体验价值。

未来,随着框架(如 Next.js、Remix)的完善与标准化,RSC 很可能会成为构建高性能 React 应用的默认选项。可以说,它不仅是性能优化的工具,更是 React 架构理念的一次进化。

如何使用 TypeScript 二次封装 Axios

· 阅读需 5 分钟

axios 已经逐步成为了 JS 前端甚至 Node 后端主流的网络请求库。其中请求/响应拦截也是使用率非常广泛的功能,众所周知其 getpost 请求参数结构不一,使得我们通常会在原 api 上进行二次封装。

既然是封装,那就要考虑到代码的健硕性,参数的扩展性,TS 类型支持,以及可维护性,如何有效设计封装,就是我们接下来要讲的重点。

CSS inset 属性介绍

· 阅读需 1 分钟

CSS 中 inset 属性大部分人可能没听过也没用过,其实就是 top right bottom left 的简写方式

inset: 0; 等价于:

div {
top: 0;
right: 0;
bottom: 0;
left: 0;
}

同时 inset: 1px 2px; 等价于:

div {
top: 1px;
right: 2px;
bottom: 1px;
left: 2px;
}

inset: 1px 2px 3px; 等价于:

div {
top: 1px;
right: 2px;
bottom: 3px;
left: 2px;
}

inset: 1px 2px 3px 4px; 等价于:

div {
top: 1px;
right: 2px;
bottom: 3px;
left: 4px;
}

设置顺序依次为上、下、左、右,这点就和 margin padding 等属性的统一设置规则一样。

4 行缩成 1 行,在代码简化上,倒是起了很大的帮助。

探索脚手架 cli 工作原理

· 阅读需 12 分钟

前端开发中,我们经常看见有一些全局脚手架包通过 xx create app-name 或者 npx xx app-name 等命令,就可以快速生成一个对应的模板项目,他们是怎么做到的呢?

利用 原生 API ULRSearchParms 处理 url query/search 参数转换问题

· 阅读需 2 分钟

前端开发中,经常会有处理 url query 参数的需求,大部分人会自己封装一个方法,去用正则转换解析成 js 对象或者遍历生成 query 参数字符串,或者找一些第三方库或 node 原生库 querystring 等。

但是许多人都或者不知道或者没用过 JS 的新的 Window 对象 API:ULRSearchParmsULRSearchParms 就是专门用来处理 query 参数的各种转换问题。