普通视图

发现新文章,点击刷新页面。
今天 — 2026年2月14日首页

2月14日:“农产品批发价格200指数”比昨天下降0.03个点

2026年2月14日 16:09
据农业农村部,2月14日“农产品批发价格200指数”为129.56,比昨天下降0.03个点,“菜篮子”产品批发价格指数为132.40,比昨天下降0.04个点。全国农产品批发市场猪肉平均价格为18.18元/公斤,比昨天下降0.2%;牛肉66.15元/公斤,比昨天下降0.7%;羊肉65.58元/公斤,比昨天上升0.9%;鸡蛋8.12元/公斤,比昨天下降2.3%;白条鸡17.29元/公斤,比昨天下降0.7%。重点监测的28种蔬菜平均价格为5.58元/公斤,与昨天持平;重点监测的6种水果平均价格为7.98元/公斤,比昨天下降1.0%。鲫鱼20.00元/公斤,比昨天上升2.2%;鲤鱼14.75元/公斤,比昨天上升1.9%;白鲢鱼10.56元/公斤,与昨天持平;大带鱼44.58元/公斤,比昨天下降0.6%。今日,国内鲜活农产品批发市场重点监测的46个品种中,与昨天相比价格升幅前五名的是青椒、鲫鱼、大白菜、鲤鱼和南瓜,幅度分别为2.2%、2.2%、2.1%、1.9%和1.8%;价格降幅前五名的是平菇、花鲢鱼、鸡蛋、巨峰葡萄和胡萝卜,幅度分别为3.3%、3.0%、2.3%、2.2%和1.9%。

千问大免单活动延长3天

2026年2月14日 16:07
2月14日,千问宣布免单活动延长3天,并接入大麦、飞猪,用户可体验AI买电影票、门票等新功能。从今天下午3点到大年初一,每个人可再领10张25元“超级免单卡”。据了解,“超级免单卡”使用范围更广,除了点餐饮、囤年货,也可以用来在千问上买电影票、门票、订酒店、机票。千问还将陆续接入AI打车、充手机话费、高德扫街榜团购、淘宝购物等新功能,均可使用超级免单卡,有效期持续到4月30日。

梅赛德斯-奔驰中国宣布重要人事任命:销售CEO段建军离职,李德思接任

2026年2月14日 15:34
2月14日,梅赛德斯-奔驰(中国)投资有限公司宣布一系列合资公司管理岗位的人事变动。北京梅赛德斯-奔驰销售服务有限公司总裁兼首席执行官段建军先生因个人原因辞去现任职务并将离开公司。自2026年3月1日起,奔驰销售公司销售执行副总裁李德思先生将被任命总裁兼首席执行官;段建军先生将担任公司战略顾问,确保平稳有序的工作交接,直至4月30日任期届满。自4月1日起,张明霞女士将结束在smart品牌全球公司(smart合资公司)担任全球首席营销官,负责全球销售、市场及客户运营业务的任期,出任奔驰销售公司销售执行副总裁。自4月1日起,梅赛德斯-奔驰汽车金融有限公司现销售与市场营销负责人康毅先生将出任smart合资公司全球首席营销官,负责销售、市场及客户运营业务。

LeetCode 25. K个一组翻转链表:两种解法详解+避坑指南

作者 Wect
2026年2月14日 15:04

LeetCode 难度为 Hard 的经典链表题——25. K个一组翻转链表,这道题是链表翻转的进阶题,考察对链表指针操作的熟练度,也是面试中的高频考点,很多人会在“组内翻转”“组间连接”“边界处理”上踩坑。

今天不仅会讲解题目核心,还会对比两份不同思路的代码,分析它们的优缺点、避坑点,帮大家彻底吃透这道题,下次遇到直接秒解!

一、题目解读(清晰易懂版)

题目核心需求很明确,一句话概括:给一个链表,每k个节点当成一组,组内翻转;如果最后剩下的节点不足k个,就保持原样

关键约束(必看,避坑前提):

  • k是正整数,且k ≤ 链表长度(不用考虑k大于链表长度的情况);

  • 不能只改节点的值,必须实际交换节点(排除“偷巧”解法);

  • 组间顺序不变,只有组内节点翻转(比如链表1->2->3->4,k=2,结果是2->1->4->3,不是4->3->2->1)。

示例辅助理解:

  • 输入:head = [1,2,3,4,5], k = 2 → 输出:[2,1,4,3,5]

  • 输入:head = [1,2,3,4,5], k = 3 → 输出:[3,2,1,4,5]

  • 输入:head = [1,2], k = 2 → 输出:[2,1]

二、链表节点定义(题目给出,直接复用)

先贴出题目给出的ListNode定义,两份解法都基于这个结构,不用额外修改:

class ListNode {
  val: number
  next: ListNode | null
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val)
    this.next = (next === undefined ? null : next)
  }
}

三、两种解法详解对比

下面分别讲解两份代码(reverseKGroup_1 和 reverseKGroup_2),从思路、执行流程、优缺点三个维度拆解,帮大家看清两种思路的差异。

解法一:reverseKGroup_1(全局翻转+局部调整+回滚,新手易上手但需避坑)

1. 核心思路

这种思路的核心是「边遍历边全局翻转,每凑够k个节点,就调整一次组间连接;最后如果不足k个节点,再把这部分翻转回去」。

可以类比成:把链表当成一串珠子,从头开始逐个翻转(珠子顺序颠倒),每翻k个,就把这k个珠子“固定”到正确的位置(连接好前后组);如果最后剩的珠子不够k个,就把这几个珠子再翻回来,恢复原样。

2. 关键变量说明

  • dummy:虚拟头节点,避免处理头节点翻转的特殊情况(所有链表题的通用技巧);

  • preGroup:每组翻转的“前置节点”,负责连接上一组的尾和当前组的头;

  • prev:翻转节点时的“前驱节点”,记录当前节点的前一个节点(用于翻转指针);

  • curr:当前正在遍历、翻转的节点;

  • count:组内节点计数器,用于判断是否凑够k个节点。

3. 代码执行流程(以 head=[1,2,3,4], k=2 为例)

  1. 初始状态:dummy(0)->1->2->3->4,preGroup=dummy,prev=dummy,curr=1,count=0;

  2. 遍历curr=1:count≠2,翻转1(1.next=prev=dummy),prev=1,curr=2,count=1;

  3. 遍历curr=2:count≠2,翻转2(2.next=prev=1),prev=2,curr=3,count=2;

  4. 凑够k=2个节点:调整组间连接——preGroup.next=prev=2(dummy->2),原组头lastNode=1,1.next=curr=3(2->1->3);更新preGroup=1,prev=1,count=0;

  5. 继续遍历curr=3:重复步骤2-3,翻转3、4,凑够k=2个节点,调整连接(1->4,3.next=null);

  6. 循环结束,count=0,无不足k个的节点,返回dummy.next=2,最终结果2->1->4->3(正确)。

4. 优点&缺点

优点:思路直观,新手容易理解(只需要掌握“单个节点翻转”的基础操作,再加上计数和回滚);代码结构清晰,逐步骤执行,容易调试。

缺点:存在冗余逻辑(比如单独处理“最后一组刚好k个节点”的else if分支);过度使用空值断言(!),有潜在空指针风险;最后回滚步骤增加了少量时间开销(虽然时间复杂度还是O(n))。

5. 核心避坑点

  • 避免链表环:翻转后必须及时调整组尾的next指针(lastNode.next=curr),否则会出现“dummy<->1”的环,触发运行错误;

  • 回滚逻辑不能漏:如果最后剩余节点不足k个,必须把这部分翻转的节点再翻回来,否则会破坏原有顺序;

  • 空值判断:preGroup.next不可能为null,可移除多余的空值判断,避免错误返回null。

解法二:reverseKGroup_2(先找组边界+组内单独翻转,最优解法)

这是更推荐的解法,也是面试中更常考的思路——「先找每组的边界(头和尾),确认够k个节点后,再单独翻转这组节点;组间连接直接通过边界节点处理,无需回滚」。

类比:还是一串珠子,先找到前k个珠子(确定组头和组尾),把这k个珠子单独翻转,再连接好前后珠子;再找下k个珠子,重复操作;如果找不到k个,就直接结束,不用再调整。

1. 关键变量说明(新增/差异变量)

  • groupTail:当前组的尾节点,通过移动k次找到,同时判断剩余节点是否够k个;

  • groupHead:当前组的头节点(翻转后会变成组尾);

  • nextGroupHead:下一组的头节点,提前记录,避免翻转后找不到下一组。

2. 代码执行流程(以 head=[1,2,3,4], k=2 为例)

  1. 初始状态:dummy(0)->1->2->3->4,preGroup=dummy;

  2. 找第一组边界:groupTail从preGroup开始移动2次,找到groupTail=2(确认够k个节点);记录groupHead=1,nextGroupHead=3;

  3. 单独翻转当前组(1->2):prev初始化为nextGroupHead=3,curr=groupHead=1;循环翻转,直到curr=nextGroupHead,翻转后变成2->1;

  4. 连接组间:preGroup.next=groupTail=2(dummy->2),preGroup更新为groupHead=1(下一组的前置节点);

  5. 找第二组边界:groupTail从preGroup=1移动2次,找到groupTail=4;记录groupHead=3,nextGroupHead=null;

  6. 单独翻转当前组(3->4),连接组间;

  7. 下一次找组边界:移动不足2次,count<k,直接返回dummy.next=2,结果2->1->4->3(正确)。

3. 优点&缺点

优点:逻辑更高效,无需回滚(提前判断节点数量,不足k个直接返回);无冗余分支,代码更简洁;指针操作更严谨,避免链表环和空指针风险;时间复杂度O(n),空间复杂度O(1),是最优解法。

缺点:对指针操作的熟练度要求更高,需要提前规划好“找边界-翻转-连接”的流程,新手可能需要多调试几次才能理解。

4. 核心避坑点

  • 找组边界时,必须同时判断节点数量:移动k次后,如果groupTail.next不存在,说明不足k个节点,直接返回;

  • 翻转组内节点时,prev初始化为nextGroupHead:这样翻转后,组尾(原groupHead)的next会自动指向nextGroupHead,无需额外调整;

  • preGroup更新为原groupHead:翻转后,原groupHead变成组尾,作为下一组的前置节点,保证组间连接正确。

四、两份代码对比总结

对比维度 reverseKGroup_1 reverseKGroup_2
核心思路 全局翻转+组间调整+不足k个回滚 先找组边界+组内单独翻转+无回滚
时间复杂度 O(n)(回滚最多增加O(k),可忽略) O(n)(最优,每个节点只遍历一次)
空间复杂度 O(1) O(1)
可读性 高,新手易理解 中等,需熟练掌握指针操作
适用场景 新手刷题、快速调试 面试、生产环境(最优解)
潜在坑点 链表环、回滚遗漏、空值断言 组边界判断、prev初始化

五、刷题建议&拓展思考

1. 刷题建议

  • 新手:先吃透 reverseKGroup_1,掌握“翻转+计数+回滚”的思路,熟练后再过渡到 reverseKGroup_2;

  • 进阶:重点练习 reverseKGroup_2,尝试自己手写“找边界-翻转-连接”的流程,避免依赖模板;

  • 调试技巧:遇到指针混乱时,画链表结构图(比如用草稿纸写出每个节点的next指向),逐步骤跟踪指针变化,比单纯看代码更高效。

2. 拓展思考(面试高频追问)

  • 如果k可以大于链表长度,该如何修改代码?(提示:在找组边界时,判断count是否等于链表长度,不足则不翻转);

  • 如何用递归实现K个一组翻转链表?(提示:递归终止条件是剩余节点不足k个,递归逻辑是翻转当前组,再递归翻转下一组);

  • 如果要求“每k个节点一组翻转,不足k个节点时全部翻转”,该如何修改?(提示:移除回滚逻辑,或不判断节点数量,直接翻转)。

六、最终优化版代码(推荐面试使用)

基于 reverseKGroup_2 优化,移除空值断言,增加防御性判断,代码更健壮、简洁,适配面试场景:

