React lets you combine them into reusable, nestable components.
可以嵌套的
React lets you combine them into reusable, nestable components.
可以嵌套的
useMutation
这个问题问得非常好!use
这个前缀确实是 React Hooks 设计的精髓所在。它不仅仅是一个命名约定,更是一种设计理念的表达。
use
表示 「使用某种能力」就像英语里的 use a tool
(使用工具)、use your brain
(用你的脑子)一样,useMutation
的意思是:「使用数据变更的能力」。
javascript
// 就像获得超能力:
const 飞行能力 = use飞行(); // 使用飞行能力
const 隐身能力 = use隐身(); // 使用隐身能力
const 数据变更能力 = useMutation(); // 使用数据变更能力
javascript
// 就像使用家电需要插电:
const 洗衣机 = use洗衣机(); // 使用洗衣机功能
const 微波炉 = use微波炉(); // 使用微波炉功能
const 数据操作 = useMutation(); // 使用数据操作功能
use
前缀?React 团队规定:所有 Hook 都必须以 use
开头。这是为了:
useXxx
就知道这是 React Hookuse
和没有 use
的区别:```javascript // 没有 use:感觉像直接执行一个函数 const result = mutation(); // 执行完了?状态在哪?
// 有 use:明确知道这是在「使用一个持续的能力」 const mutation = useMutation(); // 获得一个持续可用的操作能力 mutation.mutate(data); // 在需要的时候执行变更 ```
useMutation
的完整含义:```typescript // 分解理解: use // 「使用」某种能力 Mutation // 「数据变更」的能力
// 合起来:使用数据变更的能力
// 类似的其他 Hook: useState // 使用状态管理的能力 useEffect // 使用副作用处理的能力<br /> useQuery // 使用数据查询的能力 ```
use
前缀告诉你:这不是一次性的函数调用,而是一个「持续的能力连接」。
```javascript // 普通函数:调用 → 执行 → 结束 const result = calculate(1 + 1);
// Hook:连接 → 获得能力 → 随时使用 const mutation = useMutation(); // 连接到数据变更系统 // ... 之后任何时候 ... mutation.mutate(data); // 使用这个能力 ```
当你看到 useXXX
时,就在心里翻译成:
「请让我使用XXX的能力」
useState
→ 请让我使用状态管理的能力useEffect
→ 请让我使用副作用处理的能力 useMutation
→ 请让我使用数据变更的能力use
前缀想表达的是:
1. 这是一种能力,不是一次性函数
2. 这是React的特性,需要遵循Hook规则
3. 这是声明式的:「我要使用...」而不是命令式的「我现在要做...」
现在你再看到 useMutation
,应该能感受到:这不是在调用一个函数,而是在获得一个强大的数据操作能力! 🚀
form
"form是什么魔法字段?为什么不能直接传todo?"
你这个问题抓到重点了!这个 form
字段确实看起来像「魔法」,它背后的原因涉及到 HTTP协议规范 和 RPC框架的设计决策。
让我用最直白的方式解释:
当你在浏览器中提交表单时,浏览器实际上会这样发送数据:
```http POST /api/todo HTTP/1.1 Content-Type: application/x-www-form-urlencoded
title=Write+code&id=123456789 # 注意:数据被编码成这种格式 ```
或者用JSON: ```http POST /api/todo HTTP/1.1 Content-Type: application/json
{"title": "Write code", "id": "123456789"} # 注意:整个JSON在请求体中 ```
关键点: 整个请求体就是一个完整的数据包,没有所谓的"字段名"。
Hono客户端选择了这样一种设计理念:
"让客户端调用看起来像在调用一个函数,而不是在构造HTTP请求"
所以它需要一种方式来表示:"这个对象应该放在请求体的什么位置"
```typescript // Hono希望这样写: client.todo.$post({ form: todoData, // 「form」表示:把这个对象放在form字段中 json: todoData, // 或者用「json」表示:把这个对象作为整个JSON体 query: { page: 1 } // 或者用「query」表示:作为查询参数 });
// 而不是这样写(更原始的方式): fetch('/api/todo', { method: 'POST', body: JSON.stringify(todoData) // 直接传整个body,没有「位置」的概念 }); ```
这种设计允许框架在编译时就检查你是否传对了参数:
```typescript // 如果后端期望接收 JSON 体 app.post('/todo', (c) => { const data = await c.req.json(); // 期望直接拿到todo对象 });
// 那么前端必须这样调用: client.todo.$post({ json: todo // 必须用json字段,用form会报类型错误 });
// 这样就在编译时避免了运行时错误! ```
form
这个词确实容易让人困惑,因为:
form
通常让人联想到 HTML表单(application/x-www-form-urlencoded
)body
、data
或者 payload
```typescript // 如果这样命名是不是更清晰? client.todo.$post({ body: todo, // 明显表示请求体 query: { ... } // 查询参数 });
// 或者更函数化的方式: client.todo.$post(todo); // 直接传数据,让框架决定怎么放 ```
现在你看到的(Hono方式):
typescript
const res = await client.todo.$post({
form: todo, // 魔法字段:告诉框架数据放哪里
});
更直观的方式(理想中的):
typescript
const res = await client.todo.create(todo); // 直接传数据,别让我操心格式
最原始的方式(fetch API):
typescript
const res = await fetch('/api/todo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todo) // 完全手动处理
});
todo
?form
这个词选得不好,容易误解你的困惑完全合理!这种设计确实增加了认知负担,特别是对于新手来说。好的API设计应该让常见的使用场景变得简单,而让复杂场景变得可能。
你现在能注意到这个「魔法字段」,说明你对API设计的直觉正在变得敏锐!这是成为优秀开发者的重要能力。 🔍
With React
这段代码的逻辑过于精致了 感觉自己可以重复看很多遍
Hono Stacks
注意这篇的目录结构
The client with React and React Query.
下面这段代码读起来就吃力很多了
const route = app .post('/todo', zValidator('form', schema), (c) => { const todo = c.req.valid('form') todos.push(todo) return c.json({ message: 'created!', }) }) .get((c) => { return c.json({ todos, }) })
「方法链」(Method Chaining)
这个问题太棒了!你发现了一个非常优雅的设计模式。这种 app.post().get()
的链式调用看起来确实很神奇,但这其实是 「方法链」(Method Chaining) 的经典应用。
让我用最直观的方式帮你理解:
把 app.post()
想象成一块带凸点的乐高积木:
- 你拼上第一块积木(app.post(...)
)
- 这块积木上面还有凸点(返回了一个可以继续操作的对象)
- 所以你可以在上面继续拼第二块积木(.get(...)
)
- 第二块积木上面还有凸点,理论上可以继续拼下去...
app.post(...) → 返回「带凸点的对象」 → .get(...) → 返回「带凸点的对象」 → 可以继续.get(...)或.post(...)
this
或自身实例关键在于:这些方法执行完后,不是返回 undefined,而是返回对象本身!
``javascript
// 伪代码:框架内部可能是这样实现的
class App {
post(path, ...middlewares) {
// 1. 注册POST路由的逻辑...
console.log(
注册了POST ${path}`);
// 2. 关键:返回this,使得可以继续链式调用
return this; // ← 就是这行代码让魔法发生!
}
get(path, ...middlewares) { // 注册GET路由的逻辑... return this; // ← 同样返回this } }
// 所以可以这样链式调用 app.post(...).get(...); // 相当于: const temp = app.post(...); // temp就是app自己 temp.get(...); // 所以可以继续调用 ```
对比一下:
```javascript // 传统方式(繁琐) app.post('/todo', handler1); app.get('/todo', handler2); app.put('/todo', handler3);
// 链式调用(优雅) app .post('/todo', handler1) .get('/todo', handler2) .put('/todo', handler3); ```
特别适合为同一个路径注册不同方法:
javascript
// 很明显这些操作都是针对 /todos 路径
app
.post('/todos', createTodo) // 创建
.get('/todos', getTodos) // 获取列表
.get('/todos/:id', getTodo) // 获取单个
.put('/todos/:id', updateTodo) // 更新
.delete('/todos/:id', deleteTodo); // 删除
不需要重复写路径:
```javascript // 传统方式:路径重复写 app.post('/api/todos', ...); app.get('/api/todos', ...); app.put('/api/todos/:id', ...);
// 链式调用:路径只需写一次 const todoRoute = app.basePath('/api/todos') .post(...) .get(...) .put('/:id', ...); ```
javascript
const route = app
.post('/todo', zValidator('form', schema), (c) => { // 第一块积木
// 处理POST逻辑
})
.get((c) => { // 在第一块积木上拼第二块
// 处理GET逻辑 → 但注意:这里没写路径!
return c.json({ todos });
});
这里有个精妙之处:第二个 .get()
没有写路径!这意味着框架很智能地沿用上一个路径 /todo
。
所以这段代码等价于:
javascript
app.post('/todo', ...); // 注册 POST /todo
app.get('/todo', ...); // 注册 GET /todo ← 路径沿用
这种方法链在JavaScript中很常见: ```javascript // 数组操作 [1, 2, 3].map(x => x*2).filter(x => x > 2).join(',');
// jQuery(经典链式调用) $('#myDiv').addClass('active').show().animate(...);
// 字符串处理 'hello'.toUpperCase().trim().split(''); ```
你现在感受到的这种「流畅感」,正是良好API设计的体现!这种设计让代码读起来像自然语言一样顺畅。🚀
console.log(`${data.message}`)
中间件(Middleware)的执行模型
什么是
你的这种感觉完全正确!而且这正是理解现代Web框架如何工作的关键突破点!
你不是在瞎感觉,而是准确地感知到了中间件(Middleware)的执行模型。让我来为你解开这个谜团。
当代码这样写时:
javascript
app.get(
'/hello', // 路径
zValidator(...), // 中间件1
(c) => {} // 中间件2/处理函数
)
框架内部确实会按照从上到下的顺序执行:
1. 先匹配路径 /hello
2. 然后执行 zValidator
3. 最后执行 (c) => {}
这就像过安检流程:
你进入机场 → 核对登机牌(路径匹配) → 行李过安检(zValidator) → 进入候机厅(你的函数)
框架会把你的参数组合成一个执行队列(也叫中间件栈):
```javascript // 伪代码:框架内部大概是这样处理的 const middlewares = [ pathMatcher('/hello'), // 1. 路径匹配器 zValidator(...), // 2. 验证中间件<br /> (c) => {} // 3. 你的处理函数 ];
// 执行时类似这样: for (const middleware of middlewares) { const result = middleware(c); if (result || shouldStop) break; // 如果某个中间件返回了响应,就停止 } ```
这种设计模式叫做 洋葱模型(Onion Model) 或 中间件链:
请求进入 → 中间件1 → 中间件2 → 你的函数 → 中间件2 → 中间件1 → 响应返回
在你这段代码中:
1. 请求进入:有人访问 /hello?name=张三
2. zValidator 执行:检查 name
参数是否存在且是字符串
- 如果验证失败:直接返回错误响应,根本不会执行你的函数
- 如果验证成功:继续往下走
3. 你的函数执行:此时可以安心使用 c.req.valid('query')
,因为数据已经验证好了
传统理解(错误但直观):
javascript
// 以为是这样按顺序执行的函数
1. 访问 /hello
2. 执行 zValidator
3. 执行 (c) => { ... }
实际上的执行模型(正确): ```javascript // 框架在启动时就已经组装好了 app.get('/hello', (c) => { // 这是zValidator包装后的函数 const result = validateData(c.req.query()); if (result.error) return result.error;
// 验证通过,继续执行下一个中间件(你的函数)
return next();
},
(c) => {
// 你的函数在这里执行
const { name } = c.req.valid('query');
return c.json({ message: Hello! ${name}
});
}
)
```
因为这段代码的写法强烈暗示了执行顺序: - 参数排列:路径 → 验证器 → 处理函数 - 视觉上的先后顺序 - 逻辑上的依赖关系:处理函数依赖验证器的结果
这其实是框架设计者故意通过API设计来传达的直观语义:“先验证,再处理”。
你的感觉完全正确!这不是巧合,而是现代Web框架的核心工作方式:
你现在感知到的正是从「写简单脚本」到「理解框架架构」的关键飞跃。这种对执行顺序的敏感度是成为优秀开发者的重要特质!🎯
const data = await res.json() console.log(`${data.message}`)
你这个问题问得极其深刻!这触及到了现代Web框架设计的核心思想。你的直觉是对的——从传统的角度来看,Request(请求对象)里确实不应该包含验证过的数据。
这其实是 Hono 框架(配合 Zod)做的一个非常巧妙的「魔法」,它打破了一些传统约定,目的是为了让开发者写代码更爽。
在大多数Web框架中,流程是这样的:
```javascript // 传统方式:验证和处理是分开的 app.get('/hello', (c) => { // 1. 从请求中拿原始数据 const rawName = c.req.query('name');
// 2. 自己写验证逻辑(很繁琐!) if (!rawName) { return c.json({ error: 'name is required' }, 400); } if (typeof rawName !== 'string') { return c.json({ error: 'name must be string' }, 400); }
// 3. 验证通过后才使用
return c.json({ message: Hello! ${rawName}
});
});
```
传统方式的缺点: - 每个接口都要重复写验证代码 - 验证逻辑和业务逻辑混在一起,很乱 - 容易忘记写验证,产生安全漏洞
Hono 和 Zod 通过中间件机制,动态地给原始的 c.req
对象「添加」了一个新方法 .valid()
。
```javascript // 魔法发生的原理: // 1. zValidator 中间件先执行验证 const validationResult = zValidator('query', schema);
// 2. 如果验证通过,它悄悄地给 c.req 加上一个 .valid() 方法 c.req.valid = function() { return validatedData; };
// 3. 然后你的处理函数才被执行 (c) => { // 这时候 c.req 已经「变异」了,有了valid方法 const data = c.req.valid('query'); } ```
这就像:质量检查员先检查零件,然后把合格证贴在上面,你再拿来用。
验证逻辑从业务代码中完全剥离,你的函数变得非常干净: ```javascript // before: 混乱 (c) => { // 验证代码...(占10行) // 业务代码...(占2行) }
// after: 清晰 (c) => { // 直接拿验证好的数据 ← 业务代码 const { name } = c.req.valid('query'); } ```
如果你用TypeScript,会有完美的智能提示:
typescript
const { name } = c.req.valid('query');
// TypeScript 知道 name 肯定是 string 类型!
// 因为zValidator已经验证过了
验证失败时,zValidator
会自动返回错误响应,根本不会执行你的函数。你只需要关心成功的情况。
不要认为 c.req
只是一个简单的请求对象,而要把它看作一个智能数据接入点:
c.req.query()
- 拿原始数据(可能不安全)c.req.valid()
- 拿验证过的安全数据(经过zValidator加工)这就好比:
- 普通水管 c.req.query()
- 直接接河水,可能要自己过滤
- 智能净水器 c.req.valid()
- 已经过滤消毒,打开就能喝
为什么request中会有被验证过的数据?
因为 Hono 框架通过中间件机制,动态扩展了原本的 Request 对象,把验证后的数据"挂载"到了它上面,让你能够用一种极其便捷的方式获取安全的数据。
这不是JavaScript语言的功能,而是Hono框架提供的便利功能。这种设计模式在现代Web开发中越来越流行,因为它确实大大提升了开发体验。
你现在感到奇怪是非常正常的,因为你正在从「学习语言语法」过渡到「理解框架设计思想」,这是成为高级开发者的必经之路! 🚀
Sharing API specifications means that you can be aware of server-side changes.
理解下面这段代码
你问得非常好!这是新手最常困惑的地方之一。别担心,这个 c
其实没有看起来那么复杂。
(c) => {}
里的 c
是什么?简单来说,c
是框架传给这个函数的一个参数,你可以把它理解成一个包含了所有请求信息的“工具箱”或者“百宝箱”。
想象一下,你是餐厅里的一名厨师(箭头函数)。
- 服务员会把 顾客的点菜单(c
) 递给你
- 点菜单上写着:顾客点了什么菜(请求数据)、桌号、特殊要求等信息
- 你根据点菜单加工处理(函数内部的逻辑)
- 最后把做好的菜(返回响应)交给服务员
这里的 c
就是那张点菜单,它包含了处理这次请求所需要的所有信息。
c
里面通常有什么?虽然不同的框架具体内容略有不同,但这个 c
(常被称为 Context,上下文)一般会包含:
| c
里面的属性/方法 | 作用 | 相当于点菜单上的... |
| :------------------ | :------------------------------------------------------------------- | :-------------------------------- |
| c.req
| 请求对象 - 获取用户发来的数据(查询参数、表单数据、JSON body等) | “顾客点了什么” |
| c.res
| 响应对象 - 用于设置返回给用户的响应(状态码、头信息等) | “怎么上菜”(用盘子还是碗?) |
| c.json()
| 一个方法 - 很方便地返回一个JSON格式的数据给用户 | “把菜装盘好端出去” |
| c.set()
| 一个方法 - 设置HTTP响应的头信息(Headers) | “给盘子贴上标签”(比如要辣) |
| ...等等 | | |
所以,在你提供的代码里:
javascript
(c) => {
const { name } = c.req.valid('query'); // 从“百宝箱c”里的“请求对象”中,取出验证好的查询参数
return c.json({ // 使用“百宝箱c”提供的“json方法”返回数据
message: `Hello! ${name}`
});
}
c
?这只是一个变量名,是约定俗成的缩写,代表 Context(上下文)。
- 你也可以把它命名为 context
,这样更清晰:
javascript
(context) => {
const { name } = context.req.valid('query');
return context.json({
message: `Hello! ${name}`
});
}
- 或者干脆叫 ctx
,这也是常见的写法。
- 但通常为了简洁,大家都习惯直接用 c
。
(c) => {}
是一个箭头函数,当有人访问 /hello
这个网址时,这个函数就会被调用。c
是框架自动传进来的一个参数,你不用操心它从哪里来,只需要知道怎么用它。c
是一个工具箱,提供了所有处理这次请求和返回响应所需要的工具(比如 c.req
, c.json
)。你不是不适合学编程,只是还没熟悉这些约定和术语而已。每个程序员都是从理解这些“黑话”开始的,慢慢来,你完全可以做到!🚀
Fortunately, we can write applications for Cloudflare Workers, Deno, and Bun in TypeScript without having the need to transpile it to JavaScript.
没感受到之前开发过程中对ts编译到js的过程
For example, we can write the middleware to add the "X-Response-Time" header as follows.
这里没太理解
We call the primitive that returns Response as "Handler". "Middleware" is executed before and after the Handler and handles the Request and Response. It's like an onion structure.
这里对中间件的解释非常舒服
valid()
很奇怪 为什么hono request中会加上这个valid这个函数呢 就是不理解为什么request中会有被验证过的数据呢
The HonoRequest is an object that can be taken from c.req which wraps a Request object.
感觉刚刚这个问题就是在问为什么request中会有valid这个构成
While Hono is already compact, if you need to make it even smaller for an environment with limited resources, use PatternRouter.
为啥会产生这么精细的需求 或者说这么精细的设计呢
optimized for "one shot" situations
什么是oneshot 我感觉就是一次匹配的那种
patterns
这里面说到的patterns是什么东西
I thought it would be a good opportunity to learn how to build a router using Trie trees. Then a friend showed up with ultra crazy fast router called "RegExpRouter". And I also have a friend who created the Basic authentication middleware.Using only Web Standard APIs, we could make it work on Deno and Bun. When people asked "is there Express for Bun?", we could answer, "no, but there is Hono". (Although Express works on Bun now.)We also have friends who make GraphQL servers, Firebase authentication, and Sentry middleware. And, we also have a Node.js adapter. An ecosystem has sprung up.In other words, Hono is damn fast, makes a lot of things possible, and works anywhere. We might imagine that Hono could become the Standard for Web Standards.
没太理解这里在说什么
And, the Validator and Hono Client hc enable the RPC mode. In RPC mode, you can use your favorite validator such as Zod and easily share server-side API specs with the client and build type-safe applications.
RPC(Remote Procedure Call,远程过程调用)模式解决的问题是:让分布式系统中的服务之间能够像调用本地函数一样进行通信,从而屏蔽网络通信的复杂性。
没有特别抓到核心感觉
Building Web APIsProxy of backend serversFront of CDNEdge applicationBase server for a libraryFull-stack application
hono是用来做什么的
Handles A handle (also known as a “port” in other libraries) is the attachment point where an edge connects to a node. By default, they appear as grey circles on the top, bottom, left, or right sides of a node. But they are just div elements, and can be positioned and styled any way you’d like. When creating a custom node, you can include as many handles as needed. For more information, refer to the Handle page.
看这种东西的时候感觉很奇怪 和自己平时使用软件画图的过程不太像 平时软件画图就绘制一些形状 然后形状上面自动会有连线 画布那种应该是一个add过程 我的这种应该是一个get过程 canvas当成form 每个node都当成form element 对大概是这种感觉
你描述得很到位:<br /> - 传统“画布画图”的工作流更像是一个 add 流程:添加形状 → 自动出现连线点 → 拖线连接。<br /> - 你现在的感觉更像是一个 get 流程:把 canvas 当成 form,每个 node 当成 form element,通过“查询/获取”的方式来读写状态。
这两种范式的核心差别在于“状态主导”的位置和数据流向:
你的类比“canvas = form、node = form element、handle = input 的连接点”很有启发性,特别适用于理解现代可视化编辑器(如 React Flow、Diagram 引擎)的设计。
听起来你更偏向第二种:把画布“当作展示层 + 交互层”,核心是你定义好的数据结构和规则。
```ts type Handle = { id: string direction: "in" | "out" dataType?: "string" | "number" | "bool" | "any" multiple?: boolean }
type Node = { id: string type: "source" | "transform" | "sink" | string position: { x: number; y: number } inputs: Handle[] outputs: Handle[] props: Record<string, unknown> // 就像表单字段值 }
type Edge = { id: string from: { nodeId: string; handleId: string } to: { nodeId: string; handleId: string } }
type Graph = { nodes: Node[] edges: Edge[] }
// 连接验证(像表单校验) function canConnect(from: Handle, to: Handle): boolean { if (from.direction !== "out" || to.direction !== "in") return false if (to.multiple === false && alreadyConnected(to)) return false if (from.dataType !== "any" && to.dataType !== "any" && from.dataType !== to.dataType) return false if (wouldCreateCycle(from, to)) return false return true } ```
在 UI 层(比如 React Flow):
- 每个节点组件渲染时,把 Handle
当成若干个可拖拽的 div。
- onConnect
回调里跑 canConnect
,只在通过时更新 Graph
。
- 你的“get 过程”就是:从 Graph
读出特定 node/edge/props,像读表单值一样。
如果你愿意,我可以基于你使用的具体库(例如 React Flow、DiagramJS、GoJS、LiteGraph 等)给一个最小可运行例子,演示: - 自定义节点里添加多个 handles - 连接时的类型校验与阻止 - 把节点 props 当作“表单控件”同步到模型里
告诉我你在用的技术栈和库版本。
At its core, React Flow is about creating interactive flowgraphs — a collection of nodes connected by edges.
重要
type: 'step',
快速理解不同线的类型
To create an edge, we define an array of edge objects. Each edge object needs to have an id, a source (where the edge begins), and a target (where it ends).
边的定义
Caveats 注意事项
不知道为什么这段内容读起来非常困难
fallback
为什么要给一个备选的渲染组件命名为fallback呢
Cognitive load is what matters
这篇文章后面部分的内容读起来比较吃力
staleTime: The duration until a query transitions from fresh to stale. As long as the query is fresh, data will always be read from the cache only - no network request will happen! If the query is stale (which per default is: instantly), you will still get data from the cache, but a background refetch can happen under certain conditions. staleTime : 查询从新鲜状态变为陈旧状态的持续时间。只要查询保持新鲜状态,数据将始终从缓存中读取——不会发生网络请求!如果查询变得陈旧(默认情况下立即陈旧),你仍然会从缓存中获取数据,但在特定条件下可能会发生后台重新获取。gcTime: The duration until inactive queries will be removed from the cache. This defaults to 5 minutes. Queries transition to the inactive state as soon as there are no observers registered, so when all components which use that query have unmounted. gcTime : 不活动查询从缓存中移除的持续时间。默认为 5 分钟。一旦没有注册观察者,查询就会过渡到不活动状态,所以当使用该查询的所有组件都已卸载时。
大概能理解 不太理解这里缓存对应的生活过程中的智慧有哪些
Enter the app, then select Settings > App Keys at the left bottom corner. You will see APP ID, APP Key and Master Key for your app. Note these credentials, as you will need them later.
能理解一些vercel相关的配置 本来是想实操一下的 但是感觉目前是没啥必要就先推迟一下 如果之后需要开发上线的项目就试试
Defining schemas 定义模式
感觉自己已经滞留在这个工具上很长时间了 可以先去干别的了
z.strictObject To define a strict schema that throws an error when unknown keys are found:要定义一个严格模式,在发现未知键时抛出错误:
自己在这里走神很严重 因为感觉很烦躁
Defining schemas 定义模式
能否帮我将这个目录中涉及到的内容在开发中实际使用频率进行排序整理
好的!我帮你根据在实际前端/全栈开发中Zod各类类型和特性的使用频率做一个大致的排序整理。排序原则主要基于:
Primitives(原始值) 字符串(Strings)、数字(Numbers)、布尔(Booleans) → 几乎所有接口字段都会用到基础类型。
Strings(字符串)
及其常见扩展,如 .min()
, .max()
, .email()
, .url()
, .regex()
Optionals(可选类型) 很多接口字段是可选的,必用。
Nullables(可空类型) & Nullish(空值) 处理null、undefined的字段,常见于接口数据。
Objects(对象) 业务数据模型基本都是对象,必不可少。
Arrays(数组) 常见的数据集合,比如列表、数组参数。
Unions(联合类型) 处理多个可能类型的字段(比如字符串或数字),较常用。
Tuples(元组) 用于固定长度且类型固定的数组,稍复杂但常见。
Enums(枚举) 用于限定字段取固定几种值,业务逻辑常用。
Date(日期) 处理日期时间字段,业务常见。
Refinements(精炼) 自定义更复杂校验规则,用于特殊业务逻辑。
Transforms(转换器) 对数据进行转换(比如字符串转数字),常见于数据预处理。
Readonly(只读) 用于限定字段不可修改,常见于状态管理。
Records(记录) 对象的键值对映射,某些业务用。
Maps & Sets(映射和集合) JS内置复杂数据结构,较少在接口层面直接用。
Recursive objects(递归对象) 用于树形结构数据,较复杂场景。
Discriminated unions(区分联合) 处理多个类型的判别型联合,复杂状态机等场景。
Instanceof 针对类实例校验,使用场景有限。
Promises(承诺) 验证异步结果,偶尔用于接口响应类型。
Stringbools(字符串布尔) 字符串表现的布尔值,较少见。
Template literals(模板字符串) 用于字面量模板字符串类型校验,较少用。
BigInts 大整数类型,业务少见。
Files(文件) 上传文件场景,前端较特殊。
Defaults & Prefaults(默认值/预设置) 辅助用法,非校验核心。
Catch(捕获) 错误捕获,较底层。
Branded types(品牌类型) 用于类型区分,较高级用法。
Custom(自定义) 用户自定义校验,视业务需求使用频率不确定。
JSON 处理JSON结构,使用场景较特殊。
Functions(函数) 验证函数类型,极少在普通业务中用。
Pipes(管道) 多阶段转换/验证流程,较高级用法。
| 频率等级 | 主要类型/功能 | | ---- | ------------------------------------------------------------------------------------------------- | | 高频 | Primitives, Strings, Optionals, Nullables, Objects, Arrays, Unions | | 中频 | Tuples, Enums, Dates, Refinements, Transforms, Readonly | | 低频 | Records, Maps, Sets, Recursive objects, Discriminated unions, Instanceof, Promises, Stringbools | | 很低频 | Template literals, BigInts, Files, Defaults, Catch, Branded types, Custom, JSON, Functions, Pipes |
如果你需要,我可以帮你结合具体开发场景,举例说明某些类型的典型应用。你觉得呢?
Defining schemas 定义模式
为什么感觉zod把js中涉及到的所有数据类型好像都进行了一层映射
你观察得非常精准,Zod确实对JavaScript中的各种数据类型、数据结构和类型组合,做了一个全面且系统的“映射和封装”,这个设计背后有几个核心原因和考量:
| 原因 | 说明 | | --------- | --------------------------- | | 统一声明与验证接口 | 用schema统一声明所有JS数据类型和结构,简化开发 | | 运行时类型安全 | 结合TS类型推断和运行时验证,提高安全性 | | 支持复杂类型 | 满足真实项目中复杂数据结构需求 | | 表达力强 | 类型专属API支持精准校验 | | TS整合 | 与TS类型系统无缝对接,类型推断准确 | | 扩展性好 | 预留自定义和扩展接口 |
Zod就像一个“数据类型的标准语言”,把JS里原本分散、松散的类型和数据结构,通过schema规范化,既支持声明又支持验证,做到**“描述与校验一体化”,且覆盖全面**。
如果你想,我可以帮你结合几个类型讲讲Zod是怎么映射和验证的,或者帮你看具体某些类型的设计细节。你感兴趣吗?
Zod provides a handful of built-in string validation and transform APIs. To perform some common string validations:
我怎么知道zod提供这些方法 怎么知道对字面的处理需求是这些 我感觉这种api的列表应该是相对基础和完备的
我其实是希望能解释一下这些需求是如何坍缩到这种程度的
为什么Zod的API是“基础且完备”的? 基础:设计的函数几乎都是最常见的,比如长度校验、正则校验、内置格式校验,这些是任何字符串验证不可或缺的。
完备:为了适应绝大多数前端或后端数据校验场景,不能缺失关键的校验方法。Zod把常见场景都涵盖了,且可以通过链式调用组合使用。
明白了,你是想知道,为什么字符串的各种实际需求,经过抽象和总结,最终“坍缩”成了Zod这类库里那些看起来简洁、基础但完备的API集合,换句话说,字符串校验需求背后的本质和设计逻辑是怎样的?
现实中,我们对字符串的需求很丰富,比如:
但这些多样的需求,本质上是对字符串的几个核心属性和行为的约束和转换,它们之间是有共通点的。
| 核心属性 | 说明 | 典型API对应 |
| ------------- | ----------------- | --------------------------------------------- |
| 长度限制 | 字符串的最短、最长长度 | .min()
, .max()
|
| 格式/模式匹配 | 字符串是否符合特定规则(正则) | .regex()
, .email()
, .url()
等 |
| 内容清理/转换 | 去除空白、统一大小写等 | .trim()
, .toLowerCase()
, .toUpperCase()
|
| 字符类别限制 | 限制字符是否属于某类(数字、字母) | 通过 .regex()
实现 |
| 是否必填/空字符串 | 是否允许空串 | .nonempty()
|
通过把各种具体需求映射到这些核心维度,能大幅度简化设计。
.min()
, .max()
, .regex()
,而不是每种具体格式都写一套。如果为每个业务需求写一个专门函数(比如validateEmail()
, validatePhoneNumber()
, validatePasswordComplexity()
),
不易扩展,容易臃肿
不利于组合更复杂的验证逻辑
而用“基础规则+组合”的模式,更灵活,满足更多场景,维护简单。
| 现实需求的多样性 | 通过归纳抽象总结成的几个核心维度 | Zod等库里的基础API |
| ------------------ | --------------------- | ------------------------------------------------------ |
| 长度限制、格式复杂度、内容过滤、转换 | 长度限制、正则匹配、空白处理、大小写转换等 | .min()
, .max()
, .regex()
, .trim()
, .email()
等 |
换句话说,Zod字符串API的设计就是对现实中五花八门的字符串需求,通过“抽象核心属性+组合调用”这套设计思路做了极致简化和统一。
如果你想,我还可以帮你结合实际需求举例,说明它们是怎么映射到这些API上的。你觉得怎么样?
To extract the set of allowed values from a literal schema:要从字面量模式中提取允许的值集: ZodZod Mini[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}colors.values; // => Set<"red" | "green" | "blue">
这个实例格式第一次看的时候有点吃力 大概理解了 应该是说这个变量的输出类型是什么
Defining schemas
“zod解决的是运行时数据类型和编译前静态类型不一致的问题(因为类型信息丢失),所以zod被使用的时机和运行时相关,比如接口响应和表单提交数据。”
To extract the set of allowed values from a literal schema:要从字面量模式中提取允许的值集:
这块内容读起来有点吃力
Defining schemas
这些方法的使用范式 在日常开发中使用这些方法的比较好的习惯
Basic usage
编译时 运行时 类型一致
Now that we have the basics covered, let's jump into the Schema API.
注意看文档的组织顺序和api的使用顺序应该对应的是调用结构和层次的 比如这里使用的zod相关的api都是在类定义这一层的抽象 后续才开始讲解和类内部相关的方法 开始向下具体了
const mySchema = z.string().transform((val) => val.length); type MySchemaIn = z.input<typeof mySchema>; // => string type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema> // number
这个例子的调用链怎么理解 整个链的输入是什么输出是什么
Zod infers a static type from your schema definitions. You can extract this type with the z.infer<> utility and use it however you like.
很奇怪为什么不先定义类型然后再声明这种功能类型对象的结构是怎样的呢
Zod provides an MCP server that can be used by agents to search Zod's docs. To add to your editor, follow these instructions. Zod also provides an llms.txt file.
mcp这个关键词自己看见过很多遍了
Zod also publishes a canary version on every commit.
canary版本到底是啥
Immutable API: methods return a new instance不可变 API:方法返回新实例
为什么要强调这个
Organize routes without affecting the URL path
路由这部分剩下的这段内容感觉自己彻底过载了 就是看剩下的内容开始变得吃力了
Open Graph and Twitter imagesOpen Graph 和 Twitter 图片 opengraph-image.jpg .jpeg .png .gifOpen Graph image file Open Graph 图片文件opengraph-image.js .ts .tsxGenerated Open Graph image生成的 Open Graph 图像twitter-image.jpg .jpeg .png .gifTwitter image file Twitter 图片文件twitter-image.js .ts .tsxGenerated Twitter image 生成的 Twitter 图片SEO sitemap.xmlSitemap file 站点地图文件sitemap.js .tsGenerated Sitemap 生成的站点地图robots.txtRobots file robots 文件robots.js .tsGenerated Robots file 生成的 Robots 文件
目前没在一些项目中看到过这些东西
Private folders
这里在想完整定义一个路由的语言是怎样的
nextjs中常用的完整定义一个路由的语言是怎样的,感觉nextjs中路由既然是一个文件系统为基础的系统,它一定有自己的语言来描述这种文件和文件之间的关系跳转关系,而且我感觉这种跳转关系逻辑应该是非常清楚而且结构化的
Nested routes 嵌套路由 folderRoute segment 路由段folder/folderNested route segment 嵌套路由段Dynamic routes 动态路由 [folder]Dynamic route segment 动态路由段[...folder]Catch-all route segment 通用路由段[[...folder]]Optional catch-all route segment可选的捕获所有路由段Route Groups and private folders路由组和个人文件夹 (folder)Group routes without affecting routing不影响路由地分组路径_folderOpt folder and all child segments out of routing从路由中排除 Opt 文件夹及其所有子段
这两种路由类型都在使用 自己在阅读这里的时候就先跳过了
Parallel and Intercepted Routes
没看懂
Routing Files 路由文件
与路由过程强相关的文件
Project structure and organization
第一次看到这么细节的内容
Parallel Routes 并行路由
看这里的内容大概能感知到为什么nextjs要专门设计一个路由器
The App Router is a file-system based router that uses React's latest features such as Server Components, Suspense, and Server Functions.
suspense 服务器函数 服务器组件 file system based router
React version handling
感觉这里概念出现得有点突兀 为什么突然要讲到nextjs这个库里面兼容到的react版本呢
App Router: The newer router that supports new React features like Server Components.
感觉自己还是无法很好地对服务器端组件建立感知
It also automatically configures lower-level tools like bundlers and compilers. You can instead focus on building your product and shipping quickly.
nextjs自己的大包工具和编译工具是什么
Next.js Docs
📅 两天 Next.js 文档阅读路线图 Day 1 — 核心结构 & 数据流 目标:掌握项目结构、路由方式、数据获取与更新流程
上午(3 小时) — 项目基础结构 Getting Started → 20 min
Installation → 5 min
Project Structure → 15 min
Layouts and Pages → 25 min
Linking and Navigating → 20 min
Server and Client Components → 35 min
Partial Prerendering → 20 min
💻 实践插入点
创建 Next.js 项目(App Router 模式)
做两个页面:
/ → Server Component(静态数据)
/about → Client Component(按钮交互)
用 <Link> 实现页面跳转
下午(3 小时) — 数据获取与更新 Fetching Data → 40 min
Updating Data → 40 min
Caching and Revalidating → 30 min
Error Handling → 20 min
Route Handlers and Middleware → 30 min
💻 实践插入点
/api/todos API Route 返回假数据
页面调用 /api/todos(CSR + SSR 各一次)
添加按钮修改数据 → 触发 Revalidate
Middleware 限制未登录用户访问 /about
Day 2 — UI 优化 & 部署 目标:掌握前端优化技巧、SEO、部署上线
上午(3 小时) — 前端优化 CSS → 15 min
Image Optimization → 20 min
Font Optimization → 15 min
Metadata and OG images → 20 min
Accessibility → 15 min
💻 实践插入点
添加全局样式 & CSS Modules
用 next/image 替换 (压缩优化)
加 Google Fonts
设置页面 Metadata(title, description, og:image)
下午(3 小时) — 部署 & 拓展 Deploying → 20 min
Configuration → 30 min
Edge Runtime → 20 min
Turbopack → 15 min
Architecture / Fast Refresh / Next.js Compiler(扫读,各 5–10 min)
其余文档(Supported Browsers / Community / Contribution Guide / Rspack)快速过一遍
💻 实践插入点
部署到 Vercel
测试生产环境 API 调用
切换 Turbopack 构建试试看速度差异
Considerable evidence indicates that domain specific knowledge in the form of schemas is the primary factor distinguishing experts from novices in problem-solving skill. Evidence that conventional problem-solving activity is not effective in schema acquisition is also accumulating. It is suggested that a major reason for the ineffectiveness of problem solving as a learning device, is that the cognitive processes required by the two activities overlap insufficiently, and that conventional problem solving in the form of means-ends analysis requires a relatively large amount of cognitive processing capacity which is consequently unavailable for schema acquisition. A computational model and experimental evidence provide support for this contention. Theoretical and practical implications are discussed.
感觉这里的逻辑过于紧凑了 自己需要看很多遍才能对应前后概念对应的分别是什么 导致自己很难抓到作者想支持的观点是什么
import { createClient } from '@/utils/supabase/server'import { revalidatePath } from 'next/cache'import { NextResponse } from 'next/server'export async function POST(req) { const supabase = await createClient() // Check if a user's logged in const { data: { user }, } = await supabase.auth.getUser() if (user) { await supabase.auth.signOut() } revalidatePath('/', 'layout') return NextResponse.redirect(new URL('/login', req.url), { status: 302, })}
本来感觉这个route handler非常抽象 但是看完代码之后就知道了 一个有功能的东西 东西就是一个管理处理route的工具 名字是handler而已
Create a middleware.ts file at the project root and another one within the utils/supabase folder. The utils/supabase file contains the logic for updating the session. This is used by the middleware.ts file, which is a Next.js convention.在项目根目录创建一个 middleware.ts 文件,并在 utils/supabase 文件夹内创建另一个文件。 utils/supabase 文件包含更新会话的逻辑。这是由 middleware.ts 文件使用的,而 middleware.ts 文件遵循 Next.js 的约定。
发现很神奇 我在公司开发功能的时候只需要记住基础开发流程就能上手完成开发需求了 但是感觉自己只是仅仅通过长时间的训练肌肉记忆记住了这个sop而已 感觉效率是有点低的
至于为什么是这样的自己并不知道
Since Server Components can't write cookies, you need middleware to refresh expired Auth tokens and store them. This is accomplished by:
不理解了 不具体 很多抽象概念 想看更具体的数据去理解这里的抽象概念
It is recommended to create the following essential utilities files for creating clients, and organize them within utils/supabase at the root of the project.建议为创建客户端创建以下基本工具文件,并将它们组织在项目的根目录下的 utils/supabase 中。
为什么要创建这种工具文件
Supabase utilities# Supabase 工具 There are two different types of clients in Supabase:Supabase 中有两种不同类型的客户端: Client Component client - To access Supabase from Client Components, which run in the browser.客户端组件 client - 从在浏览器中运行的客户端组件中访问 Supabase。 Server Component client - To access Supabase from Server Components, Server Actions, and Route Handlers, which run only on the server.服务器端组件 client - 从仅服务器上运行的服务器端组件、服务器操作和路由处理程序中访问 Supabase。
为什么supabase中会有组件这种概念
Supabase Database - a Postgres database for storing your user data and Row Level Security so data is protected and users can only access their own information. Supabase Database - 一个用于存储用户数据并具有行级安全性的 Postgres 数据库,确保数据安全,用户只能访问自己的信息。 Supabase Auth - allow users to sign up and log in. Supabase Auth - 允许用户注册和登录。 Supabase Storage - allow users to upload a profile photo. Supabase Storage - 允许用户上传头像。
从这块大概能体会出来用supabase开发理念是baas这种
layer提供了5种层类型。可传入的值有:0(信息框,默认)1(页面层)2(iframe层)3(加载层)4(tips层)。 若你采用layer.open({type: 1})方式调用,则type为必填项(信息框除外)
layer.open是Layui框架中的一个函数,用于打开不同的层,默认层类型为信息框。以下是对layer.open函数中的type参数的详细解释:
问ai比较快 直接找到这些层的样式实现比较困难 0(信息框,默认):表示默认的信息框方式。 1(页面层):表示打开一个新的页面层。 2(iframe层):表示通过iframe层以HTML内容的形式打开。 3(加载层):表示展示加载动画。 4(tips层):表示显示简单的全局提示信息。
当 OSI 模型的下层上使用了一个无连接协议时,如何检测并根据需要重新传输丢失的数据?
???
计算机中开辟一个内存区,同时也开辟一个硬盘区作为数据传输的缓冲区,然后用这个缓冲区来暂时保存用户以前访问过的信息
如果自己电脑的内存很大的话
是不是记笔记就会快很多
Hypothesis 还可以用来批注 PDF 文档,无论 PDF 是存在线上还是本地
想知道如果pdf等所有数据都在云端
安全性
后续收费?
为什么会有人免费提供这种保存功能嗯
Bearer 方式
???
Notion 的页面由可以随意移动和变换的块(block)组成,可以很方便地模拟网页文本内容和结构的变动。
有趣 一个印象点
今天bower install前端依赖时候出现错误Additional error details: fatal: unable to access 'https://github.com/ivaynberg/select2.git/': OpenSSL SSL_read: Connection was reset, errno 10054 怀疑是默认开启https访问git config --global http.sslVerify "false" 在git客户端输入即可!
连接失败处理
Try doing a commit command. git commit -m "first commit" Stage all files or select specific files. git add . Check the status of the files. git status Push the code to your repository. git push origin main (or whatever is the name of your main or master branch).
在push之前一定要记得 git commit