分类 前端 下的文章 - CorePlayers
首页
我的项目
708 智能控制台
Ethan认证中心
Ethan's ToDoList
Ethan's Tech Blog
好友链接
妙站分享
联系站长
搜 索
1
2026技术架构新趋势:从微服务回调到AI原生架构设计
45 阅读
2
DDD领域驱动设计:从底层原理到生产级全链路落地实战
41 阅读
3
Go + 云原生2026:从微服务到AI Infra的实战架构
35 阅读
4
事件驱动架构(EDA):从理论到项目落地的完整实践
34 阅读
5
2026 AI编程范式演进:从Vibe Coding到Spec-Driven Development再到Harness Engineering
31 阅读
ALL
(78)
AI
(20)
前端
(24)
后端
(23)
Dify/Coze
(7)
架构设计
(6)
登录
/
注册
搜 索
标签搜索
AI Agent
边缘计算
RSC
虚拟线程
Java
Spring Boot 4
Vibe Coding
AI原生
SDD
全栈开发
高并发
Project Loom
性能优化
2026趋势
协议标准
工具调用
MCP协议
多Agent协作
CrewAI
Spring AI
EthanCcc
累计撰写
78
篇文章
累计收到
1
条评论
首页
栏目
ALL
AI
前端
后端
Dify/Coze
架构设计
页面
我的项目
708 智能控制台
Ethan认证中心
Ethan's ToDoList
Ethan's Tech Blog
好友链接
妙站分享
联系站长
用户登录
登录
注册
前端
2025-06-20
Next.js 15 App Router 最佳实践:路由、缓存与数据获取
Next.js 15 将 Server Components 和 Server Actions 纳入稳定功能,Turbopack Dev 也达到生产可用状态,并重构了缓存策略。本文梳理 2025 年 Next.js 15 App Router 的核心最佳实践。一、Server Components 优先策略app/ 目录下的组件默认都是服务端组件,基本原则是:尽量让父组件(页面)是服务端组件负责取数据,把数据作为 props 传给客户端组件负责交互。// ✅ 推荐:Server Component 直接获取数据 export default async function Page() { const data = await fetch('https://api.example.com/data'); const json = await data.json(); return <div></div>; } // ❌ 避免:不必要的 Client Component 'use client'; export default function Page() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/data').then(r => r.json()).then(setData); }, []); return <div></div>; }二、数据获取策略Next.js 对原生 fetch 做了扩展,支持缓存控制:// ISR 模式:每 24 小时重新验证 fetch(url, }); // SSR 模式:每次请求都重新获取 fetch(url, ); // SSG 模式:永久缓存 fetch(url, );在 Server Component 中推荐使用原生 fetch 而非 axios,因为只有原生 fetch 能享受 Next.js 的缓存扩展能力。axios 在客户端组件中完全可用。三、路由系统要点文件即路由: app/page.tsx → / app/about/page.tsx → /about app/posts/[id]/page.tsx → /posts/:id(动态路由) app/blog/[...slug]/page.tsx → /blog/*(捕获所有路由) Next.js 15 重要变更:params 现在是 Promise 类型,需要使用 await:export default async function PostPage(: > }) { const = await params; // ... }四、缓存策略重构Next.js 15 将缓存从"隐式默认"改为"显式可控": GET 路由处理程序默认不再缓存 客户端路由缓存默认不再缓存页面组件 开发者需要显式声明缓存策略 这一变更使行为更加可预测,但也要求开发者明确思考每个请求的缓存需求。五、路由守卫实现在项目根目录创建 middleware.ts:import from 'next/server'; import type from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('token')?.value; if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } }六、并行数据获取利用 Promise.all 避免瀑布式请求:const [user, posts, comments] = await Promise.all([ getUser(params.id), getPosts(params.id), getComments(params.id), ]);
2025年06月20日
11
0
1
2025-06-05
浏览器事件循环与页面渲染机制完全指南
事件循环(Event Loop)是 JavaScript 运行时的核心机制,也是面试和实际开发中都无法绕开的关键知识点。不理解 Event Loop,你就无法解释 setTimeout 的延迟为什么不准、为什么某些动画会卡顿、以及为什么 async/await 的行为有时会出乎意料。本文将系统性地解析 Event Loop 和页面渲染的关系。JavaScript 的单线程本质JavaScript 被设计为单线程语言,主要原因是作为浏览器脚本语言,它需要操作 DOM——如果有两个线程同时操作同一个 DOM 节点,结果将不可预测。但单线程带来了一个问题:如果一个任务执行时间过长,页面就会"卡住"。Event Loop 正是为了解决这个问题而设计的。宏任务与微任务:事件循环的两层结构事件循环中,任务分为两种优先级:宏任务(MacroTask)——包括 script 整体、setTimeout、setInterval、I/O、UI 渲染、postMessage 等,每次事件循环取一个执行。微任务(MicroTask)——包括 Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick 等,宏任务执行完后一次性清空所有微任务。事件循环的完整流程浏览器的事件循环可以概括为以下循环过程:(1) 从宏任务队列中取出一个宏任务执行;(2) 执行过程中产生的微任务放入微任务队列;(3) 当前宏任务执行完毕后,清空微任务队列(包括微任务中新产生的微任务);(4) 如果需要,执行 UI 渲染(requestAnimationFrame 回调在此阶段);(5) 回到步骤 1,进入下一轮事件循环。console.log('1');\nsetTimeout(() => , 0);\nPromise.resolve().then(() => );\nconsole.log('6');\n// 输出:1 → 6 → 4 → 2 → 3 → 5async/await 的本质:Promise 的语法糖async function foo() \n// 等价于:\nfunction foo() );\n}Event Loop 与页面渲染的关系浏览器大约每 16.6ms(60fps)进行一次页面渲染。渲染发生在一轮事件循环的宏任务和微任务都执行完毕后。这意味着:微任务太多会阻塞渲染(如果微任务不断产生新的微任务,渲染将被无限推迟);requestAnimationFrame 在渲染前执行,适合做动画相关的计算;requestIdleCallback 在渲染后、下一轮循环前的空闲时间执行,适合做低优先级工作。// 危险:死循环微任务会永久阻塞渲染\nfunction blockingMicroTask() );\n}\n// requestIdleCallback:在浏览器空闲时执行非关键任务\nrequestIdleCallback((deadline) => \n});重排(Reflow)与重绘(Repaint)重排(Reflow):元素的几何属性(位置、尺寸)发生变化 → 浏览器重新计算布局。触发操作包括修改 width/height、添加/删除 DOM、改变字体大小、读取 offsetHeight 等布局属性。重绘(Repaint):元素的视觉属性(颜色、背景)发生变化但不影响布局 → 只重新绘制。重排一定触发重绘,但重绘不一定触发重排。重排的成本远高于重绘。// 错误:强制同步布局,每次读取 offsetHeight 都触发重排\nfor (let i = 0; i < 1000; i++) \n// 正确:批量读写分离\nconst width = element.offsetWidth; // 先读\nconst height = element.offsetHeight;\nelement.style.width = width + 10 + 'px'; // 后写\nelement.style.height = height + 10 + 'px';总结理解事件循环和渲染机制后,你在写代码时会自然地做出更好的决策:长列表用虚拟滚动、动画用 requestAnimationFrame、非关键任务用 requestIdleCallback、合并 DOM 操作避免强制同步布局。这些看似微小的优化,积累起来就是用户体验的巨大差异。
2025年06月05日
14
0
1
2025-05-18
TypeScript 高级类型编程:泛型、条件类型与类型体操实战
TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面进行"编程"。掌握高级类型不仅能提高代码的可维护性,更是编写高质量库和框架的必备技能。本文从泛型出发,逐步深入到条件类型和类型体操。泛型的本质:类型参数化泛型(Generics)让函数、接口、类可以接受类型参数,实现"一次定义,多处复用":// 没有泛型:每个类型都要写一遍\nfunction firstNumber(arr: number[]): number | undefined \nfunction firstString(arr: string[]): string | undefined \n// 使用泛型:一个函数适用所有类型\nfunction first(arr: T[]): T | undefined \n\nconst num = first([1, 2, 3]); // num: number\nconst str = first(['a', 'b']); // str: string泛型约束:限制类型参数的范围// 使用 extends 约束泛型\ninterface HasLength \nfunction logLength(arg: T): T \nlogLength('hello'); // string 有 length\nlogLength([1, 2, 3]); // array 有 length\nlogLength(123); // number 没有 length,编译错误\n\n// 使用 keyof 约束为对象属性的键\nfunction getProperty(obj: T, key: K): T[K] 条件类型:类型层面的 if/else条件类型是 TypeScript 类型系统的"控制流":// 基本语法:T extends U ? X : Y\ntype IsString = T extends string ? 'yes' : 'no';\ntype A = IsString; // 'yes'\ntype B = IsString; // 'no'\n\n// 实际应用:提取类型\ntype ExtractPromise = T extends Promise ? R : T;\ntype C = ExtractPromise; // string\ntype D = ExtractPromise; // number\n\n// 深层提取\ntype DeepExtractPromise = T extends Promise ? DeepExtractPromise : T;infer 关键字:从类型中提取信息// 提取函数返回值类型\ntype ReturnType = T extends (...args: any[]) => infer R ? R : never;\n// 提取函数参数类型\ntype FirstParam = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n// 提取数组元素类型\ntype ArrayElement = T extends (infer E)[] ? E : never;模板字面量类型:类型层面的字符串操作type EventName = `on$`;\ntype ClickEvent = EventName; // 'onClick'\ntype ChangeEvent = EventName; // 'onChange'\n\n// 实战:类型安全的事件系统\ntype EventMap = ; change: };\ntype EventHandlers = `]: (data: EventMap[K]) => void;\n};\n// ) => void;\n// onChange: (data: ) => void }映射类型与工具类型实战interface User \n// Partial:所有属性变为可选\ntype PartialUser = Partial;\n// Required:所有属性变为必填\ntype RequiredUser = Required;\n// Pick:选取部分属性\ntype UserPreview = Pick;\n// Omit:排除部分属性\ntype UserWithoutId = Omit;\n// Record:构造对象类型\ntype PageRoute = Record;实战:类型安全的 API 请求封装// 定义 API 接口映射\ninterface ApiMap ;\n '/api/users': ;\n}\ninterface ApiResultMap ;\n '/api/users': ;\n}\n\n// 类型安全的请求函数\nasync function request(\n url: T, params: ApiMap[T]\n): Promise );\n return response.json();\n}\n\n// 使用时获得完整的类型提示和检查\nconst user = await request('/api/user', ); // user.name 有自动补全总结TypeScript 的类型系统是一把双刃剑。用得好的类型能成为最好的文档和最可靠的守卫;用得过度的类型会成为维护的负担。掌握高级类型的关键在于:先解决实际问题,再追求类型的优雅。
2025年05月18日
11
0
1
2025-05-12
Bun 1.2 全栈实战:新一代 JavaScript 运行时深度评测
Bun 作为新兴的 JavaScript 运行时,在 2025 年发布的 1.2 版本中新增了大量企业级特性,正逐步从一个"快速的 Node.js 替代品"进化为"全栈开发一体化平台"。本文将从实际项目出发,全面评测 Bun 1.2 的核心能力。Bun 1.2 核心特性1. 内置 PostgreSQL 客户端:Bun 1.2 新增了原生 SQL 支持,无需安装第三方驱动,可以直接编写参数化查询。性能比 node-postgres 快 3-5 倍。import from "bun"; const users = await sql`SELECT * FROM users WHERE age > $`;2. Bun.serve() HTTP 服务器升级:支持 HTTP/2 和 WebSocket 零配置,内置压缩和 TLS。3. 改进的 Node.js 兼容性:Bun 1.2 的 Node.js 兼容率已从 90% 提升至 97%,大部分 npm 包可以直接运行。构建全栈应用使用 Bun 的全栈能力,一个简单的 API 服务只需要几十行代码:const server = Bun.serve( return new Response("Not Found", ); }, });与 Node.js/Deno 对比在相同的 HTTP 服务基准测试中,Bun 1.2 的吞吐量是 Node.js 20 的 2.8 倍,是 Deno 2.0 的 1.6 倍。启动速度方面,Bun 平均在 25ms 内完成冷启动,而 Node.js 需要约 80ms。但需要注意,Bun 在 Windows 上的支持仍然落后于 macOS/Linux。何时应该迁移到 Bun?如果你的项目是一个新启动的全栈应用,或者对服务端性能有较高要求,Bun 1.2 是一个非常值得考虑的选择。但对于依赖大量原生 C++ 模块的大型项目,建议等待生态进一步成熟。
2025年05月12日
13
0
1
2025-04-25
Vue 3 Composition API 实战:从 Options API 到 Composables 思维转型
Vue 3 的 Composition API 不仅仅是一种新的 API 风格,它代表了一种全新的代码组织思维——从"按选项类型组织"转向"按逻辑关注点组织"。本文将从一个真实项目的重构案例出发,深入讲解 Composition API 的实战技巧。Options API 的痛点:逻辑碎片化考虑一个典型的搜索组件,使用 Options API 编写时,一个功能(搜索)的逻辑会散落在 data、watch、methods、mounted、beforeUnmount 等多个选项中。当组件变得复杂时,在数百行代码中追踪一个功能的完整逻辑变得极其困难。Composition API 的解决方案:按功能聚合同样的搜索功能,用 Composition API 重写后,所有搜索相关的逻辑——状态、副作用、清理——都被封装在一个 composable 函数中。这就是 Composable(组合函数) 的核心思想。function useSearch() \n finally \n }\n\n function debounceSearch(val) , 300);\n }\n\n watch(keyword, debounceSearch);\n onBeforeUnmount(() => clearTimeout(timer));\n\n return ;\n}ref vs reactive:何时用哪个?// ref:适用于基本类型和需要整体替换的场景\nconst count = ref(0);\nconst user = ref();\ncount.value++; // 需要 .value\nuser.value = ; // 可以整体替换\n\n// reactive:适用于复杂对象,不需要 .value\nconst state = reactive(, settings: });\nstate.user.name = '李四'; // 直接访问,无 .value\n\n// reactive 的限制:不能整体替换\nlet state = reactive();\nstate = reactive(); // 破坏了响应式引用\n// 用 ref 包装对象避免此问题实战建议:对于组件内部状态,优先使用 ref(更灵活,类型推断更好);对于包含多个相关属性的配置对象,使用 reactive。Composable 设计原则1. 单一职责一个 Composable 只做一件事。如果 useUser() 既管理用户认证又管理用户偏好设置,应该拆分为 useAuth() 和 usePreferences()。2. 可配置而非硬编码function useSearch( = ) 3. 清理副作用function useEventListener(target, event, handler) 实战案例:可复用的数据获取 Composablefunction useFetch(url) catch (e) \n finally \n }\n\n if (isRef(url)) ); }\n else \n\n return ;\n}从 Options API 迁移的渐进策略不需要一次性重写整个项目:新组件直接用 Composition API,提取可复用的 Composable 从现有 mixin 开始转化,复杂组件(超过 300 行)逐个重构,利用 setup 语法糖进一步简化代码。Options API 不会消失——对于简单组件,它仍然是非常好的选择。Composition API 的价值主要体现在复杂组件的逻辑组织和跨组件的逻辑复用。
2025年04月25日
11
0
1
1
...
3
4
5