function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
  if (k === 1 || !head || !head.next) return head;

  const dummy = new ListNode(0, head);
  let preGroup = dummy; // 每组翻转的前置节点
  let count = 0;

  while (true) {
    // 第一步:找组尾,判断剩余节点是否够k个
    let groupTail = preGroup;
    count = 0;
    while (count < k && groupTail.next) {
      groupTail = groupTail.next;
      count++;
    }
    if (count < k) return dummy.next; // 不足k个,直接返回

    // 第二步:记录关键节点
    const groupHead = preGroup.next;
    const nextGroupHead = groupTail.next;

    // 第三步:组内翻转
    let prev: ListNode | null = nextGroupHead;
    let curr = groupHead;
    while (curr !== nextGroupHead) {
      const next = curr?.next;
      if (curr) curr.next = prev;
      prev = curr;
      curr = next;
    }

    // 第四步:组间连接
    preGroup.next = groupTail;
    preGroup = groupHead!;
  }
}

七、总结

LeetCode 25题的核心是「组内翻转+组间连接」,两种解法的本质都是通过指针操作实现,但思路的高效性有差异。

无论哪种解法,都要记住三个核心要点:① 用虚拟头节点简化头节点处理;② 明确每组的边界(头、尾、下一组头);③ 翻转时避免链表环和空指针。

刷题不是背代码,而是理解思路、掌握技巧。建议大家多调试、多画图,熟练掌握指针操作,下次遇到类似的链表翻转题(比如两两翻转、指定区间翻转),就能举一反三、轻松应对!

迪拜2025年国际游客近2000万,中国游客达86万

2026年2月14日 15:00
迪拜经济和旅游部公布的最新数据显示,2025年迪拜共接待国际过夜游客1959万,较2024年的1872万同比增长5%,连续第三年创历史新高。其中,中国2025年赴迪拜的过夜游客规模达86万人次,海湾阿拉伯国家合作委员会及中东和北非地区周边市场合计贡献了迪拜26%的游客量。

Claude Code 作者再次分享 Anthropic 内部团队使用技巧

作者 Immerse
2026年2月14日 14:52

大家好,我是 Immerse,一名独立开发者、内容创作者、AGI 实践者。

关注公众号:沉浸式AI,获取最新文章(更多内容只在公众号更新)

个人网站:yaolifeng.com 也同步更新。

转载请在文章开头注明出处和版权信息。

我会在这里分享关于编程独立开发AI干货开源个人思考等内容。

如果本文对您有所帮助,欢迎动动小手指一键三连(点赞评论转发),给我一些支持和鼓励,谢谢!


Boris 又发了一份 Anthropic 内部的 Claude Code 使用心得。

看完觉得挺实用,记录几条:

1. 多开 worktree 同时跑 3-5 个 git worktree,每个开一个独立会话。团队里公认这个最提效。Boris 自己习惯用 git checkout,但大部分人更爱 worktree。

2. 复杂任务先规划 遇到复杂活儿就开 plan mode。可以让一个 Claude 写计划,另一个当幕僚审查。跑偏了就切回去重新规划。验证环节也会专门进计划模式。

3. 错误后更新 CLAUDE.md 每次纠错完都加一句:"更新你的 CLAUDE.md,别再犯同样的错。"反复迭代到错误率明显降下来。

4. 自建 Skills 库 把常用操作做成 Skills 提交到 git,各项目复用。一天做两次以上的事就该做成 Skills。

5. 让 Claude 自己修 bug 接入 Slack MCP,把 bug 讨论帖扔给 Claude,说一句"修它"就行。或者直接"去修失败的 CI",不用管细节。

6. 提高提示词质量 试试"严格审查这些改动,测试不过不准建 PR",让 Claude 当审查员。或者"证明给我看这能跑通",让它对比 main 和功能分支的差异。

7. 追求更优方案 碰到平庸的修复就说:"基于现在掌握的信息,废掉这个方案,实现更优雅的。"任务前写详细规格,减少歧义。描述越具体,输出越好。

8. 终端配置 团队在用 Ghostty 终端,支持同步渲染、24 位色彩和完整 Unicode。用 /statusline 自定义状态栏显示上下文用量和 git 分支。给标签页做颜色编码和命名,一个任务一个标签页。

9. 语音输入 说话比打字快三倍,提示词也会详细很多。macOS 连按两次 fn 就能开启。

10. 用子代理 想让 Claude 多花点算力就加"use subagents"。把任务分给子代理,主代理的上下文窗口保持干净。

详情:x.com/bcherny/status/2017742741636321619 x

gsap 配置解读 --5

作者 大时光
2026年2月14日 14:52

什么是ScrollTo

 <header>
    <h1>案例 29:ScrollTo 平滑滚动</h1>
    <button id="to-second">滚动到第二屏</button>
  </header>
  <section>
    <div class="panel">第一屏内容</div>
  </section>
  <section id="second">
    <div class="panel">第二屏内容</div>
  </section>
  <section>
    <div class="panel">第三屏内容</div>
  </section>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollToPlugin.min.js"></script>
  <script>
    const button = document.querySelector("#to-second");

    // 注册 ScrollToPlugin
    gsap.registerPlugin(ScrollToPlugin);

    button.addEventListener("click", () => {
      gsap.to(window, {
        duration: 1,
        scrollTo: "#second",
        ease: "power2.out"
      });
    });
  </script>

ScrollToPluginGSAP(GreenSock Animation Platform) 提供的一个轻量级但非常实用的插件,用于实现 平滑、可控的页面滚动动画——无论是滚动到页面某个元素、指定坐标,还是精确控制滚动行为。


📌 简单定义:

ScrollToPlugin 让你用 GSAP 的动画语法(如 durationease)驱动浏览器窗口或任意可滚动容器,平滑滚动到目标位置。

它解决了原生 window.scrollTo() 只能“瞬间跳转”或简单 behavior: 'smooth' 缺乏控制的问题。


✅ 核心能力:

1. 滚动到多种目标
// 滚动到元素(通过选择器或 DOM 节点)
scrollTo: "#second"
scrollTo: document.querySelector(".footer")

// 滚动到具体坐标
scrollTo: { y: 500 }          // 垂直滚动到 500px
scrollTo: { x: 200, y: 300 }  // 水平 + 垂直

// 滚动到页面顶部/底部
scrollTo: { y: "top" }
scrollTo: { y: "bottom" }

// 滚动到元素并预留偏移(如避开固定导航栏)
scrollTo: { y: "#section", offsetY: 80 }
2. 完全控制动画体验
  • duration: 滚动持续时间(秒)
  • ease: 缓动函数(如 "power2.out""expo.inOut"
  • 可暂停、反向、加入时间轴(Timeline)
3. 支持任意可滚动容器

不仅限于 window,也可用于 <div style="overflow: auto"> 等局部滚动区域:

gsap.to(scrollableDiv, {
  duration: 1,
  scrollTo: { y: 1000 }
});

🔧 在你的代码中:

gsap.to(window, {
  duration: 1,
  scrollTo: "#second",      // 平滑滚动到 id="second" 的 <section>
  ease: "power2.out"        // 先快后慢的缓动效果
});

点击按钮后:

  • 页面不会“瞬间跳转”到第二屏;
  • 而是用 1 秒时间,以 优雅的缓动曲线 滚动到 #second 元素的顶部;
  • 用户体验更自然、专业。

🌟 典型应用场景:

场景 示例
导航跳转 点击菜单项平滑滚动到对应章节
“回到顶部”按钮 带缓动的返回顶部动画
表单错误定位 提交失败时滚动到第一个错误字段
交互式故事页 按钮触发滚动到下一情节
局部滚动容器 在聊天窗口中自动滚动到底部

⚙️ 高级选项(常用):

scrollTo: {
  y: "#target",
  offsetX: 0,       // 水平偏移
  offsetY: 60,      // 垂直偏移(常用于避开固定头部)
  autoKill: true    // 用户手动滚动时自动中断动画(默认 true)
}

🆚 对比原生方案:

方式 控制力 缓动 中断处理 兼容性
window.scrollTo({ behavior: 'smooth' }) 仅线性 现代浏览器
ScrollToPlugin 任意 GSAP 缓动 智能中断 全浏览器(含 IE11)

⚠️ 注意事项:

  • 必须注册插件:gsap.registerPlugin(ScrollToPlugin)
  • 目标元素必须已存在于 DOM 中
  • 如果结合 ScrollSmoother(平滑滚动容器),需使用其 API 而非直接操作 window

📚 官方文档:

👉 greensock.com/docs/v3/Plu…


✅ 总结:

ScrollToPlugin 是 GSAP 中实现“专业级页面导航动画”的标准工具——它用极简的代码,赋予滚动行为电影般的流畅感和精准控制,是提升网站交互质感的必备插件。

什么是SplitText

<div class="card">
      <h1 id="headline">SplitText 可以拆分文字做逐字动画</h1>
      <button id="play">逐字出现</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/SplitText.min.js"></script>
    <script>
      const headline = document.querySelector("#headline");
      const playButton = document.querySelector("#play");

      // 注册 SplitText 插件
      gsap.registerPlugin(SplitText);

      let split;

      playButton.addEventListener("click", () => {
        if (split) {
          split.revert();
        }

        // 将文字拆分为字符
        split = new SplitText(headline, { type: "chars" });

        gsap.from(split.chars, {
          opacity: 0,
          y: 20,
          duration: 0.6,
          ease: "power2.out",
          stagger: 0.04
        });
      });
    </script>

SplitTextGSAP(GreenSock Animation Platform) 提供的一个强大工具(虽然叫“插件”,但实际是一个独立的实用类),用于将 HTML 文本智能地拆分为可单独动画的 <span> 元素,从而实现精细的逐字、逐词或逐行动画效果。


📌 简单定义:

SplitText 能把一段普通文字(如 <h1>Hello</h1>)自动转换成包裹在 <span> 中的字符、单词或行,让你可以用 GSAP 对每个部分做独立动画。

例如:

<!-- 原始 -->
<h1 id="headline">你好</h1>

<!-- SplitText({ type: "chars" }) 处理后 -->
<h1 id="headline">
  <span class="char"></span>
  <span class="char"></span>
</h1>

✅ 核心功能:三种拆分模式

模式 说明 生成结构
type: "chars" 拆分为单个字符(包括中文、英文、标点) 每个字一个 <span class="char">
type: "words" 拆分为单词(以空格/标点分隔) 每个词一个 <span class="word">
type: "lines" 拆分为视觉上的行(根据实际换行) 每行外层包 <div class="line">

💡 也可组合使用:type: "words, chars" → 先分词,再把每个词拆成字。


split = new SplitText(headline, { type: "chars" });

gsap.from(split.chars, {
  opacity: 0,
  y: 20,
  duration: 0.6,
  ease: "power2.out",
  stagger: 0.04 // 每个字符延迟 0.04 秒启动
});
  • 点击按钮时,标题文字被拆成单个字符;
  • 每个字符从下方 20px、透明的状态,依次向上淡入
  • 形成“逐字打字机”或“文字飞入”的经典动效。

⚠️ 注意:每次点击前调用 split.revert() 是为了还原原始 HTML 结构,避免重复嵌套 <span> 导致样式错乱。


🌟 为什么需要 SplitText?

如果不使用它,手动写 <span> 包裹每个字:

  • 繁琐:尤其对动态内容或 CMS 内容不现实;
  • 破坏语义:影响 SEO 和可访问性(屏幕阅读器);
  • 难以维护

SplitText

  • 非破坏性:原始文本保持不变,仅运行时包装;
  • 智能处理:正确保留 HTML 标签、空格、换行、内联样式;
  • 支持复杂排版:包括多行、响应式断行(lines 模式会监听 resize)。

🛠️ 高级特性:

  • 保留原始样式:即使文字有 CSS 动画、颜色、字体,拆分后依然生效。
  • 与 ScrollTrigger 结合:实现“滚动到此处时逐字出现”。
  • 支持 SVG 文本(需额外配置)。
  • 可自定义包裹标签:默认 <span>,也可设为 <div> 等。

⚠️ 注意事项:

  • 不是免费插件:在 GSAP 3 中,SplitText 属于 Club 会员专属功能(可试用,但商业项目需授权)。
  • 不要重复拆分:务必在重新拆分前 revert(),否则会嵌套多层 <span>
  • 对 SEO 友好:因为原始 HTML 不变,搜索引擎仍能读取完整文本。

📚 官方文档:

👉 greensock.com/docs/v3/Plu…


✅ 总结:

SplitText 是 GSAP 中实现“高级文字动画”的基石工具——它将枯燥的文本转化为可编程的动画单元,让逐字淡入、弹跳、飞入等效果变得简单、可靠且专业,广泛应用于官网、片头、交互叙事等场景。

什么是TextPlugin

 <div class="card">
      <h1>案例 31:TextPlugin 数字滚动</h1>
      <p>让文本从 0 变化到目标值。</p>
      <div class="counter" id="counter">0</div>
      <button id="play">开始计数</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/TextPlugin.min.js"></script>
    <script>
      const counter = document.querySelector("#counter");
      const playButton = document.querySelector("#play");

      // 注册 TextPlugin
      gsap.registerPlugin(TextPlugin);

      const tween = gsap.to(counter, {
        duration: 1.6,
        text: "1280",
        ease: "power2.out",
        paused: true
      });

      playButton.addEventListener("click", () => {
        counter.textContent = "0";
        tween.restart();
      });
    </script>

TextPluginGSAP(GreenSock Animation Platform) 提供的一个轻巧但非常实用的插件,专门用于对 DOM 元素的文本内容进行动画化更新。它最经典的应用就是实现 “数字滚动计数器” 效果(如从 0 平滑变化到 1280),但也支持普通文本的渐变替换。


📌 简单定义:

TextPlugin 能让元素的 textContent 从一个值“动画过渡”到另一个值——对于数字,它会逐帧递增/递减;对于文字,它可模拟打字、随机字符替换等效果。


✅ 核心功能:

1. 数字滚动(最常用)
gsap.to(element, {
  duration: 2,
  text: "1000" // 自动从当前数字(如 "0")滚动到 1000
});
  • 自动识别数字并进行数值插值
  • 支持整数、小数、带千分位格式(需配合 delimiter);
  • 可设置前缀/后缀(如 $%)。
2. 文本替换动画
gsap.to(element, {
  text: "Hello World",
  duration: 1.5
});
  • 默认行为:直接替换(无中间动画);
  • 但配合 delimiter 或自定义逻辑,可实现打字机、乱码过渡等(不过复杂文本动画更推荐 ScrambleTextPlugin)。

gsap.to(counter, {
  duration: 1.6,
  text: "1280",        // 目标文本
  ease: "power2.out",
  paused: true
});
  • 初始文本是 "0"
  • 点击按钮后,TextPlugin 会:
    • 解析 "0""1280" 都是有效数字
    • 1.6 秒内,将文本内容从 0 → 1 → 2 → ... → 1280 逐帧更新
    • 视觉上形成“数字飞速增长”的计数器效果。

💡 注意:每次播放前重置 counter.textContent = "0" 是为了确保动画从起点开始。


⚙️ 常用配置选项(通过 text 对象):

gsap.to(element, {
  text: {
    value: "¥1,280",     // 目标值
    delimiter: ",",      // 千分位分隔符
    prefix: "¥",         // 前缀(也可直接写在 value 里)
    suffix: " 元",       // 后缀
    padSpace: true       // 保持文本长度一致(防跳动)
  },
  duration: 2
});

🌟 典型应用场景:

场景 示例
数据看板 用户数、销售额、点赞数的动态增长
加载进度 “加载中... 78%”
倒计时/计时器 活动剩余时间、秒表
游戏得分 分数变化动画
简单文本切换 状态提示(“成功” → “完成”)

🆚 对比其他方案:

方法 数字滚动 文本动画 精确控制 性能
手动 setInterval 一般
CSS + JS 拼接 ⚠️ 复杂 ⚠️ 有限 一般
TextPlugin ✅✅✅ (GSAP 时间轴)

⚠️ 注意事项:

  • 只作用于 textContent,不会影响 HTML 标签(即不能插入 <strong> 等);
  • 如果起始或目标文本不是纯数字,则直接替换(无滚动);
  • 要实现更炫的文字扰动(如乱码过渡),应使用 ScrambleTextPlugin
  • 免费可用TextPlugin 是 GSAP 的标准免费插件(无需会员)。

📚 官方文档:

👉 greensock.com/docs/v3/Plu…


✅ 总结:

TextPlugin 是 GSAP 中实现“数字计数器动画”的首选工具——它用一行代码就能将静态数字变成动态增长的视觉焦点,简单、高效、且完全集成于 GSAP 动画生态系统。

什么是EasePack

 <div class="card">
    <h1>案例 32:EasePack 特殊缓动</h1>
    <p>RoughEase / SlowMo / ExpoScaleEase 都在 EasePack 中。</p>
    <div class="row">
      <div>
        <div class="lane">
          <div class="ball" id="ballA"></div>
        </div>
        <div class="label">RoughEase</div>
      </div>
      <div>
        <div class="lane">
          <div class="ball" id="ballB"></div>
        </div>
        <div class="label">SlowMo</div>
      </div>
      <div>
        <div class="lane">
          <div class="ball" id="ballC"></div>
        </div>
        <div class="label">ExpoScaleEase</div>
      </div>
    </div>
    <button id="play">播放缓动</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>

  <!-- RoughEase, ExpoScaleEase and SlowMo are all included in the EasePack file -->
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/EasePack.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomEase.min.js"></script>
  <!-- CustomBounce requires CustomEase -->
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomBounce.min.js"></script>
  <!-- CustomWiggle requires CustomEase -->
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomWiggle.min.js"></script>
  <script>
    const ballA = document.querySelector("#ballA");
    const ballB = document.querySelector("#ballB");
    const ballC = document.querySelector("#ballC");
    const playButton = document.querySelector("#play");

    // 预设三个缓动
    const rough = RoughEase.ease.config({
      strength: 1.5,
      points: 20,
      template: Power1.easeInOut,
      randomize: true
    });
    const slowMo = SlowMo.ease.config(0.7, 0.7, false);
    const expoScale = ExpoScaleEase.config(1, 3);

    const timeline = gsap.timeline({ paused: true });
    timeline.to(ballA, { y: 100, duration: 1.2, ease: rough }, 0);
    timeline.to(ballB, { y: 100, duration: 1.2, ease: slowMo }, 0);
    timeline.to(ballC, { y: 100, duration: 1.2, ease: expoScale }, 0);

    playButton.addEventListener("click", () => {
      gsap.set([ballA, ballB, ballC], { y: 0 });
      timeline.restart();
    });
  </script>

RoughEaseSlowMoExpoScaleEaseGSAP(GreenSock Animation Platform) 中三个非常有特色的高级缓动函数(easing functions),它们都包含在 EasePack 插件中。它们超越了传统的“入/出”缓动(如 easeInOut),提供了更具创意和物理感的动画节奏。

下面分别解释它们的作用和适用场景:


1. 🌀 RoughEase —— “抖动式”缓动

✅ 作用:

模拟不规则、随机抖动的运动效果,常用于表现:

  • 手绘感、草图风格
  • 震动、故障、不稳定状态
  • 卡通式的“弹跳后晃动”

🔧 核心参数(通过 .config() 设置):

const rough = RoughEase.ease.config({
  strength: 1.5,     // 抖动强度(0~2,默认 1)
  points: 20,        // 抖动点数量(越多越密集)
  template: Power1.easeInOut, // 基础缓动曲线(决定整体趋势)
  randomize: true    // 是否每次播放随机(true=更自然)
});

🎯 在你的代码中:

  • 小球 A 下落时会上下轻微抖动,不是平滑移动,而是像“被手抖着拉下来”。

💡 适合:游戏中的受击反馈、加载失败提示、趣味 UI。


2. 🐢 SlowMo —— “慢动作中心”缓动

✅ 作用:

让动画在中间阶段变慢,两端加速,形成“慢镜头”效果。
特别适合强调某个关键状态(如悬停、高亮、停顿)。

🔧 核心参数:

const slowMo = SlowMo.ease.config(
  linearRatio,   // 中间匀速部分占比(0~1)
  power,         // 两端加速强度(0=线性,1=强缓出)
  yoyoMode       // 是否用于往返动画(true=对称)
);

例如:SlowMo.ease.config(0.7, 0.7, false)
→ 动画 70% 的时间以近似匀速缓慢进行,开头和结尾快速过渡。

  • 小球 B 下落时,大部分时间缓慢移动,只在开始和结束瞬间加速,仿佛“优雅降落”。

💡 适合:产品展示、LOGO 入场、需要突出中间状态的动画。


3. 📏 ExpoScaleEase —— “指数缩放”缓动

✅ 作用:

实现基于比例(scale)或指数增长/衰减的非线性缓动。
常用于:

  • 缩放动画(从 1x 到 10x)
  • 音量/亮度/透明度等对数感知属性
  • 模拟真实世界的指数变化(如声音衰减、光强)

🔧 核心参数:

const expoScale = ExpoScaleEase.config(startValue, endValue);
  • 它会将动画值从 startValueendValue指数曲线映射。
  • 通常配合 scaleopacity 或自定义属性使用。

🎯 虽然用于 y,但效果仍体现非线性:

  • 小球 C 的下落速度先快后慢(或反之,取决于范围),但变化是非线性的,比 power2 更“陡峭”。

💡 更典型用法:

gsap.to(circle, {
  scale: 5,
  ease: ExpoScaleEase.config(1, 5) // 从 1 倍到 5 倍的指数缩放
});

💡 适合:放大镜效果、爆炸扩散、雷达扫描、声波可视化。


🆚 对比总结:

缓动类型 视觉特点 典型用途
RoughEase 随机抖动、不规则 故障风、手绘感、震动反馈
SlowMo 中间慢、两头快 强调关键帧、优雅停顿
ExpoScaleEase 指数级加速/减速 缩放、对数感知属性、物理模拟

⚠️ 注意事项:

  • 这些缓动都来自 EasePack,需单独引入(如你代码中已做);
  • 它们可以像普通 ease 一样用在任何 GSAP 动画中;
  • 结合 Timeline 可创建复杂节奏组合。

📚 官方文档:


✅ 总结:

RoughEaseSlowMoExpoScaleEase 是 GSAP 赋予动画“性格”的秘密武器——它们让运动不再机械,而是充满随机性、戏剧性或物理真实感,是打造高级交互动效的关键工具。

什么是 CustomEase

<div class="card">
      <h1>案例 33:CustomEase 自定义缓动</h1>
      <p>用贝塞尔曲线定义缓动曲线。</p>
      <div class="track">
        <div class="block" id="block"></div>
      </div>
      <button id="play">播放自定义缓动</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomEase.min.js"></script>
    <script>
      const block = document.querySelector("#block");
      const playButton = document.querySelector("#play");

      // 注册 CustomEase
      gsap.registerPlugin(CustomEase);

      // 创建一个自定义缓动曲线
      CustomEase.create("myEase", "0.25,0.1,0.25,1");

      const tween = gsap.to(block, {
        x: 470,
        duration: 1.4,
        ease: "myEase",
        paused: true
      });

      playButton.addEventListener("click", () => {
        tween.restart();
      });
    </script>

CustomEaseGSAP(GreenSock Animation Platform) 提供的一个强大插件,允许你通过自定义贝塞尔曲线(cubic-bezier)来创建完全个性化的缓动函数(easing function),从而精确控制动画的速度变化节奏。


📌 简单定义:

CustomEase 让你像在 CSS 或设计工具中那样,用 4 个控制点定义一条缓动曲线,并将其注册为可复用的 GSAP 缓动名称,用于任何动画。

它打破了内置缓动(如 power2.inOutelastic)的限制,实现电影级、品牌专属或物理拟真的运动节奏


✅ 核心原理:贝塞尔曲线

缓动曲线本质是一条 三次贝塞尔曲线(Cubic Bezier),由 4 个点定义:

  • 起点固定为 (0, 0)
  • 终点固定为 (1, 1)
  • 中间两个控制点 (x1, y1)(x2, y2) 决定曲线形状

CustomEase 中,你只需提供这 4 个数值(按顺序):

" x1, y1, x2, y2 "

例如你的代码:

CustomEase.create("myEase", "0.25,0.1,0.25,1");

表示:

  • 控制点 1: (0.25, 0.1)
  • 控制点 2: (0.25, 1)

这条曲线的特点是:启动非常快(y1 很低),然后突然减速并平稳结束,形成一种“急冲后刹车”的效果。


🔧 使用步骤:

  1. 引入插件

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomEase.min.js"></script>
    
  2. 注册自定义缓动

    CustomEase.create("myEase", "0.25,0.1,0.25,1");
    
    • 第一个参数:缓动名称(字符串,如 "myEase"
    • 第二个参数:贝塞尔控制点(4 个 0~1 之间的数字,用逗号分隔)
  3. 在动画中使用

    gsap.to(block, {
      x: 470,
      duration: 1.4,
      ease: "myEase" // 直接使用注册的名称
    });
    

🌟 优势 vs 其他方式:

方式 灵活性 可视化 复用性 性能
CSS cubic-bezier() ✅(开发者工具) ❌(需重复写)
手动计算进度 ⚠️
CustomEase ✅✅✅ ✅(配合 GSAP 工具) ✅✅✅(全局注册) ✅✅(预计算优化)

💡 CustomEase预计算并缓存曲线数据,运行时性能极高,适合复杂动画。


🛠️ 如何获取贝塞尔值?

  1. 使用 GSAP 官方工具(推荐!)
    👉 GSAP Ease Visualizer

    • 拖动控制点实时预览动画
    • 自动生成 CustomEase 代码
  2. 从 CSS 复制
    如果你在 CSS 中写了:

    transition: all 1s cubic-bezier(0.25, 0.1, 0.25, 1);
    

    那么值就是 "0.25,0.1,0.25,1"

  3. 从 Figma / After Effects 导出
    许多设计工具支持导出贝塞尔缓动参数。


🎨 典型应用场景:

效果 贝塞尔示例 用途
弹性回弹 "0.68,-0.55,0.27,1.55" 按钮点击反馈
缓入缓出加强版 "0.33,0,0.67,1" 平滑过渡
快速启动+慢速结束 "0.25,0.1,0.25,1"(你的例子) 强调终点状态
延迟启动 "0.5,0,0.75,0" 悬停后才开始动画

⚠️ 注意事项:

  • 所有数值必须在 0 到 1 之间(超出会导致不可预测行为);
  • 注册一次后,可在整个项目中复用(如 "brandBounce""softEase");
  • 免费可用CustomEase 是 GSAP 的标准插件(无需 Club 会员);
  • 若需更复杂曲线(如多段),可结合 CustomWiggleCustomBounce(它们依赖 CustomEase)。

📚 官方资源:


✅ 总结:

CustomEase 是 GSAP 中实现“精准运动设计”的终极工具——它把缓动从“选择预设”升级为“自由创作”,让开发者和设计师能用同一套语言定义品牌专属的动画节奏,是打造高端用户体验的核心技术之一。

React 性能优化双子星:深入、全面解析 useMemo 与 useCallback

作者 AAA阿giao
2026年2月14日 14:51

引言

在现代 React 应用开发中,随着组件逻辑日益复杂、状态管理愈发庞大,性能问题逐渐成为开发者绕不开的话题。幸运的是,React 提供了两个强大而精巧的 Hooks —— useMemouseCallback,它们如同“缓存魔法”,帮助我们在不牺牲可读性的前提下,显著提升应用性能。

本文将结合完整代码示例,逐行解析、对比说明、深入原理,带你彻底掌握 useMemouseCallback 的使用场景、工作机制、常见误区以及最佳实践。文章内容力求全面、准确、生动有趣,并严格保留原始代码一字不变,确保你既能理解理论,又能直接复用实战。


一、为什么需要 useMemo 和 useCallback?

1.1 React 函数组件的“重运行”特性

在 React 中,每当组件的状态(state)或 props 发生变化时,整个函数组件会重新执行一遍。这意味着:

  • 所有变量都会重新声明;
  • 所有函数都会重新定义;
  • 所有计算逻辑都会重新跑一次。

这本身是 React 响应式更新机制的核心,但也会带来不必要的开销

💡 关键洞察
“组件函数重新运行” ≠ “DOM 重新渲染”。
React 会通过 Virtual DOM diff 算法决定是否真正更新 DOM。
昂贵的计算子组件的无谓重渲染,仍可能拖慢应用。


二、useMemo:为“昂贵计算”穿上缓存外衣

2.1 什么是“昂贵计算”?

看这段代码:

// 昂贵的计算
function slowSum(n) {
  console.log('计算中...')
  let sum = 0
  for(let i = 0; i < n*10000; i++){
    sum += i
  }
  return sum
}

这个 slowSum 函数执行了 n * 10000 次循环!如果 n=100,就是一百万次加法。在每次组件重渲染时都调用它,用户界面可能会卡顿。

2.2 不用 useMemo 的后果

假设我们这样写:

const result = slowSum(num); // ❌ 每次渲染都重新计算!

那么,即使你只是点击了 count + 1 按钮(与 num 无关),slowSum 依然会被执行!因为整个 App 函数重新运行了。

2.3 useMemo 如何拯救性能?

React 提供 useMemo记忆(memoize)计算结果

const result = useMemo(() => {
  return slowSlow(num)
}, [num])

工作原理

  • 第一次渲染:执行函数,缓存结果。

  • 后续渲染:检查依赖项 [num] 是否变化。

    • 如果 num 没变 → 直接返回缓存值,不执行函数体
    • 如果 num 变了 → 重新执行函数,更新缓存。

2.4 完整上下文中的 useMemo 使用

export default function App(){
  const [num, setNum] = useState(0)
  const [count, setCount] = useState(0)
  const [keyword, setKeyword] = useState('')
  const list = ['apple','banana', 'orange', 'pear']

  // ✅ 仅当 keyword 改变时才重新过滤
  const filterList = useMemo(() => {
    return list.filter(item => item.includes(keyword))
  }, [keyword])

  // ✅ 仅当 num 改变时才重新计算 slowSum
  const result = useMemo(() => {
    return slowSum(num)
  }, [num])

  return (
    <div>
      <p>结果: {result}</p>
      <button onClick={() => setNum(num + 1)}>num + 1</button>

      <input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      {
        filterList.map(item => (
          <li key={item}>{item}</li> 
        ))
      }
    </div>
  )
}

🔍 重点观察

  • 点击 “count + 1” 时:

    • slowSum 不会执行(因为 num 没变);
    • filterList 不会重新计算(因为 keyword 没变);
    • 控制台不会打印 “计算中...” 或隐含的 “filter执行”。
  • 这就是 useMemo 带来的精准缓存

2.5 关于 includes 和 filter 的小贴士

  • "apple".includes("") 确实返回 true(空字符串是任何字符串的子串);
  • list.filter(...) 返回的是一个新数组,即使结果为空(如 []),它也是一个新的引用

⚠️ 正因如此,如果不使用 useMemo,每次渲染都会生成一个新数组引用,可能导致依赖该数组的子组件误判为 props 变化而重渲染!


三、useCallback:为“回调函数”打造稳定身份

3.1 问题起源:函数是“新”的!

在 JavaScript 中,每次函数定义都会创建一个新对象

// 每次 App 重运行,handleClick 都是一个全新函数!
const handleClick = () => { console.log('click') }

即使函数体完全一样,handleClick !== previousHandleClick

3.2 子组件为何“无辜重渲染”?

看这段代码:

const Child = memo(({count, handleClick}) => {
  console.log('child重新渲染')
  return (
    <div onClick={handleClick}>
      <h1>子组件 count: {count}</h1>
    </div>
  )
})
  • memo 的作用:浅比较 props,若没变则跳过渲染。
  • 但每次父组件重渲染,handleClick 都是新函数 → props 引用变了 → memo 失效 → 子组件重渲染!

即使你只改了 numChild 也会重渲染,尽管它只关心 count

3.3 useCallback 的解决方案

useCallback 本质上是 useMemo 的语法糖,专用于缓存函数

const handleClick = useCallback(() => {
  console.log('click')
}, [count])

效果

  • 只要 count 不变,handleClick 的引用就保持不变;
  • Child 的 props 引用未变 → memo 生效 → 跳过重渲染

3.4 完整 useCallback 示例

import {
  useState,
  memo,
  useCallback
} from 'react'

const Child = memo(({count, handleClick}) => {
  console.log('child重新渲染')
  return (
    <div onClick={handleClick}>
      <h1>子组件 count: {count}</h1>
    </div>
  )
})

export default function App(){
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(0)

  // ✅ 缓存函数,依赖 count
  const handleClick = useCallback(() => {
    console.log('click')
  }, [count])
  
  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <p>num: {num}</p>
      <button onClick={() => setNum(num + 1)}>num + 1</button>
      <Child count={count} handleClick={handleClick} />
    </div>
  )
}

🔍 行为验证

  • 点击 “num + 1”:Child 不会打印 “child重新渲染”;
  • 点击 “count + 1”:Child 重渲染(因为 counthandleClick 都变了);
  • 如果 handleClick 不依赖 count(依赖项为 []),则只有 count 变化时 Child 才重渲染。

四、useMemo vs useCallback:一张表说清区别

特性 useMemo useCallback
用途 缓存任意值(数字、数组、对象等) 缓存函数
本质 useMemo(fn, deps) useMemo(() => fn, deps) 的简写
典型场景 昂贵计算、过滤/映射大数组、创建复杂对象 传递给 memo 子组件的事件处理器
返回值 函数执行的结果 函数本身
错误用法 用于无副作用的纯计算 用于依赖外部变量但未声明依赖

💡 记住
useCallback(fn, deps)useMemo(() => fn, deps)


五、常见误区与最佳实践

❌ 误区1:到处使用 useMemo/useCallback

  • 不要为了“可能的优化”而滥用

  • 缓存本身也有开销(存储、比较依赖项)。

  • 只在以下情况使用

    • 计算确实昂贵(如大数据处理);
    • 导致子组件无谓重渲染(配合 memo);
    • 作为 props 传递给已优化的子组件。

❌ 误区2:依赖项遗漏

const handleClick = useCallback(() => {
  console.log(count) // 依赖 count
}, []) // ❌ 错误!应该写 [count]

这会导致函数捕获旧的 count(闭包陷阱)。

✅ 正确做法:所有外部变量都必须出现在依赖数组中

✅ 最佳实践

  1. 先写逻辑,再优化:不要过早优化。
  2. 配合 React DevTools Profiler:定位真实性能瓶颈。
  3. useMemo 用于值,useCallback 用于函数
  4. 依赖项要完整且精确:使用 ESLint 插件 eslint-plugin-react-hooks 自动检查。

六、总结:性能优化的哲学

useMemouseCallback 并非银弹,而是 React 赋予我们的精细控制权。它们让我们能够:

  • 隔离变化:让无关状态的更新不影响其他部分;
  • 减少冗余:避免重复计算和渲染;
  • 提升用户体验:使应用更流畅、响应更快。

正:

“count 和 keyword 不相关”
“某一个数据改变,只想让相关的子组件重新渲染”

这正是 React 性能优化的核心思想:局部更新,全局协调


附:完整代码地址

源码地址:react/memo/memo/src/App.jsx · Zou/lesson_zp - 码云 - 开源中国

🎉 掌握 useMemouseCallback,你已经迈入 React 性能优化的高手之列!
下次遇到“为什么子组件总在乱渲染?”或“计算太慢怎么办?”,你就知道答案了。

Happy coding! 🚀

Seedance 2.0之后,字节跳动又发布豆包大模型2.0

2026年2月14日 14:50
2月14日,字节跳动发布豆包大模型2.0系列(Doubao-Seed-2.0),该系列包括旗舰版Pro和高性价比Lite版。豆包2.0 Pro在数学和推理能力上达到世界顶尖水平,斩获IMO、CMO数学竞赛及ICPC编程竞赛金牌,并在Putnam基准测试中超越Gemini 3 Pro。它加强了长尾领域知识覆盖,在SuperGPQA等测试中表现突出,科学领域知识成绩与Gemini 3 Pro和GPT 5.2相当。多模态理解全面升级,视觉推理、空间感知、长上下文理解等权威测试均取得业界最佳。同时增强对时间序列与运动感知,支持实时视频流分析、环境感知与主动交互,可应用于健身指导、穿搭建议、看护陪伴等生活场景。Agent能力方面,指令遵循、工具调用和Search Agent评测达顶级水平,在HLE-Text测试中以54.2分大幅领先。目前,豆包2.0 Pro已在豆包App、电脑客户端和网页版上线,用户选择「专家」模式即可体验;火山引擎也已提供API服务。定价上,豆包2.0 Pro按输入长度计费,32k以内输入3.2元/百万tokens,输出16元/百万tokens,相比竞品有较大成本优势。

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-js 2025详细解读

作者 shadowingszy
2026年2月14日 14:37

往期文章:

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-js 2021 & state-of-css 2021详细解读

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-css 2022 & state-of-js 2022详细解读

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-css 2023详细解读

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-js 2023详细解读

【前端趋势调查系列】带你看看前端生态圈的技术趋势state-of-css 2024和state-of-js 2024详细解读

一、写在前面

  • 本次分享的数据来源是state-of-js,是由Devgraphics开源社区团队发起的前端生态圈中规模最大的数据调查。
  • 想要贡献state-of-js调查结果中文翻译的同学可以联系我,或者直接向Devographics/locale-zh-Hans这个仓库提PR,然后艾特我来帮你review。
  • 如果这篇文章有其他意见或更好的建议,欢迎各位同学们多多指教。

二、受访者统计

今年的state-of-js调查共回收了13002份问卷结果。和去年相问卷结果又少了一些。

其实自从2022年起,填写问卷的人就越来越少,原因无外乎这么几个:

  • 前端的整体热度都在走低,像是google trends上前端相关的搜索词的热度都在下降;
  • 问卷内容过长导致内容填写起来比较麻烦;
  • 受访者虽然一直关注这项调查,但填了第一年的问卷之后第二年的问卷就不填了等等。

而在今年我结合我在Datawhale做的一些数据调查来看,有一个更重要的原因,就是AI的崛起——大部分开发者们的注意力已经转向了AI领域(包括我自己也是),基本不会在前端领域投入过多关注了

之前我也和调查发起人@SachaG聊过state-of-js调查的未来,作为一项坚持了9年的前端数据调查,也算是见证了前端领域的崛起与衰落。而如今,前端领域的热度早已不再是当年的样子,这项调查也不知道还能做多少年,大家且看且珍惜吧。

三、JS特性

语法特性

从今年的语法特性使用情况来看,社区对提升代码健壮性和简洁性的新特性抱有极大的热情:

  • 空值合并 运算符 ?? 的使用率高达 87% ,已经成为事实上的标准,这说明开发者在处理 nullundefined 时,迫切需要一种比 || 更严谨、更明确的工具来避免将 0false 等有效值意外覆盖,在日常开发中,我们应当优先使用 ?? 来处理默认值赋值,以增强代码的稳定性和可预测性。
  • 动态导入( Dynamic Import 66% 的使用率紧随其后,反映出代码分割和按需加载已是现代 Web 应用性能优化的核心实践,在构建大型应用、特别是需要考虑首屏加载速度的场景时,动态导入几乎是必修课。
  • 类私有字段( Private Fields 逻辑赋值 运算符 Logical Assignment 的使用率分别为 43%35% ,表明封装和代码简写同样是开发者追求的目标,尤其是私有字段,为在团队协作中保护内部状态、减少意外修改提供了语言层面的保障。

Array、Set、Object的特性

今年对 ArraySetObject 数据结构的新特性调查,揭示了不可变性(Immutability)数据处理便利性 已成为前端开发的核心趋势:

  • 返回新数组的 toSorted() 使用率已达 47% ,其孪生兄弟 toReversed() 也达到 37% ,说明社区正主动避免原地修改数组带来的副作用。
  • Set 新方法整体普及度不高,但在使用者中 union()intersection()difference() 等集合运算需求最集中,开始用于表达更复杂的数据关系与权限逻辑。
  • 首次进入调查的 Object.groupBy() 拿到 39% 使用率,说明了“按字段分组”这类高频需求可以摆脱 Lodash 等库,直接靠原生 JS 优雅解决。

Promise的特性

在异步编程领域,对多个 Promise 的精细化控制能力已成为现代前端的标配:

  • Promise.allSettled()52% 的使用率登顶,适合在“批量请求但不希望单点失败拖垮整体流程”的场景下使用,例如并行拉取多个非关键数据源、日志或埋点结果,它能保证我们总能拿到每个 Promise 的最终状态。
  • Promise.any() 使用率也达到 47% ,是“抢最快一个结果”的利器,典型场景是对多个镜像服务发起并行请求、谁先返回就用谁,从而显著优化响应延迟。
  • 这两个 API 的走红说明前端异步模型已经从“能并发”走向“可编排”,开发者不再满足于简单的 Promise.all,而是开始为不同业务场景选择更合适的并发策略。

浏览器API

浏览器 API 的使用情况反映了 Web 应用能力正从传统的页面展示,向功能更丰富、更接近原生应用的形态演进:

  • WebSocket 仍以 64% 的使用率牢牢占据基础设施地位,支撑了社交、协作、监控看板等场景中的实时通信。
  • PWA 使用率达到 48% ,说明离线能力、安装体验和通知能力已经被越来越多团队纳入评估维度。
  • 更值得关注的是 WebAssembly (WASM) ,使用率已达 21% 且排名上升 2 位,高性能语言(如 C++、Rust)编译到浏览器侧解决音视频处理、加解密、游戏等计算密集型问题,正在从先锋实践迈向工程常规武器。

JS语言的痛点

关于 JS 语言自身的痛点,今年的结果再次印证了社区共识:

  • 缺乏静态类型(Static Typing)28% 的提及率高居第一,这直接解释了为何 TypeScript 能在短时间内成为事实标准——大型项目在可维护性、重构安全和错误提前暴露上的诉求远非动态类型所能满足。
  • 日期处理(Dates)10% 排名第二,说明即便有 Temporal 提案在推进,现实中开发者仍大量依赖 date-fnsDay.js 等第三方库来填补标准库短板。
  • 同时,ESM CJS 的兼容问题标准库整体匮乏 等历史包袱也依然是工程实践中的绊脚石,这些痛点共同构成了“JS 好用但不够省心”的真实写照。

浏览器的痛点

当我们把视线从语言本身转向其运行环境——浏览器时,痛点显得更具工程现实感:

  • 跨浏览器支持(Browser support)31% 的提及率稳居首位,说明即便现代浏览器在标准实现上趋于一致,边缘行为差异、新特性落地节奏和兼容性策略仍是困扰前端团队的主要问题。
  • 浏览器测试(Browser testing)13% 位列第二,本质上是跨浏览器差异在测试和回归成本上的放大反馈
  • 而被单独点名的 Safari7% 成为第三大痛点,很多团队已经默认把它视作“新时代的 IE”,其标准跟进节奏和独特限制,为跨端一致性和平滑体验带来了额外负担。

四、JS技术

综述

这两张图分别从“历史趋势”和“当前满意度”两个维度,为我们描绘了 JS 技术生态的全景图:

  • 左侧四象限清晰展示出以 Vite 为代表的新一代工具,正沿着“低使用、高满意度”向“高使用、高满意度”高速跃迁,而曾经的王者 webpack 虽然仍有庞大使用量,但满意度明显滑落且轨迹线转为紫色,显示出疲态
  • 从右侧满意度分级我们可以发现,Vite (98%)Vitest (97%)Playwright (94%)Astro (94%) 等新星占据 S 级,而 webpack (26%)Angular (48%)Next.js (55%) 等传统选手则跌入 B/C 级,这意味着“存量巨大但口碑一般”的技术栈随时可能迎来用户流失;同时,Vite 生态中 Vite + Vitest 的双双登顶也说明高度协同的一体化工具链的优势,对于开发者而言,技术选型时不能只看当前占有率,更要关注满意度和趋势曲线,尤其要多留意那些位于右下象限、线条仍在上扬的新工具。

前端框架

前端框架的长期“三巨头”格局正在被悄然改写:

  • React 依旧以 80%+ 的使用率牢牢占据生态核心,但满意度已滑落到 B 级(72%),复杂的心智模型和渐进式演化成本让不少团队收到困扰。
  • Vue.js 在 2022 年前后正式超越 Angular 成为第二大框架,并以 84% 的满意度稳居 A 级,证明其在开发体验与性能之间找到了不错的平衡点。
  • Svelte 则凭借“无虚拟 DOM”的编译时理念持续走高,使用率已升至 26% ,成为追求极致性能和简洁语法团队的心头好。
  • 更有意思的是 HTMX,在近两年实现爆发式增长、使用率来到 13% ,它用“回归 HTML、用属性驱动交互”的思路,对当下 JS-heavy 的前端栈提出了有力反思。

元框架(前后端一体化框架)

元框架领域呈现出“一家独大 + 新星涌现”的混合格局:

  • Next.js 继续凭借与 React 的深度绑定,以近 60% 的使用率统治榜单,是大多数 React 团队构建生产级应用的默认选项,App Router 等激进改动和整体复杂度的提升正在透支开发者耐心。
  • Nuxt 在 Vue 生态中稳扎稳打,使用率升至 28%
  • AstroSvelteKit 则是近年最值得关注的两颗新星,前者在内容密集型站点中大放异彩,后者与 Svelte 深度绑定,为全栈应用提供了端到端的极致体验。

后端框架

在 Node.js 后端框架领域,我们不难看出,还是有些新面孔:

  • 老牌选手 Express 仍以 80%+ 的使用率稳居第一,作为“薄核心 + 丰富中间件”的事实标准难以被完全替代,但 81% 的满意度也表明开发者正在寻找更现代的方案
  • tRPC 是过去两年最耀眼的新星,通过直接在 TypeScript 中实现端到端类型安全调用,大幅简化了前后端联调与接口演进的成本。

测试框架

JavaScript 测试生态正在经历一场“现代化重构”:

  • 在单元与集成测试层面,Jest75% 的使用率独占鳌头。
  • 端到端测试领域则被 Cypress (55%)Playwright (49%) 两强主导,其中 Playwright 以 94% 的满意度跻身 S 级,体现了其在稳定性、调试体验和多浏览器支持上的优势。
  • 紧随其后的是 Vitest,作为 Vite 生态的一员,在短短两年内使用率冲到 50% ,满意度更是高达 97% ,验证了“测试工具与构建工具深度一体化”带来的体验红利。

构建工具

前端构建工具领域也在发生变革:

  • webpack 依旧以 85% 的使用率占据绝对存量,但满意度已经跌至 26% ,复杂配置和缓慢构建让它越来越像一座难以完全迁移的“基础设施债务”。
  • Vite 则是新时代的领跑者,使用率在短短数年间拉升到 83% 、几乎追平 webpack,满意度更是高达 98% ,依托基于 Go 的 esbuild 实现极快冷启动和热更新,重新定义了“本地开发体验”的下限
  • 在更底层 esbuild 的直接使用率已达 52%SWC 也拿到 83% 的满意度,说明社区正将编译热点下沉到 Rust/Go 等原生实现,再在其之上搭建更友好的工具。

五、其它工具

JS库使用情况

在通用 JS 库层面,数据清晰地表明开发者最在乎两件事:

  • 类型安全数据处理效率。以 TypeScript 为优先设计的校验库 Zod48% 的使用率登顶,成为“运行时数据校验 + 类型推导”领域的绝对主角,反映出大家在 API 返回、表单输入等链路上,对类型与数据一致性的强烈诉求。
  • 传统工具库 Lodash (39%) 依然宝刀不老,仍在大量项目中承担通用数据处理职责。
  • 而在日期处理上,date-fns (39%)Moment (25%)Day.js (24%) 等多家共存,本质上是对 JS 原生日期能力长期缺位的弥补
  • 即便是已经被视作“老古董”的 jQuery (16%) ,也仍凭借海量遗留项目保持着不可忽视的存在感。

AI使用情况

AI 工具已经深度嵌入前端开发者的日常工作流,成为新的基础设施:

  • ChatGPT60% 的使用率位居首位,承担了问答、代码草稿生成、调试思路辅助等“外脑”角色。
  • 深度集成 IDE 的 GitHub Copilot 使用率也达 51% ,更偏向于在写代码时提供上下文感知补全与重构建议,两者形成“离线思考 + 在线自动补全”的互补关系
  • 与此同时,Claude (44%)Google Gemini (28%) 等通用大模型产品也在快速补位,说明开发者愿意多源头对比体验
  • 值得注意的是 AI-native 编辑器 Cursor 已有 26% 的使用率,一部分人开始直接迁移到“以 AI 为核心交互对象”的编辑环境中,这预示着未来开发工具形态本身也会被 AI 重塑。
  • 另外,国产大模型 Deepseek 也榜上有名,占据了 8% 的使用率。

其它编程语言使用情况

这张图展示了 JS 开发者的多语言画像:

  • Python41% 的占比成为最常见的第二语言,依托后端开发、自动化脚本、数据分析与 AI 等丰富场景,为前端同学打开了更多技术边界。
  • PHP (27%) 的存在感说明不少人仍在使用 Web 传统栈构建项目或是在维护古老的历史项目。
  • 在工具链和 DevOps 侧,Bash (22%) 几乎是所有工程师的“必修课”。
  • Java (21%)Go (20%)C# (19%) 等企业级后端语言,以及以安全与性能著称的 Rust (16%) ,则构成了很多前端开发者向全栈或更底层系统方向延展的技能支点。

六、使用情况及痛点问题

TS与JS的使用情况

这张分布图有力地说明,TypeScript 已经从“可选增强”进化为 JavaScript 生态的默认选项

  • 48% 的受访者表示项目代码 100% 使用 TS 编写,体现出“一旦采用就倾向于全量迁移”的强烈偏好;在所有项目(包括纯 JS、纯 TS 与混合工程)中计算得到的平均采用率高达 77% ,意味着当今前端代码大部分都运行在类型系统保护之下;仍坚持纯 JS 的开发者仅占 6% ,多半集中在遗留项目或极轻量脚本场景;对于在做技术选型的新项目来说,这几乎已经构成了一个共识结论:默认使用 TS,而不是再纠结要不要上 TS

AI代码生成情况

这张图刻画了 AI 在代码生成中的“真实渗透率”,结论很清晰:

  • AI 目前更像是开发者的“副驾驶”,而非自动写代码的主力工程师。只有 10% 的受访者认为项目代码完全没有 AI 贡献,说明九成以上的团队或多或少已经在用 AI 提效;最集中的区间是 1%–20% 代码由 AI 生成(占 38% ),典型用法是让 AI 帮忙写模板代码、样板逻辑、特定算法实现或提供重构建议,而不是让它从零实现完整模块;总体算下来,平均约有 29% 的代码可以归功于 AI,这是一个不容忽视但远未到“全自动开发”的比例,也意味着复杂业务建模、架构设计和质量把控这些高阶工作,短期内仍牢牢掌握在人类开发者手中。

JS的痛点问题

在所有 JS 开发痛点中,真正让团队头疼的并不是某个语法细节,而是宏观层面的工程复杂度:

  • 代码架构(Code Architecture)38% 的提及率高居榜首,说明随着前端项目体量和生命周期不断拉长,如何拆分模块、划分边界、治理依赖、避免“屎山”成为最大挑战。
  • 紧随其后的是 状态管理(State Management,34%) ,无论是 React 的 hooks 与各种状态库,还是 Vue 的 Pinia,跨组件、跨页面的复杂状态流转依然极易失控。
  • 依赖管理(Managing Dependencies,32%) 也是老大难问题,node_modules 黑洞、版本冲突、安全漏洞以及 ESM/CJS 兼容性都会侵蚀工程稳定性。
  • 相对而言,曾经广受诟病的 异步 代码(Async Code) 如今只剩 11% 的人视其为痛点,Promiseasync/await 已经在很大程度上平滑了这块心智负担,这也从侧面证明语言与工具的演进确实可以逐步“消灭”一部分历史问题。

七、总结

首先,毫无疑问,TypeScript 已然胜出。它赢下的不只是「能编译成js的工具」的争论,而是语言本身。Deno 和 Bun 早已原生支持它。如今,你甚至能在稳定版 Node.js 中直接编写 TypeScript了。

而 Vite 的时代也已到来。今年,Vite 的下载量正式超越 webpack。与之相伴,Vitest 的使用量也大幅飙升。现在正是切换到新一代 Vite 工具链的好时机,而 2026 年注定会是全面落地之年—— 随着 Rolldown 稳定版发布,将驱动出更快的新一代 Vite,同时还有一体化的「Vite+」值得期待。

我们的开发工具从未如此优秀。但大家如今真正关心的却是另一个问题:AI 又将带来什么?

AI 即将彻底改变我们查阅文档、编写代码、做架构决策等一系列工作方式。各家公司都在全力押注全新的开发模式。对我们绝大多数人而言,AI 编程助手正在改变我们与代码交互的方式。

这是一件好事吗?

截至 2025 年底,已有近 30% 的代码由 AI 生成。Cursor 的人气暴涨,尽管它们暂时还无法撼动 VS Code 第一 IDE 的地位。而基于智能代理的工具,比如 Claude、Gemini 和 Copilot,也在迅速普及。

对开发者来说,无论使用什么工具,懂得分辨「什么是好代码」 将会比以往任何时候都更重要。紧跟新语言特性、知道该基于哪些库去开发,而非凭感觉从零手写一切,也会变得愈发关键。

现在,一天之内快速搭建新项目、轻松迁移老项目都已成为现实。这对框架和库的作者来说是个挑战。我们必须保证工具能持续服务好开发者,不能指望用户会一直因惯性而使用。

而这一点,恰恰值得所有开发者的期待。

就让我们拭目以待 2026 年的变化吧。我期待着更快的工具、更好的开发体验,以及技术真正成为能力放大器,强化我们自身的判断与选择。

父传子全解析:从基础到实战,新手也能零踩坑

2026年2月14日 14:30

在 Vue3 组件化开发中,父传子是最基础、最常用的组件通信方式,也是新手入门组件通信的第一步。无论是传递简单的字符串、数字,还是复杂的对象、数组,甚至是方法,父传子都有清晰、规范的实现方式。

不同于 Vue2 选项式 API 中 props 的写法,Vue3 组合式 API(

一、核心原理:单向数据流 + Props 传值

Vue3 父传子的核心逻辑只有两个关键词:Props单向数据流

  • Props:父组件通过在子组件标签上绑定属性(类似 HTML 标签属性),将数据传递给子组件;子组件通过定义 props,接收父组件传递过来的数据,相当于子组件的「输入参数」。
  • 单向数据流:数据只能从父组件流向子组件,子组件不能直接修改父组件传递过来的 props 数据(否则会报错)。如果子组件需要修改 props 数据,必须通过子传父的方式,通知父组件修改原始数据。

记住一句话:Props 是只读的,修改需找父组件。这是 Vue 组件通信的核心规范,也是避免数据混乱的关键。

父传子的核心流程(3步走):

  1. 父组件:在使用子组件的标签上,通过 :属性名="要传递的数据" 绑定数据;
  2. 子组件:通过 defineProps 定义要接收的 props(声明属性名和类型,可选但推荐);
  3. 子组件:在模板或脚本中,直接使用 props 中的数据(无需额外导入,直接通过 props.属性名 或 直接写属性名使用)。

二、基础用法:最简洁的父传子实现(必学)

我们用一个「父组件传递基本数据,子组件展示」的简单案例,讲解最基础的父传子写法,代码可直接复制到项目中运行,零门槛上手。

1. 父组件(Parent.vue):绑定数据并传递

<template>
  <div class="parent">
    <h3>我是父组件</h3>
    <p>父组件的基本数据:{{ parentName }}、{{ parentAge }}</p>
    <p>父组件的数组:{{ parentList.join('、') }}</p>
    <p>父组件的对象:{{ parentObj.name }} - {{ parentObj.gender }}</p>

    <!-- 1. 核心:在子组件标签上,通过 :属性名 绑定要传递的数据 -->
    <Child 
      :name="parentName"  // 传递字符串
      :age="parentAge"    // 传递数字
      :list="parentList"  // 传递数组
      :user-info="parentObj"  // 传递对象推荐用短横线命名)
    />
  </div>
</template>

<script setup>
// 引入子组件(Vue3 <script setup> 中,引入后可直接在模板中使用)
import Child from './Child.vue'
import { ref, reactive } from 'vue'

// 父组件要传递的数据(涵盖基本类型、数组、对象)
const parentName = ref('张三') // 字符串
const parentAge = ref(25)     // 数字
const parentList = ref(['苹果', '香蕉', '橙子']) // 数组
const parentObj = reactive({  // 对象
  name: '李四',
  gender: '男',
  age: 30
})
</script>

2. 子组件(Child.vue):定义Props并使用

<template>
  <div class="child">
    <h4>我是子组件(接收父组件传递的数据)</h4>
    <p>接收的字符串:{{ name }}</p>
    <p>接收的数字:{{ age }} 岁</p>
    <p>接收的数组:{{ list.join('、') }}</p>
    <p>接收的对象:{{ userInfo.name }}({{ userInfo.gender }})</p>
  </div>
</template>

<script setup>
// 2. 核心:通过 defineProps 定义要接收的 props
// 写法1:数组形式(简单场景,只声明属性名,不限制类型)
// const props = defineProps(['name', 'age', 'list', 'userInfo'])

// 写法2:对象形式(推荐,可限制类型、设置默认值、必填校验)
const props = defineProps({
  // 字符串类型
  name: {
    type: String,
    default: '默认用户名' // 默认值(父组件未传递时使用)
  },
  // 数字类型
  age: {
    type: Number,
    default: 18
  },
  // 数组类型(注意:数组/对象的默认值必须用函数返回,避免复用污染)
  list: {
    type: Array,
    default: () => [] // 数组默认值:返回空数组
  },
  // 对象类型(同理,默认值用函数返回)
  userInfo: {
    type: Object,
    default: () => ({}) // 对象默认值:返回空对象
  }
})

// 3. 在脚本中使用 props 数据(通过 props.属性名)
console.log('脚本中使用props:', props.name, props.age)
</script>

3. 基础细节说明(新手必看)

  • defineProps 是 Vue3 内置宏,无需导入,可直接在
  • 父组件传递数据时,属性名推荐用 kebab-case(短横线命名),比如 :user-info,子组件接收时用 camelCase(小驼峰命名),比如 userInfo,Vue 会自动做转换;
  • 数组/对象类型的 props,默认值必须用 函数返回(比如 default: () => []),否则多个子组件会复用同一个默认值,导致数据污染;
  • 子组件模板中可直接使用 props 的属性名(比如{{ name }}),脚本中必须通过 props.属性名 使用(比如 props.name)。

三、进阶用法:优化父传子的体验(实战常用)

基础用法能满足简单场景,但在实际开发中,我们还会遇到「必填校验」「类型多可选」「props 数据转换」等需求,这部分进阶技巧能让你的代码更规范、更健壮,避免后续维护踩坑。

1. Props 校验:必填项 + 多类型 + 自定义校验

通过 defineProps 的对象形式,我们可以对 props 进行全方位校验,避免父组件传递错误类型、遗漏必填数据,提升代码可靠性。

<script setup>
const props = defineProps({
  // 1. 必填项校验(required: true)
  username: {
    type: String,
    required: true, // 父组件必须传递该属性,否则控制台报警告
    default: '' // 注意:required: true 时,default 无效,可省略
  },

  // 2. 多类型校验(type 为数组)
  id: {
    type: [Number, String], // 允许父组件传递数字或字符串类型
    default: 0
  },

  // 3. 自定义校验(validator 函数)
  score: {
    type: Number,
    default: 0,
    // 自定义校验规则:分数必须在 0-100 之间
    validator: (value) => {
      return value >= 0 && value <= 100
    }
  }
})
</script>

说明:校验失败时,Vue 会在控制台打印警告(不影响代码运行),但能帮助我们快速定位问题,尤其适合团队协作场景。

2. Props 数据转换:computed 处理 props 数据

子组件不能直接修改 props 数据,但可以通过 computed 对 props 数据进行转换、格式化,满足子组件的展示需求,不影响原始 props 数据。

<template>
  <div class="child">
    <p>父组件传递的分数:{{ score }}</p>
    <p>转换后的等级:{{ scoreLevel }}</p>
    <p>父组件传递的姓名(大写):{{ upperName }}</p>
  </div>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  score: {
    type: Number,
    default: 0
  },
  name: {
    type: String,
    default: ''
  }
})

// 对 props 分数进行转换:0-60 不及格,60-80 及格,80-100 优秀
const scoreLevel = computed(() => {
  const { score } = props
  if (score >= 80) return '优秀'
  if (score >= 60) return '及格'
  return '不及格'
})

// 对 props 姓名进行格式化:转为大写
const upperName = computed(() => {
  return props.name.toUpperCase()
})
</script>

3. 传递方法:父组件给子组件传递回调函数

父传子不仅能传递数据,还能传递方法(回调函数)。核心用途:子组件通过调用父组件传递的方法,通知父组件修改数据(解决子组件不能直接修改 props 的问题)。

<!-- 父组件(Parent.vue) -->
<template>
  <div class="parent">
    <p>父组件计数器:{{ count }}</p>
    <!-- 传递方法::方法名="父组件方法" -->
    <Child 
      :count="count"
      :addCount="handleAddCount"  // 传递父组件的方法
    />
  </div>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const count = ref(0)

// 父组件的方法(将被传递给子组件)
const handleAddCount = () => {
  count.value++
}
</script>
<!-- 子组件(Child.vue) -->
<template>
  <div class="child">
    <p>子组件接收的计数器:{{ count }}</p>
    <!-- 调用父组件传递的方法 -->
    <button @click="addCount">点击让父组件计数器+1</button>
  </div>
</template>

<script setup>
const props = defineProps({
  count: {
    type: Number,
    default: 0
  },
  // 声明接收父组件传递的方法(type 为 Function)
  addCount: {
    type: Function,
    required: true
  }
})

// 也可以在脚本中调用父组件的方法
const callParentMethod = () => {
  props.addCount()
}
</script>

注意:传递方法时,父组件只需写 :addCount="handleAddCount"(不带括号),子组件调用时再带括号 addCount();如果父组件写 :addCount="handleAddCount()",会导致方法立即执行,而非传递方法本身。

4. 批量传递 props:v-bind 绑定对象

如果父组件需要给子组件传递多个 props,逐个绑定会比较繁琐,这时可以用 v-bind 批量绑定一个对象,子组件只需对应接收即可。

<!-- 父组件(Parent.vue) -->
<template>
  <div class="parent">
    <!-- 批量传递:v-bind="对象",等价于逐个绑定对象的属性 -->
    <Child v-bind="userObj" />
  </div>
</template>

<script setup>
import Child from './Child.vue'
import { reactive } from 'vue'

// 要批量传递的对象
const userObj = reactive({
  name: '张三',
  age: 25,
  gender: '男',
  address: '北京'
})
</script>
<!-- 子组件(Child.vue) -->
<script setup>
// 逐个接收父组件批量传递的 props,和普通 props 接收一致
const props = defineProps({
  name: String,
  age: Number,
  gender: String,
  address: String
})
</script>

四、实战场景:父传子的高频应用(贴合实际开发)

结合实际开发中的高频场景,补充 3 个常用案例,覆盖大部分父传子需求,直接套用即可。

场景1:父组件控制子组件弹窗显示/隐藏

<!-- 父组件(Parent.vue) -->
<template>
  <div class="parent">
    <button @click="visible = true">打开子组件弹窗</button>
    <!-- 传递弹窗显示状态 + 关闭弹窗的方法 -->
    <ChildModal 
      :visible="visible"
      :closeModal="handleCloseModal"
    />
  </div>
</template>

<script setup>
import ChildModal from './ChildModal.vue'
import { ref } from 'vue'

const visible = ref(false)

// 关闭弹窗的方法
const handleCloseModal = () => {
  visible.value = false
}
</script>
<!-- 子组件(ChildModal.vue) -->
<template>
  <div class="modal" v-if="visible">
    <div class="modal-content">
      <h4>子组件弹窗</h4>
      <button @click="closeModal">关闭弹窗</button>
    </div>
  </div>
</template>

<script setup>
const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  },
  closeModal: {
    type: Function,
    required: true
  }
})
</script>

场景2:父组件给子组件传递接口数据

实际开发中,父组件通常会请求接口,将接口返回的数据传递给子组件展示,这是最常见的场景之一。

<!-- 父组件(Parent.vue) -->
<template>
  <div class="parent">
    <!-- 加载中状态 -->
    <div v-if="loading">加载中...</div>
    <!-- 接口数据请求成功后,传递给子组件 -->
    <ChildList :list="goodsList" v-else />
  </div>
</template>

<script setup>
import ChildList from './ChildList.vue'
import { ref, onMounted } from 'vue'

const goodsList = ref([])
const loading = ref(false)

// 父组件请求接口
onMounted(async () => {
  loading.value = true
  try {
    const res = await fetch('https://api.example.com/goods')
    const data = await res.json()
    goodsList.value = data.list // 接口返回的列表数据
  } catch (err) {
    console.error('接口请求失败:', err)
  } finally {
    loading.value = false
  }
})
</script>

场景3:子组件复用,父组件传递不同配置

子组件复用是组件化开发的核心优势,通过父传子传递不同的配置,让同一个子组件实现不同的展示效果。

<!-- 父组件(Parent.vue) -->
<template>
  <div class="parent">
    <!-- 同一个子组件,传递不同配置,展示不同效果 -->
    <Button 
      :text="按钮1"
      :type="primary"
      :disabled="false"
    />
    <Button 
      :text="按钮2"
      :type="default"
      :disabled="true"
    />
  </div>
</template>

<script setup>
import Button from './Button.vue'
</script>
<!-- 子组件(Button.vue) -->
<template>
  <button 
    class="custom-btn"
    :class="type === 'primary' ? 'btn-primary' : 'btn-default'"
    :disabled="disabled"
  >
    {{ text }}
  </button>
</template>

<script setup>
const props = defineProps({
  text: {
    type: String,
    required: true
  },
  type: {
    type: String,
    default: 'default',
    validator: (val) => {
      return ['primary', 'default', 'danger'].includes(val)
    }
  },
  disabled: {
    type: Boolean,
    default: false
  }
})
</script>

五、常见坑点避坑指南(新手必看)

很多新手在写父传子时,会遇到「props 接收不到数据」「修改 props 报错」「方法传递后无法调用」等问题,以下是最常见的 5 个坑点,帮你快速避坑。

坑点1:父组件传递数据时,忘记加冒号(:)

错误写法:<Child name="parentName"></Child>(没有冒号,传递的是字符串 "parentName",而非父组件的 parentName 变量);

正确写法:<Child :name="parentName"></Child>(加冒号,传递的是父组件的变量)。

坑点2:子组件直接修改 props 数据

错误写法:props.name = '李四'(直接修改 props,会报错);

正确写法:通过父传子的方法,通知父组件修改原始数据(参考「传递方法」章节),或通过 computed 转换数据(不修改原始 props)。

坑点3:数组/对象 props 的默认值未用函数返回

错误写法:list: { type: Array, default: [] }(直接写数组,会导致多个子组件复用同一个数组,数据污染);

正确写法:list: { type: Array, default: () => [] }(用函数返回数组,每个子组件都会得到一个新的空数组)。

坑点4:传递方法时,父组件带了括号

错误写法:<Child :addCount="handleAddCount()"></Child>(方法立即执行,传递的是方法的返回值,而非方法本身);

正确写法:<Child :addCount="handleAddCount"></Child>(不带括号,传递方法本身)。

坑点5:props 命名大小写不一致

错误写法:父组件 :userInfo="parentObj",子组件接收 userinfo(小写 i);

正确写法:父组件用 kebab-case(:user-info),子组件用 camelCase(userInfo),或保持大小写一致(不推荐)。

六、总结:父传子核心要点回顾

Vue3 父传子的核心就是「Props 传值 + 单向数据流」,记住以下 4 个核心要点,就能应对所有父传子场景:

  1. 基础流程:父组件 :属性名="数据" 绑定 → 子组件 defineProps 接收 → 子组件使用数据;
  2. 核心规范:Props 是只读的,子组件不能直接修改,修改需通过父传子的方法通知父组件;
  3. 进阶技巧:props 校验提升可靠性,computed 转换数据,v-bind 批量传值,传递方法实现双向交互;
  4. 避坑关键:加冒号传递变量、不直接修改 props、数组/对象默认值用函数返回、传递方法不带括号。

父传子是 Vue3 组件通信中最基础、最常用的方式,掌握它之后,再学习子传父、跨层级通信(provide/inject)、全局通信(Pinia)会更轻松。

成都天府大道车辆碰撞事故完成责任认定,酒后驾车当事人全责

2026年2月14日 14:04
成都天府大道此前发生的小米SU7 Ultra交通事故持续引发关注。据南都N视频报道,目前,事故责任认定已完成,驾驶人邓某某负全责。涉事车辆与前车发生碰撞前3秒时行驶速度超200km/h、撞击时车速超160km/h,超过事发路段限速要求。此前,成都警方曾通报,事发2025年10月13日凌晨3时18分许,事故造成驾驶人邓某某死亡,涉事两车不同程度受损;驾驶人涉嫌酒后驾驶机动车。

真我 Neo8 体验:从性能、显示到影像来了一次全面升级,真正全面的性价比旗舰

作者 梁梦麟
2026年2月14日 13:56

最近,真我发布了 Neo 系列新机——真我 Neo8,首销价 2399 元,国补到手价 2039.15 元。

同样定位在 2000 元档的性能旗舰上,真我这次可算是给 Neo 系列来了一次非常全面的升级。不仅升级了骁龙芯片和更大的电池,屏幕性能再上了一个档次,还加上了潜望式长焦镜头,从主打性能的机型变成全方位提升的产品。

先讲最重要的性能,真我 Neo8 这次搭载最新的第五代高通骁龙 8 移动平台,搭配 LPDDR5X RAM 和 UFS 4.1 ROM 的储存组合。机身内置了大气流冷锋散热系统,总散热面积为 39225mm²,覆盖了 65% 的机身面积,以提升散热效能。

为了提升手机的性能释放能力,真我这次在 Neo8 的游戏空间中加入了新的极客性能面板,用户可以在自由调节 CPU、GPU 频率,还有内置五档温度调节,尽可能提升游戏性能。

Neo8 也搭载了新一代 GT 性能引擎,透过「先知能效调度技术」进行更精准的性能,提升重负载状态下的稳帧表现。

目前,真我 Neo8 支持《三角洲行动》、《暗区突围》、《和平精英》、《使命召唤手游》、《穿越火线手游》等游戏的原生 165 帧模式,《无畏契约》和《王者荣耀》的原生 144 帧模式,《原神》和《崩坏:星穹铁道》的 120 + 1.5k 超分超帧模式。

性能释放升级之后,真我在 Neo8 上加入了 PC 掌机模式。

这个模式可以绑定 Steam 账号并下载里面的内容,以及游戏存档同步的功能,目前《空洞骑士:丝之歌》、《哈迪斯 2》、《只狼:影逝二度》、《古墓丽影 9》、《女神异闻录 4 黄金版》等游戏。

屏幕部分,Neo8 用上了一块 6.78 英寸165Hz 三星苍穹屏。

屏幕搭载了三星 M14 发光材质,手动最高亮度为 1000nits,全局峰值亮度达到 18nits,局部峰值亮度可达到 6500nits,还有个 20% 窗口下支持 3800nits 显示的阳光模式。屏幕还支持「全亮度 DC 调光+硬件级低蓝光+智能护眼」组合,有 TUV 莱茵无频闪认证和可以智能调节显示参数,保证护眼的前提下提升显示准度。

屏幕显示刷新率最高位 165Hz,支持真我 GT8 Pro 同款的游戏触显同步技术,瞬时触控最高刀到 3800Hz,十字触控有 360Hz 报点率。

最直观的体验就是在 FPS 的时候操作可以更加跟手,开镜响应更快,显示的卡顿会在少一点。

续航方面,真我这次给到一块 8000mAh 的电池,容量比 GT8 标准版还要大 1000mAh。日常使用,只要不是经常用最高性能的模式打游戏,那坚持两天还是没有问题的。

比起续航能力,Neo8 充电性能的变化会来得更有吸引力。

手机支持 80W 闪充,并支持 UFCS、PPS、PD、QC 的全协议栈快充和旁路供电,8000mAh 的手机用自家快充组合需要 75 分钟,接入 AI 小电拼 Ultra 最高支持 51W 充电,0-100 充电需要 77 分钟。这个成绩看来,Neo8 是真的可以彻底告别官方充电器限制的一款产品。

影像部分,真我 Neo8 选择的是同价位非常罕见的「超广+广角主摄+潜望式长焦」标准三摄。

主摄用了索尼 1/1.56 英寸 5000 万像素传感器,原生焦段支持 8K 超清直出。长焦部分用上了 3.5 倍光学变焦的潜望式镜头,支持 7 倍无损变焦以及最高 40 倍的数码变焦。相机内有 AI 望远算法的加持,进一步听声远摄的清晰度。

此外,Neo8 内置了影调模式,用户可以直接在相机内滑动选取不同的色彩质感,也可以自定义色彩、锐度、颗粒等参数。人像模式支持了 1x、1.5x、2x、3.5x 和 4x 的选项,机内还提供了 Live Photo 慢动作,丰富相机玩法。

不过,放到这个价位的手机身上,能够实现超广、主摄、长焦的整齐覆盖,而且长焦用的还是潜望式长焦,这一点就已经比很多定位性价比的机型要好了。

最后来看看外观,真我 Neo8 用了透明玻璃后盖设计,后盖加入了新一代漫反射工艺,手机后盖受光的情况下可以见到不同的光影效果。

透明后盖下有通过单层透明立体分区工艺,透过立体分区视觉做出 11 种纹理深度差低到 1.2μm 的差异化纹理和微球面处理,增加了后侧细节的丰富度。

换成透明后盖之后,真我经典的觉醒光环设计回归到 Neo8 DECO 右侧,环绕着 NFC 模块,可以根据提醒、游戏状态、开机等场景做光效切换。

机身采用了磨砂金属中框,提升了整体的手感和外观质感。

最后看看售价,真我 Neo8 有五个储存版本,首销价 2399 元起:

  • 12GB + 256GB:首销价 2399 元,国补到手价 2039.15 元
  • 16GB + 256GB:首销价 2699 元,国补到手价 2294.15 元
  • 12GB + 512GB:首销价 2899 元,国补到手价 2464.15 元
  • 16GB + 512GB:首销价 3199 元,国补到手价 2719.15 元
  • 16GB + 1TB:首销价 3699 元,国补到手价 3199 元
「买吧,不贵。」

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


去年国内风电新增吊装容量创历史新高,海外出口表现亮眼

2026年2月14日 13:45
中国可再生能源学会风能专业委员会(CWEA)2月13日晚最新发布的《2025年中国风电吊装容量统计简报》显示,去年国内风电新增装机18272台,新增吊装容量1.3亿千瓦,创历史新高,同比增长49.9%,远超2024年9.6%的同比增幅。陆风、海风发展分化明显,陆上风电仍是国内增长绝对主力。2025年国内陆上风电新增装机1.25亿千瓦,占全部新增装机的95.8%,占比较2024年进一步提升了2.3个百分点。海上风电因受多重壁垒制约,去年新增装机容量556万千瓦,同比下滑了2.3%,占全部新增装机容量比重下滑至4.2%。

京东回应“巴黎仓库遭遇盗窃”:绝大多数被盗物资被顺利追回

2026年2月14日 13:15
36氪获悉,从京东方面获悉,去年12月22日,位于法国巴黎大区的京东仓库遭遇盗窃。近2个月来,在法国巴黎警方的不懈努力和中国驻法大使馆的大力支持下,案件侦破工作已取得阶段性成果,目前绝大多数被盗物资被顺利追回。京东表示,对于失窃货物的后续追回和交接,将全力配合巴黎警方,做好妥善安排处理。

今天全国铁路预计发送旅客1535万人次

2026年2月14日 13:00
今天春运进入第13天,各地交通出行客流保持高位运行。14日,全国铁路预计发送旅客1535万人次。高速公路车流量持续增长,预计京津冀、长三角、珠三角和成渝地区车流量大,民航方面,今天预计运输旅客245万人次。

i18n Ally Next:重新定义 VS Code 国际化开发体验

作者 Lyda
2026年2月14日 12:38

生成关于 i18n Ally Next 的图片.png

做国际化(i18n)是前端开发中最「看起来简单、做起来要命」的事情之一。翻译文件散落各处,键名拼写错误要到运行时才发现,新增语言要手动逐个补全,源语言改了但翻译没跟上……如果你也深有同感,这篇文章就是写给你的。

为什么要做 i18n Ally Next?

i18n Ally 是 VS Code 生态中最受欢迎的国际化插件之一,由 @antfu 创建。它提供了内联注解、翻译管理、文案提取等一系列强大功能,极大地提升了 i18n 开发体验。

但随着时间推移,原项目的维护逐渐放缓。社区积累了大量 Issue 和 PR 未被处理,一些现代框架(如 Next-intl、Svelte 5)的支持也迟迟没有跟上。更关键的是——原项目没有文档站点,所有使用说明散落在 README 和 Wiki 中,新用户上手成本很高。

i18n Ally Next 正是在这样的背景下诞生的——我们 fork 了原项目,在保留所有经典功能的基础上,持续修复 Bug、新增特性、改善性能,并且从零搭建了完整的文档体系

为什么文档这么重要?

原版 i18n Ally 功能强大,但有一个致命问题:你不知道它能做什么

很多开发者安装后只用到了内联注解这一个功能,对审阅系统、自定义框架、正则匹配、路径匹配等高级功能一无所知。这不是用户的问题,是文档缺失的问题。

i18n Ally Next 的文档站点从以下几个维度重新组织了内容:

🧱 结构化的指南体系

  • 快速开始 — 从安装到看到第一个内联注解,5 分钟搞定
  • 支持的框架 — 完整列出 25+ 支持的框架及其自动检测机制
  • 语言文件格式 — JSON、YAML、JSON5、PO、Properties、FTL……每种格式的用法和注意事项
  • 命名空间 — 大型项目必备的翻译文件组织方式
  • 文案提取 — 从硬编码字符串到 i18n 键的完整工作流
  • 审阅系统 — 团队协作翻译的质量保障
  • 机器翻译 — 8 种翻译引擎的配置与对比

🏗️ 框架最佳实践

不同框架的 i18n 方案差异很大。我们为每个主流框架编写了专属的最佳实践指南:

📋 完整的配置参考

每一个配置项都有:类型、默认值、使用场景说明和代码示例。不再需要猜测某个配置是干什么的。

新增功能详解

🤖 Editor LLM:零配置 AI 翻译

这是 i18n Ally Next 最具创新性的功能之一。它直接调用你编辑器内置的大语言模型进行翻译——无需 API Key,无需额外配置

{ "i18n-ally-next.translate.engines": ["editor-llm"] }

它会自动检测你的编辑器环境:

  • VS Code — 调用 GitHub Copilot
  • Cursor — 调用 Cursor 内置模型
  • Windsurf — 调用 Windsurf 内置模型

更强大的是,Editor LLM 支持批量翻译。当你选择翻译多个键时,它会将同一语言对的翻译请求合并为一次 API 调用,按 JSON 格式批量处理,大幅提升翻译速度并降低 token 消耗。

你也可以指定模型:

{ "i18n-ally-next.translate.editor-llm.model": "gpt-4o" }

🦙 Ollama:完全离线的本地翻译

对于有数据安全要求的团队,Ollama 引擎让你可以使用本地部署的大模型进行翻译,数据完全不出本机

{
  "i18n-ally-next.translate.engines": ["ollama"],
  "i18n-ally-next.translate.ollama.apiRoot": "http://localhost:11434",
  "i18n-ally-next.translate.ollama.model": "qwen2.5:latest"
}

通过 OpenAI 兼容 API 调用,支持任何 Ollama 上可用的模型。翻译 prompt 经过专门优化,能正确保留 {0}{name}{{variable}} 等占位符。

🔌 8 种翻译引擎全覆盖

引擎 特点 适用场景
Google 免费、语言覆盖广 日常开发
Google CN 国内可直接访问 国内开发者
DeepL 翻译质量最佳 面向用户的正式翻译
OpenAI 灵活、可自定义 API 地址 需要高质量 + 自定义
Ollama 完全离线、数据安全 企业内网环境
Editor LLM 零配置、批量翻译 快速迭代
百度翻译 国内 API、中文优化 中文项目
LibreTranslate 开源自托管 完全自主可控

引擎可以配置多个作为 fallback:

{ "i18n-ally-next.translate.engines": ["editor-llm", "deepl", "google"] }

🕵️ 陈旧翻译检测

这是一个容易被忽视但极其重要的功能。当源语言的文案发生变更时,其他语言的翻译可能已经过时了——但你不会收到任何提醒。

i18n Ally Next 的陈旧翻译检测(Stale Translation Check)解决了这个问题:

  1. 插件会记录每个键的源语言快照
  2. 当你运行检测命令时,它会对比快照与当前值
  3. 发现变更后,你可以选择:
    • 全部重新翻译 — 一键将所有过期翻译发送到翻译引擎
    • 逐个确认 — 逐条查看变更内容,决定是否重新翻译
    • 仅更新快照 — 确认当前翻译仍然有效,更新基准

这意味着你的翻译永远不会「悄悄过期」。

🔍 全项目扫描与批量提取

单文件的硬编码检测很有用,但真正的 i18n 迁移需要全项目级别的能力。

「扫描并提取全部」命令可以:

  1. 扫描项目中所有支持的文件(可通过 glob 配置范围)
  2. 检测每个文件中的硬编码字符串
  3. 显示扫描结果摘要(N 个文件,M 个硬编码字符串)
  4. 确认后自动批量提取,为每个字符串生成键名并写入 locale 文件
{
  "i18n-ally-next.extract.scanningInclude": ["src/**/*.{ts,tsx,vue}"],
  "i18n-ally-next.extract.scanningIgnore": ["src/generated/**"],
  "i18n-ally-next.extract.keygenStrategy": "slug",
  "i18n-ally-next.extract.keygenStyle": "camelCase"
}

📝 审阅系统(v2的功能)

翻译不是一个人的事。i18n Ally Next 内置了审阅系统(Review System),支持:

  • 逐条审阅翻译结果,标记为「通过」或「需修改」
  • 留下评论,与团队成员异步协作
  • 审阅数据以 JSON 存储在项目中,可纳入版本控制
  • 翻译结果可先保存为候选翻译translate.saveAsCandidates),审阅通过后再正式写入

这意味着翻译质量不再是黑盒——每一条翻译都有迹可循。

自定义框架:支持任意 i18n 方案

这是 i18n Ally Next 最灵活的功能之一。无论你使用什么 i18n 库,甚至是团队自研的方案,都可以通过一个 YAML 配置文件让插件完美支持。

为什么需要自定义框架?

内置框架覆盖了 25+ 主流方案,但现实中总有例外:

  • 公司内部封装的 i18n 工具函数
  • 使用了非标准的翻译函数名(如 __(), lang(), msg()
  • 新兴框架尚未被内置支持
  • 需要同时匹配多种调用模式

如何配置?

在项目根目录创建 .vscode/i18n-ally-next-custom-framework.yml

# 指定生效的文件类型
languageIds:
  - typescript
  - typescriptreact
  - vue

# 正则匹配翻译函数调用,{key} 是占位符
usageMatchRegex:
  - "\\Wt\\(\\s*['\"`]({key})['\"`]"
  - "\\W\\$t\\(\\s*['\"`]({key})['\"`]"
  - "\\Wi18n\\.t\\(\\s*['\"`]({key})['\"`]"

# 提取重构模板,$1 代表键名
refactorTemplates:
  - "t('$1')"
  - "{t('$1')}"

# 命名空间支持
namespace: true
namespaceDelimiter: ":"

# 作用域范围检测(如 React useTranslation hook)
scopeRangeRegex: "useTranslation\\(['\"](.+?)['\"]\\)"

# 是否禁用所有内置框架
monopoly: false

作用域范围是什么?

scopeRangeRegex 是一个非常实用的功能。以 React 为例:

const { t } = useTranslation("settings")

t("title")        // → 自动解析为 "settings.title"
t("theme.dark")   // → 自动解析为 "settings.theme.dark"

插件会根据正则匹配的结果自动划分作用域范围——从匹配位置到下一个匹配位置(或文件末尾)。在作用域内的所有 t() 调用都会自动加上命名空间前缀。

热重载

修改 YAML 配置文件后无需重启 VS Code,插件会自动检测文件变更并重新加载。这让调试正则表达式变得非常方便——改完立刻看效果。

快速上手

安装

在 VS Code 扩展面板搜索 i18n Ally Next,或从以下渠道安装:

最小配置

对于大多数项目,你只需要两步:

1. 指定语言文件路径

// .vscode/settings.json
{
  "i18n-ally-next.localesPaths": ["src/locales"],
  "i18n-ally-next.sourceLanguage": "zh-CN"
}

2. 打开项目,开始使用

插件会自动检测你的 i18n 框架(Vue I18n、react-i18next 等),无需额外配置。

打开任意包含翻译键的文件,你会看到:

  • 🏷️ 翻译键旁边出现内联注解,直接显示翻译值
  • 🌐 悬停键名可查看所有语言的翻译
  • ✏️ 点击即可编辑翻译
  • 📊 侧边栏显示翻译进度和缺失项

从 i18n Ally 迁移

如果你正在使用原版 i18n Ally,迁移非常简单:

  1. 卸载 i18n Ally
  2. 安装 i18n Ally Next
  3. settings.json 中的 i18n-ally. 前缀替换为 i18n-ally-next.

所有配置项保持兼容,无需其他改动。

写在最后

国际化不应该是痛苦的。i18n Ally Next 的目标是让 i18n 成为开发流程中自然而然的一部分——写代码时看到翻译,提交前检查缺失,源文案变更时自动提醒,协作时有据可查。

我们不只是在做一个插件,更是在构建一套完整的 i18n 开发工具链:从文档到配置,从检测到提取,从翻译到审阅,每一个环节都有对应的解决方案。

如果你觉得这个项目有用,欢迎:

  • ⭐ 在 GitHub 上给我们一个 Star
  • 🐛 提交 Issue 反馈问题
  • 💬 分享给你的团队和朋友
  • 📖 阅读完整文档开始使用

本文同步发布于 i18n Ally Next 官方文档

❌
❌