普通视图

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

对话孙来春:年入25亿的林清轩不想做中国欧莱雅 |厚雪专访

2026年5月7日 09:30

作者 | 李小霞

访谈| 李小霞、杨轩

编辑 | 乔芊

林清轩创始人孙来春有一段广为人知的惊险故事。

那是2020年春节疫情初起,林清轩大面积闭店,业绩断崖式下滑90%,公司账户里的现金最多支撑67天。

在那一刻,孙来春突然发现自己过去十几年引以为傲的线下基因,变成了最不堪一击的包袱。

后来的故事很多人也都知道。孙来春走进直播间,从一个害怕镜头、会紧张到呕吐的中年男人,变成了能和屏幕那头的陌生用户聊护肤、聊品牌的网红CEO。

随之变化的,还有林清轩的命运。公司在去年末完成了上市,成为港股“国货高端护肤第一股”。

这些光鲜放到以前不敢想象:入驻商场被赶来赶去,融资屡屡碰壁。如今,这一切都翻篇了。

最近,林清轩发布了上市来的首份财报:

林清轩2025年营收24.5亿元,同比增长103%;净利润为3.6亿元,同比增长93%,毛利率维持在82%,核心旗舰品山茶花精华油累计销量突破5500万瓶。

线上收入占比首次超过线下,升至70.4%,创始人IP与品牌IP矩阵,成为区别于传统美妆品牌的独特标签。

质疑也纷至沓来。诸如“产品单一”“国货做不起高端”……孙来春自己也说,“品牌冲高端的一路上,嘲讽、质疑、不理解从未间断,只能坦然接纳。”

在他的规划中,做大山茶花精华油只是林清轩的第一步,他想要的是,参考欧莱雅,做多品牌以及国际化。

这并不是说林清轩要「成为中国的欧莱雅」——一个无数中国美妆品牌都曾喊过的口号。

“我们只想成为林清轩。”

近日,36氪与林清轩创始人进行了一次访谈,以下为对话内容(经编辑):

 「距离真正的高端还有差距。」

36氪:林清轩最早从平价手工皂起家,从大众走到高端,中间经历了怎样的转变?国内能做成高端美妆的国货并不多,为什么是林清轩?

孙来春:林清轩起点确实低,但不能因此认为我们做不了高端护肤品。没有人有权利来定义一个品牌“他出生在哪里就应该在哪里”。

当年我们虽然是做手工皂,但选择了一条差异化路径,就是开线下百货和购物中心店。这真正决定了林清轩未来的战略选择。

还有一点,我们主做直营店,后来又做了研发和全产业链布局,全部是重资产投入,慢慢具备了打造高端品牌的可能性。

从2012年至今,我们用了14年时间准备、运作、突围,九死一生才走出来,到现在距离真正的高端还有差距。

36氪:这段距离体现在哪里?

孙来春:比如高端精华、面霜,还没有成功打造出来。其次品牌影响力,消费者认知都要有过程。换个角度看,这恰恰是我们未来的成长空间。

36氪:品牌刚起步没什么声量的时候,你们当初是怎么说服商场引进入驻的?这个过程是不是特别难?

孙来春:不是难,是太难了。2008年3月,林清轩产品已经全部准备就绪,部分都已经生产入库。从3月到12月,整整九个月,我跑了两百多家商场、百货和购物中心,90%都是直接拒绝。

现在回想起来,特别感谢上海中山公园龙之梦。当时商场刚开业,比较愿意接纳我们这样的新国货品牌。

十几年后我偶遇凯德当时的总经理,当面表达感谢。他说商场每年都会预留名额,引进3—5家有创新潜力的本土品牌做扶持。过往引进过很多,但林清轩是为数不多,从一家店起步、走向全国,未来还有机会走向全球的品牌。

我们开到某知名百货时,已经有四五十家门店规模了。起初商场给了不错的位置,经营半年后,商场引进日本大牌,就以“国货不配和国际品牌同区”为由,把我们赶到了冷门的国产品牌专区;又过半年引进韩系品牌,再一次把我们排挤到地下楼层。

我们干了一年下来,装修两次一分钱不挣。又过了六个月,说地下改造,不要化妆品了,就把我们赶出去了。

不过后来换了一个CEO,比较认同中国文化,他认为中国品牌在崛起,就把我们重新招进来了。我当时也跟对方说,不用有顾虑,让挪位置就挪,让搬走就搬走,我们都能接受。

一路走来,林清轩经历过无数次被排挤、被挪位,一点点熬到现在快六百家门店。所以我说要有个过程,忍得了寂寞,接受不断的蹂躏,才有未来。

 品类单一,只是阶段性状态

36氪:大众对以油护肤的认知度并不高,你们是怎么去突破市场教育的?

孙来春:说实话,林清轩做以油养肤、开辟精华油新品类,难度比品牌高端转型、布局线下门店还要大。

首先是用户认知空白:大众习惯用水乳、普通精华,根本没有用油护肤的意识;而且大众固有印象里,油就等于油腻,大家反而都在买控油产品,天然存在负面认知。

其次是山茶花原料认知弱:大家只知道山茶花好看,但不清楚它的护肤功效,需要花大量精力做市场教育。

唯一的优势是,山茶油护肤有千百年古籍、宫廷沿用和文史记载背书,自古就被用来润肤抗皱。除此之外,一切都要靠我们自己投入科研,把产品做得轻薄好吸收,再持续做市场推广。

36氪:依靠山茶花 + 以油养肤,能否撑起林清轩的长期大盘?外界也一直有声音质疑品牌单品过于单一。

孙来春:这就像早年的可口可乐开创了一个新品类,质疑它的人,觉得产品线太单一,只有一款可乐,没有矿泉水、酒水、零食等多元品类。而今天可口可乐也早已拓展出雪碧、天然水等多条产品线。

像现在SK-II你会觉得他家就卖神仙水吗、雅诗兰黛只卖精华吗。我们是先聚焦成为一个品类王,后续再逐步延伸,打造「1+4+N」多旗舰的高端护肤。

外界觉得现在林清轩靠单品支撑,这话没错,但只是阶段性状态,我们的第二成长曲线已经出来、新品牌布局也已经在路上了。

36氪:新品牌布局是什么?

孙来春:我们分为三个阶段布局。第一个阶段就是单品牌的高端化,目前正在推进中,初见成果。第二个多品牌矩阵。针对不同的人群,甚至不同的地区,不同的皮肤性质,形成一个差异化的多品牌。第三个是全球化。

36氪:会选择什么样的多品牌,或者做哪些品类的多品牌?

孙来春:目前林清轩定位中高端,还有不少布局空白需要补齐:缺少顶奢线,也没有香氛、彩妆、男士护肤及大众平价品牌。参考欧莱雅,如果把林清轩比作兰蔻,我们还缺赫莲娜、巴黎欧莱雅、美宝莲等。

就算同属中高端赛道,我们主打以油养肤精华,还缺少主打面霜、化妆水、功效精华等细分定位的品牌。

 学习欧莱雅做多品牌布局

36氪:你们会想成为中国的欧莱雅吗?

孙来春:我们不是要成为中国的欧莱雅。我们是学习欧莱雅集团多品牌布局,我们只想成为林清轩。

36氪:不过这么多年想成为欧莱雅的公司很多,但至今没有出现。

孙来春:我相信中国企业中一定会出现欧莱雅、雅诗兰黛一样的国际集团。是哪家不确定,大家都在努力,而且努力都有成果。

36氪:你应该也有听到很多杂音,说中国品牌做高端化就是割韭菜,你们会怎么消解大众这种固有偏见?

孙来春:小时候,我住在煤矿矿区,当时环境风气不好,不少小孩随地吐痰、砸玻璃、扔石头捣乱。大家都跟着一起做坏事时,你若坚持不跟风、不参与,不仅不被认同,还会被嘲笑、排挤,甚至被同伴欺负。这就像一篓螃蟹,有一只想要爬出去,其余的都会把它拖下来。

做品牌也是一样,不能因为当下缺少本土高端品牌,就断定国货不适合走高端化。这是错误的归纳思维,应该用长远的演绎思维看趋势。二十年前我就笃定,中国一定能诞生比肩国际大牌的高端护肤品牌,也抱着这个梦想做起了林清轩。

36氪:所以这种声音不会成为你们的障碍。

孙来春:我奶奶是个山东老太太。她常说的一句话是“光听拉拉蛄叫,就别种地了”,大意就是不要因外界的声音而失去动力。

36氪:2024 年高端护肤排名里,林清轩是前15名里唯一的国货。过去的成绩已经证明,我们现在有实力和国际大牌掰手腕了。接下来,要怎么继续跟他们比拼?

孙来春:过去三年,国产美妆整体市场占比从不到50% ,今年已经涨到 57%,份额还在持续扩大。原因一是中国品牌的科技实力上来了。第二消费者的文化自信上来了。所以说怎么去竞争?还是踏踏实实做好科研,老老实实做好品牌,做好零售,做好服务,一步一步的迭代。

 高毛利,不是在抖音盈利的核心点

36氪:目前国内高端美妆里,真正走到上市阶段的,差不多就毛戈平和林清轩两家。为什么偏偏是这两家?

孙来春:我们走的路完全不一样,但有共同基因,比方说愿意开线下门店,踏踏实实做好零售和顾客体验,再就是敢于去开创新品类,敢于做中高端品牌的投入。

36氪:做电商直播,你为什么非要亲自下场?

孙来春:在巨大的困难面前,作为一个领头的,你是说兄弟们跟我冲,还是兄弟们跟(着)我冲?我选择了后者,那时候大家都居家隔离,没人愿意主动做直播。

我不光自己上,还号召公司所有管理层都去尝试直播、摸索这个新事物。只有亲自下场试过,才能找到手感,知道这件事到底难不难,知道茶是苦、药是苦的。

我觉得任何一个老板,如果对业务一点手感没有,在那瞎指挥肯定不行。公司转型关键时候,作为领导干部肯定要冲锋陷阵。当业务已经成型时,领导干部要从前线下来打造组织、团队,培养复制更多优秀的将军,而不是一直在一线。

36氪:你有推崇的企业家或企业吗?

孙来春:现阶段对我们帮助最大的是华为,它从战略到执行非常厉害。另外我最近对丹纳赫的体系非常着迷。接下来林清轩布局多品牌、国际化和并购战略,也会计划将丹纳赫这套体系吸纳过来。

36氪:林清轩的毛利很高,这是否也是支撑你们能持续做好抖音的关键,毕竟抖音的成本要高一些。

孙来春:我们的产品复购率平均达到34.2%,但有些品牌的护肤品只有4.5%的复购率,就算天天拼命拉新,用户来过一次就再也不回头,根本赚不到钱。

所以不是高毛利,而是高复购率决定了林清轩的抖音能够有利润。再一个就是二十多年线下门店沉淀下来的品牌力,带来了高转化。

36氪:当初生死存亡之际,你做了两个重要决定,一个是入局电商,另一个是开放融资,找投资人这个过程艰难吗?

孙来春:2020年之前,林清轩一直坚持“防火防盗防投资人”,压根不想融资。那时候公司经营状况很好,很多投资人找上门,反复劝我们接受投资,都被我拒绝了。

结果2020年一下来了疫情,突然发现我们的现金流,只能支撑67天。我就有一种巨大的危机感。

然后我就开始大量找投资人,在上海隔离的一个礼拜,过年初一到初七,联系了五六十个所能联系上的投资人。有的就说等疫情过去之后,过来看一下,但我心想等疫情过去了,估计公司也��有了。

我想起马云说的:天晴时就要修房子,特别后悔,天晴的时候没修房子。现在下雨屋也漏。所以说那时候就下决心自己做业绩,转型电商,转型短视频直播带货,先把企业救过来。

后来企业转型活过来了,我们又开始主动找投资人。但大部分人表示:“我们现在都投电商网红品牌,你这个品牌太不性感,你开这家店怎么估值?”2020年我们已经卖到5亿的销售额。有一个投资人说你没做互联网,只能给你一个亿的估值。这样的故事数不胜数。

36氪:现在对林清轩来说,其实已经度过了最艰难的时期了,应该没有比2020年那会让你面临如此巨大压力的事情了吧。

孙来春:我觉得每个阶段压力不同,以前是生存压力,现在是发展压力。上市才是万里长征第一步,未来还要去翻越一座又一座山,去实现品牌高端化、多品牌矩阵、全球化。革命的道路刚刚开始。

斯坦福博士后创立的柔性触觉传感器公司获超亿元融资,低成本触觉手套将量产落地|硬氪首发

2026年5月7日 09:30

作者|黄楠

编辑|袁斯来

硬氪获悉,途见科技(北京)有限公司(以下简称“途见科技”)近日完成Pre-A轮、Pre-A+轮融资,两轮累计金额超亿元,投资方包括联想之星、华控基金、元禾璞华、元禾控股、洪泰基金等。资金将主要用于核心技术研发,机器人全身触觉、触觉手套、触觉数据采集工具链等产品矩阵拓展和生产体系升级,加速市场化落地验证。

途见科技成立于2020年1月,聚焦可拉伸多模态柔性电子皮肤和具身触觉感知系统的研发与制造,以及触觉数据采集工具链的开发和模型建立,推动技术在具身智能、智能座舱、健康生活及情感陪伴等场景落地。公司分设北京和深圳两大总部,在美国硅谷成立前沿技术探索中心,已完成2000平米研发中心及自动化产线的搭建并实现投产。

创始人兼CEO���建诚长期专注柔性电子皮肤材料、器件与系统集成领域研究,他曾在美国斯坦福大学鲍哲南院士实验室从事博士后研究,并先后主导了科技部首批国家重点研发计划颠覆性技术创新项目等多个国家级、省部级重点项目。

途见科技自动化产线(图源/企业)

全球触觉传感器市场正伴随具身智能的发展加速扩容。据Verified Market Research测算,2024年全球整体触觉传感器市场规模超150亿美元,预计2031年将突破350亿美元,年复合增长率约12.8%;其中,依托人形机器人、智能装备与医疗健康等新兴场景的爆发,柔性触觉感知赛道迎来高速增长,年复合增长率达35%。

具体到技术路线层面,触觉传感器的类别划分通常有两个维度。按传感原理,主流方案包括电阻、电容、压电、摩擦电、光学/视觉、磁学等,不同原理决定了传感器的灵敏度、量程和响应速度。按材料与结构形态,则大致可分为四类:刚性硅基/陶瓷阵列、半柔性基底阵列、织物基以及本征可拉伸弹性体。

其中,不同材料结构的四条路径各有优劣。刚性阵列精度高,但难以贴合机器人本体及灵巧手的复杂曲面;半柔性基底解决了部分弯曲问题,但不可弯折,且依然缺乏本征可拉伸性,在动态变形场景下容易失效;织物方案虽然柔软透气,却受限于密度、精度、温漂和防水性。

相较之下,本征可拉伸材料被普遍认为是更贴合仿生皮肤特性、更符合“第一性原理”的前沿方向,这也成为途见科技重点布局的差异化路线。

途见科技自主研发了一系列柔性材料体系,在高拉伸条件下能够稳定保持电学性能,拉伸度超过100%且能完全恢复,弯曲半径小于1毫米,且适配各类异形不可展曲面,可以有效解决传统柔性器件“不可拉伸”或“一拉伸就失效”的痛点。

途见科技研发的电子皮肤(图源/企业)

在创新材料基础上,公司还在其单片电子皮肤上集成了多模态传感器阵列,标准产品每平方厘米可集成400个传感器,通过多模态传感器的拓扑结构设计,可实现压力、温度、切应力、表面纹理、接近觉等多维信息的感知。这套感知体系中具备了独特的“接近觉”能力,即传感器能在物体尚未接触时、最远20厘米处就感知到它的靠近。

这也意味着,搭载途见科技触觉方案的具身智能机器人不仅能判断“是否碰到”,还能提前预判“即将碰到”,并辨别材质、纹理、温度、滑动方向等丰富的触觉信息,这对人机交互安全和防碰撞等至关重要。比如在设备转身或有儿童靠近时,机器人可以提前刹车,提升安全性;其在智能汽车安全驾驶领域也有广泛应用。

搭载途见科技触觉方案的方向盘(图源/企业)

“人的皮肤本身就是可拉伸的、多模态的感知网络。”途见科技创始人兼CEO赖建诚告诉硬氪,“基于这一仿生起点,我们选择从材料端直接复现生物触觉的底层逻辑。这种‘材料+器件+系统’的一体化设计,使得传感器在轻薄、灵敏、多模态融合等方面具备天然优势,是传统方案靠堆叠很难跨越的竞争壁垒。”

基于这套技术底座,途见科技在具身智能领域形成了三大产品矩阵,分别是灵巧手全覆盖触觉、机器人全身触觉和触觉手套数据采集系统,从材料、器件到系统集成,构建完整的触觉感知生态。

这三类产品并非孤立布局,灵巧手和机器人全身触觉面向机器人端的实时感知与交互,触觉手套则面向人类操作端的数据采集与模型训练,共同形成“人类示教数据采集—机器人触觉执行反馈—模型持续优化”的闭环体系。

灵巧手方面,途见科技此前已同多家头部产业方达成合作,部分已进入批量交付阶段。全身触觉是更具想象力的方向。公司与头部具身企业及主机厂等合作,尝试薄膜式触觉感知层在具身机器人全身的覆盖,提升其感知运控能力。有了全身触觉,机器人能够感知身体各部位的受力情况,从而动态调整姿态,让操作更加稳健。

触觉手套数据采集系统是途见科技2026年核心的标准化产品。该系统集成触觉与视觉,佩戴者可进入真实工作和生活场景采集操作数据,用以训练机器人模型。相比固定式数采工厂,手套可低成本、大规模部署,目前公司已经拿下多家厂商合作意向订单。

与传统动作捕捉、力反馈手套或外置式数采方案不同,途见科技的触觉手套强调对真实接触过程的高密度、低干扰采集,其手部触觉单元覆盖数量更多,可在指尖、指腹、手掌等关键区域形成多点触觉感知网络,捕捉抓取、捏合、按压、滑动、摩擦等精细操作中的触觉变化。同时,该系统能够有效区分“真实接触”与“空抓动作”,避免将无效动作误标记为有效操作数据,这一特性对于提升触觉数据质量尤为关键。

数据层面,触觉手套不仅采集人的手部运动轨迹,更重要的是采集“动作—接触—受力—滑移—物体反馈”之间的连续关系。视觉可以解析物体位置、形状和场景语义,但难以直接判定接触是否稳定、抓取是否打滑、受力是否过大以及操作是否完成。触觉数据补足了机器人与物理世界交互中的关键闭环信息,使机器人能够学习人在真实操作中如何根据接触状态调整力度、姿态和动作节奏。

基于触觉手套采集的大规模真实交互数据,途见科技希望进一步构建面向机器人操作任务的触觉—视觉融合数据集和工具链,为具身智能模型训练提供底层数据基础。

途见科技触觉手套数据采集系统(图源/企业)

赖建诚告诉硬氪,途见科技后续将聚焦多场景适配能力的升级,同步加快产品量产出货,建立规模化供应链体系。在场景端,公司将继续以具身智能、消费机器人、智慧医疗及汽车电子为核心赛道,持续拓展国内外市场,推动柔性电子皮肤技术在更多终端场景落地。

当前,通用机器人本体的竞争已进入白热化,而触觉感知作为“最后一毫米”的核心壁垒,正在加速成型。对于具身智能而言,真正稀缺的不只是传感器硬件本身,更是高质量、可规模化、与真实物理交互强相关的触觉数据。

从数据采集到实时交互,从灵巧手到本体整机,从垂直行业到泛服务场景,柔性触觉传感器逐渐由可选方案演进为新的技术原点。途见科技试图通过机器人端��子皮肤与人类端触觉手套的双向布局,构建从感知硬件、数据采集到模型训练的完整链路,使触觉成为机器人理解和操作物理世界的底层操作系统。

A股三大指数集体高开,太辰光涨超7%

2026年5月7日 09:26
36氪获悉,A股三大指数集体高开,沪指高开0.2%,深成指高开0.53%,创业板指高开0.79%;贵金属、通信设备、电脑硬件板块领涨,太辰光涨超7%,中国长城涨超6%,华锡有色涨超4%;教育、能源、水务板块跌幅居前,*ST国中跌超5%,中国海油跌超2%,行动教育跌超1%。

恒指开盘涨1.21%,恒生科技指数涨2.41%

2026年5月7日 09:22
36氪获悉,恒指开盘涨1.21%,恒生科技指数涨2.41%;传媒、软件服务、汽车板块领涨,快手涨超5%,百度集团、腾讯音乐涨超4%,小鹏集团涨超2%;能源、化工板块跌幅居前,中国石油股份跌超3%,中伟新材跌超1%。

Upstage与Kakao达成协议,收购韩国第二大搜索引擎Daum

2026年5月7日 09:18
韩国人工智能初创公司Upstage周四宣布,已与互联网巨头Kakao正式签署协议,收购韩国第二大搜索引擎Daum。根据协议,Upstage将收购Kakao持有的Daum运营商AXZ Corp.的全部股份,而Kakao将获得Upstage的新股份。双方均未披露协议的具体财务条款。(新浪财经)

10.响应式系统演进:通过位运算优化动态依赖收集(Vue3.2)

作者 Cobyte
2026年5月7日 09:17

前言

在 Vue3.2 的版本里面还通过位运算优化动态依赖收集的性能,那么具体是怎么做的呢?首先我们来看看原来为什么会存在性能问题,我们回顾一下第5篇文章讲解 Vue3 响应式原理的时候,在收集依赖的时候有以下一段代码。

image.png

首先是只要存在 activeEffect 变量,我们就会往 deps 中添加依赖,如果存在重复的依赖,会利用 Set 数据的特性来去重。目前这种依赖管理方式在高频更新或深层递归场景下存在性能瓶颈。具体表现为副作用函数(effect)的依赖可能随条件分支动态变化。例如:

const state = reactive({ a: '掘金签约作者', b: 'Cobyte', flag: true })

effect(() => {
  if (state.flag) {
    // 依赖 state.a
    console.log(state.a);
  } else {
    // 依赖 state.b
    console.log(state.b);
  }
});

state.flag = false
state.a = '小前端'

我们运行上述例子,结果如下:

掘金签约作者
Cobyte
Cobyte

从上述测试结果我们可以看到当设置 state.flag 为 true 时,打印了 Cobyte,这是正确的,但当改变state.a 值时,也打印了 Cobyte,其实当 state.flag 为 true 时,该副作用就跟 state.a 没有关系了,因为不管 state.a 的值怎么变,副作用的打印结果都是一样的,所以此时当 state.a 改变就触发副作用更新的行为就是浪费性能。

所以我们目前的实现存在以下问题,当 state.flag 变化时,依赖需从 state.a 切换到 state.b 时无法自动清理过期依赖,导致冗余触发而引发性能瓶颈。

对此 Vue3.2 创新性地引入 位运算(Bitwise Operations)优化依赖收集,解决了动态依赖切换导致的冗余依赖问题,从而大幅提升了响应式系统的性能。本文将从设计背景、实现原理、性能优势等方面展开分析,揭示位运算在这一场景下的核心价值。

此外对位运算还不熟的同学,可以先复习一下位运算相关知识

为什么要使用位运算来设计依赖优化?

我们在前言的例子中讲到当 state.flag 变化时,依赖需从 state.a 切换到 state.b,传统 Set 数据结构无法自动清理过期依赖,导致冗余依赖。那么怎么实现自动清理过期的依赖呢?

普通实现方案

原来的数据结构如下:

image.png

那么实现这个清除失效的依赖,按我们普通的实现方案可以这样设计,设计一个记录该依赖在 之前的层级 是否被追踪的变量 wasSet = new Set();再设计一个记录该依赖在 当前层级 是否被追踪的变量 newSet = new Set();这样我们在一轮循环中判断是否记录新的依赖的时候,先往变量 newSet 中添加该依赖,再从 wasSet 变量中判断是否已经存在该依赖,如果已经存在,那么就不再记录,如果不存在,那么就需要往原来记录依赖的变量 deps 中添加新的依赖。这样在一轮循环的最后,再去判断该依赖如果只存在 wasSet 变量中,而没有在 newSet 变量中时,则说明该依赖需要从 deps 变量中清除掉了,这样将来该依赖发生变化都不会响应式到渲染函数的重新执行。那么 wasSet 中的数据怎么来呢?可以在初始化的时候从 deps 中进行赋值。

我们上面通过文字描述大概讲了一遍普通方案的实现,那么我现在通过伪代码再还原展示一偏。

状态记录相关变量:

  • wasSet: Set<Dep> :记录上一轮执行中所有被追踪的依赖。
  • newSet: Set<Dep> :记录当前轮次执行中所有被追踪的依赖。
  • deps: Dep[] :实际存储依赖的集合。

初始化阶段:

wasSet = new Set(deps); // 初始化为上一轮的依赖  
newSet = new Set();  

依赖收集阶段:

if (!newSet.has(dep)) {  
  newSet.add(dep);  
  if (!wasSet.has(dep)) {  
    deps.push(dep); // 新增依赖  
  }  
}  

依赖清理阶段:

for (const dep of wasSet) {  
  if (!newSet.has(dep)) {  
    deps.splice(deps.indexOf(dep), 1); // 移除失效依赖  
  }  
}  
wasSet = newSet; // 更新历史状态

从上述伪代码可以清晰看出通过比对 wasSet 和 newSet 的差异,移除不再被使用的依赖,从而实现了条件分支的支持。

但这种普通方案存在以下性能瓶颈:

  1. 内存开销

    • 需维护多个 Set 实例(wasSetnewSet),存储大量依赖时内存占用高。
    • 每次递归层级变化需复制依赖集合(如 wasSet = new Set(deps))。
  2. 操作效率

    • 集合操作hasadddelete 的时间复杂度为 O(1),但哈希表操作仍存在性能损耗(如哈希碰撞)。
    • 清理阶段:遍历 wasSet 并检查 newSet 的时间复杂度为 O(n²)。
  3. 递归层级管理

    • 深层递归时需为每层维护独立的 Set,内存和计算开销指数级增长。

所以 Vue3 并没有采用这种实现方式,那么接下来让我们继续探讨 Vue3 的实现方案吧。

位运算优化方案(Vue3 实现)

在 Vue3 中则巧妙地创建一个兼具 依赖存储 和 追踪状态标记 的复合数据结构的变量。设计如下:

image.png

通过扩展 Set 而非创建全新数据结构,复用 Set 的高效存储,仅添加 wasTrackednewTracked 两个整数字段,就创建一个兼具 依赖存储 和 追踪状态标记 的复合数据结构了。具体 wasTrackednewTracked 两个字段的作用是:

  • wasTracked:记录该依赖在 之前的层级 是否被追踪。
  • newTracked:记录该依赖在 当前层级 是否被追踪。

wasTrackednewTracked 的值都是一个二进制数字,例如:若某依赖在之前的层级(如父组件渲染)中被访问过,wasTracked 对应的位会被标记;newTracked 则是在当前渲染中如果被访问了,对应的位也会被标记。

那么为什么要使用位运算来设计呢?我们从传统的权限管理的痛点说起,因为上述的依赖优化管理机制与权限系统的位掩码设计异曲同工。

假设需要为一个用户管理系统设计权限控制,包含以下权限:

  • 读(R)0b001(二进制) → 1(十进制)
  • 写(W)0b010 → 2
  • 执行(X)0b100 → 4

传统实现方式:

const userPermissions = {
  read: true,
  write: false,
  execute: true
};

// 检查是否有读权限
if (userPermissions.read) { /* ... */ }

这种方案存在以下问题:

  • 存储冗余:每个权限需独立布尔字段,内存占用高。
  • 组合权限复杂:判断用户是否同时有读和执行权限需多次检查。
  • 扩展性差:新增权限(如 admin)需修改数据结构。

使用位运算设计权限管理系统:

通过 位掩码(Bitmask)  将权限编码为单个整数:

// 权限定义
const PERMISSIONS = {
  READ: 0b001,   // 1
  WRITE: 0b010,  // 2
  EXECUTE: 0b100 // 4
};

用户初始权限:

// 用户权限(初始为 0)
let userPermissions = 0;

添加读和执行权限:

// 添加读和执行权限
userPermissions |= PERMISSIONS.READ;    // 0b001 → 1
userPermissions |= PERMISSIONS.EXECUTE; // 0b101 → 5

检查是否有写权限:

const hasWrite = (userPermissions & PERMISSIONS.WRITE) > 0; // false

检查是否有读和执行权限:

const hasReadAndExecute = 
  (userPermissions & (PERMISSIONS.READ | PERMISSIONS.EXECUTE)) 
  === (PERMISSIONS.READ | PERMISSIONS.EXECUTE); // true

优势分析

(1) 内存高效

  • 传统方式:每个权限占用一个布尔值(通常 4 字节)。
  • 位运算:所有权限压缩为单个整数(4 字节),内存占用减少 75%

(2) 操作快速

  • 添加权限userPermissions |= PERMISSIONS.WRITE(O(1))。
  • 移除权限userPermissions &= ~PERMISSIONS.WRITE(O(1))。
  • 检查权限:按位与操作(O(1))。

(3) 组合权限灵活

// 检查是否同时有读和写权限
const required = PERMISSIONS.READ | PERMISSIONS.WRITE;
const hasAll = (userPermissions & required) === required;

那么根据上述权限系统的实现的启发,我们就可以设计如果当前依赖层级为 1,那么历史层级的追踪状态变量 wasTracked 就会被设置为 0b1,当前层级为 2 那么 wasTracked 就会被设置为 0b10,同样地 3,4 ... 层就会被设置为 0b1000b1000,如果一个变量在1、2、3、4层都被引用,那么 wasTracked 就会被设置为:0b1111。同样地当前层级的追踪状态 newTracked 也是如此设计。

同样地,层级变量也可以使用二进制表示,比如,1层为:0b1;2层为:0b10;3层为:0b100。这样标记和判断等相关操作都可以通过位运算进行。比如当前层级为2,那么 层级变量 = 0b10,那么标记添加则是 wasTracked = wasTracked | 0b10;而判断当前历史层级是否已被标记则是 has = wasTracked & 0b10

位运算的原子性操作(如 |=&)速度远超传统 Set 的操作(如遍历、过滤),且位运算具有极致的性能优势,这就是为什么使用为什么要使用位运算来设计依赖优化。

组件嵌套的 effect 实现原理

我们前面讲到多层嵌套的 effect,会存在内存占用高操作缓慢的缺点。而我们前面实现的 Vue3 响应式源码是还没实现嵌套 effect 的,所以我们先要实现嵌套 effect。例如下面的例子:

window.state = reactive({ parent: 'parent', child: 'child' })
effect(() => {
    effect(() => {
        console.log(`我是子组件:${state.child}`)
    })
  console.log(`我是父组件:${state.parent}`)
})

执行结果如下:

image.png

我们给 state.child 重新赋值:

image.png

这时子组件的 effect 执行了,这是正常的。

接著我们给 state.parent 重新赋值:

image.png

这时我们发现父组件的 effect 不执行了。这是为什么呢?我们来观察一下我们之前实现的 ReactiveEffect 类:

class ReactiveEffect {
    deps = []
    constructor(fn) {
        this._fn = fn
    }
    run () {
        activeEffect = this
        this._fn()
        activeEffect = null
    }
    stop () {
      this.deps.forEach(dep => dep.delete(this))
    }
} 

我们知道 activeEffect 变量是唯一的,当嵌套之后,子组件执行完之后,activeEffect 将被设置了 null,这时父组件如果还有响应式数据需要收集的时候,由于 activeEffect 为 null 而会导致父组件的响应式数据的依赖收集不到。

为了解决这个问题,Vue3 底层设置了一个副作用函数栈变量 effectStack,我们要确保 activeEffect 始终指向当前正在运行的响应式副作用 effect。实现代码如下:

// 用于管理嵌套 effect 的调用栈
const effectStack = []
class ReactiveEffect {
    // 存储所有包含本 effect 的依赖集合(Set)
    // 用于实现 stop 功能时快速清理依赖
    deps = []
    constructor(fn) {
        // 包装的副作用函数(开发者传入的原始函数)
        this._fn = fn
    }
    // 执行副作用函数,并触发依赖收集
    run () {
        // 这里为什么要用try...finally呢?比如如果_fn中有错误,finally块仍然会执行,保证栈的平衡。
        try {
            // 1. 设置当前激活的 effect 为自身
            activeEffect = this;
            // 2. 压入 effect 调用栈(处理嵌套 effect 的关键)
            effectStack.push(this);
            // 3. 执行原始函数,触发响应式属性的 getter,进行依赖收集
            return this._fn(); // 返回函数执行结果(支持 computed 等场景)
        } finally {
            // 4. 无论执行是否抛出异常,确保以下清理逻辑一定执行
            effectStack.pop(); // 当前 effect 出栈
            // 5. 恢复 activeEffect 为上一个 effect(栈顶元素)或 undefined
            activeEffect = effectStack.length > 0 ? effectStack[effectStack.length - 1] : undefined;
        }
    }
    // 停止当前 effect 的响应式追踪
    stop () {
      // 遍历所有关联的依赖集合,从中删除本 effect
      this.deps.forEach(dep => dep.delete(this))
    }
} 

主要的实现思路也很简单,就是在执行原始函数之前,先把当前的响应式副作用压入 effectStack 调用栈,通过使用 try...finally 确保无论 this._fn() 是否抛出异常,effectStack 都会被正确弹出,activeEffect 会被恢复为上一个响应式副作用 effect 或 undefined。这样通过维护 effectStack,确保嵌套的响应式副作用 effect 的执行顺序正确,activeEffect 变量始终指向当前正在运行的响应式副作用 effect。

我们再来看看迭代后的执行结果:

image.png

我们可以看到当父组件的响应式变量 parent 被改变后,相关的嵌套代码都被执行了。

到此,我们就实现了嵌套 effect

依赖标记流程

初始化依赖的追踪状态标记

初始化依赖的追踪状态标记的核心逻辑就是在副作用函数执行前,记录所有 已有依赖 的追踪状态,即某个依赖在 上一轮执行 中被追踪过,其对应的位会被标记到 wasTracked 中。具体就是将每个依赖的 wasTracked 字段的 当前层级对应位 设为 1。我们可以设置一个全局变量 effectTrackDepth 来表示当前副作用执行的 递归深度,也就是所谓层级,初始为 0,每递归一次就增加 1。在每一轮的副作用函数执行前,将全局递归深度加 1,表示进入新一层级,执行完副作用函数后,将全局递归深度减 1,表示返回到上一层级的执行环境。

然后通过位运算 1 << effectTrackDepth 生成一个二进制掩码,也就是 第 effectTrackDepth 位为 1,其余位为 0。例如,若 effectTrackDepth = 2,则掩码为 0b100(十进制 2)。这样每个递归层级 effectTrackDepth 对应独立的二进制位,避免嵌套 effect 的依赖状态相互干扰。最后通过按位或操作(|),将 wasTracked 的对应二进制位设为 1,其他位保持不变。

具体代码实现如下:

// 用于管理嵌套 effect 的调用栈
const effectStack = []
+ // 当前副作用执行的递归深度
+ let effectTrackDepth = 0
class ReactiveEffect {
    // 存储所有包含本 effect 的依赖集合(Set)
    // 用于实现 stop 功能时快速清理依赖
    deps = []
    constructor(fn) {
        // 包装的副作用函数(开发者传入的原始函数)
        this._fn = fn
    }
    // 执行副作用函数,并触发依赖收集
    run () {
        // 这里为什么要用try...finally呢?比如如果_fn中有错误,finally块仍然会执行,保证栈的平衡。
        try {
            // 1. 设置当前激活的 effect 为自身
            activeEffect = this;
            // 2. 压入 effect 调用栈(处理嵌套 effect 的关键)
            effectStack.push(this);
+            // 将全局递归深度加 1,表示进入新一层级
+            effectTrackDepth++;
+            // 初始化标记
+            this.initDepMarkers();
            // 3. 执行原始函数,触发响应式属性的 getter,进行依赖收集
            return this._fn(); // 返回函数执行结果(支持 computed 等场景)
        } finally {
+            // 将全局递归深度减 1,表示返回到上一层级的执行环境 
+            effectTrackDepth--;
            // 4. 无论执行是否抛出异常,确保以下清理逻辑一定执行
            effectStack.pop(); // 当前 effect 出栈
            // 5. 恢复 activeEffect 为上一个 effect(栈顶元素)或 undefined
            activeEffect = effectStack.length > 0 ? effectStack[effectStack.length - 1] : undefined;
        }
    }
    // 停止当前 effect 的响应式追踪
    stop () {
      // 遍历所有关联的依赖集合,从中删除本 effect
      this.deps.forEach(dep => dep.delete(this))
    }
+    // 初始化依赖的追踪状态标记
+    initDepMarkers() {
+        const { deps } = this
+        if (deps.length) {
+            for (let i = 0; i < deps.length; i++) {
+                // 若某个依赖在 上一轮执行 中被追踪过,其对应的位会被标记到 wasTracked 中
+                deps[i].wasTracked = deps[i].wasTracked | 1 << effectTrackDepth
+            }
+        }
+    }
} 

小结一下:当副作用函数 effect 执行时,会进入不同的递归层级,每个层级对应一个位。在初始化时,会通过 initDepMarkers 方法设置对应依赖的 wasTracked 属性的位,表示上一轮这个依赖是否被跟踪。

通过位运算判断是否收集依赖

我们在之前的依赖收集的判断逻辑是这样的,判断全局变量 activeEffect 是否存在,存在就进行收集, 那么现在我们要判断当前依赖的当前层级是否标记该依赖为已追踪,也就是 deps.newTracked 的对应层级 (1 << effectTrackDepth) 是否为 1。这就要通过与运算(&)来判断。我们通过封装一个函数来实现这个功能,代码如下:

function newTracked(dep) {
  return (dep.newTracked & (1 << effectTrackDepth)) !== 0;
}

若当前层级未标记该依赖为已追踪(!newTracked(dep)),则需要将当前依赖 newTracked 设置为当前层级 (1 << effectTrackDepth) ,也就是标记为 1。我们通过封装一个函数来实现这个功能,代码如下:

function setNewTracked(dep) {
  dep.newTracked |= (1 << effectTrackDepth); // 按位或操作
}

最后我们还要检查依赖的 wasTracked 字段的当前层级(1 << effectTrackDepth) 对应 是否为 1(即是否在上一轮执行中被追踪过)。我们通过封装一个函数来实现这个功能,代码如下:

function wasTracked(dep) {
  return (dep.wasTracked & (1 << effectTrackDepth)) !== 0;
}

整体代码迭代如下:

function track(target, key) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      // 标记依赖在 上一轮执行周期 中是否被追踪
      deps.wasTracked = 0
      // 标记依赖在 当前执行周期 中是否被追踪
      deps.newTracked = 0
      depsMap.set(key, deps)
    }
-    if (activeEffect) {
-        deps.add(activeEffect)
-        activeEffect.deps.push(deps)
-    }
+    trackEffects(deps)
}

+ function trackEffects(dep) {
+     let shouldTrack = false
+     if (!newTracked(dep)) {
+      setNewTracked(dep)
+      shouldTrack = !wasTracked(dep)
+    }

+    if (shouldTrack) {
+        dep.add(activeEffect)
+        activeEffect.deps.push(dep)
+    }
+ }

+ function newTracked(dep) {
+   return (dep.newTracked & (1 << effectTrackDepth)) !== 0;
+ }

+ function setNewTracked(dep) {
+   dep.newTracked |= (1 << effectTrackDepth); // 按位或操作
+ }

+ function wasTracked(dep) {
+   return (dep.wasTracked & (1 << effectTrackDepth)) !== 0;
+ }

在执行 effect 函数的过程中,当访问响应式属性时,会调用 track 函数,进而调用 trackEffects,设置 newTracked 的位,表示当前层级这个 dep 被跟踪了。

接着我们测试一下我们写的代码,测试代码如下:

window.state = reactive({ flag: false,  a: 'parent', b: 'child' })
effect(() => {
  if (state.flag) {
    console.log(`条件一:${state.a}`);
  } else {
    console.log(`条件二:${state.b}`);
  }
});

我们运行上面的测试代码,结果输出:条件二:child。这是正确的输出结果。说明我们上述的迭代代码是正确的。

我们现在改变 flag 的值, state.flag = true,结果输出:条件一:parent。这也是正确的输出结果。这时我们再改变 b 的值, state.b = '掘金签约作者',结果输出:条件一:parent,这个结果不是我们期待的,因为 b 属性我们已经不再使用了,b 属性值的改变不应该再触发更新才对。所以我们还要实现最后一个功能,通过位运算实现动态依赖的精准管理。

实现动态依赖精准管理

我们通过上文知道当effect执行时,会进入不同的递归层级,每个层级对应一个位。在初始化时,会通过initDepMarkers方法设置wasTracked的位,表示上一轮这个dep是否被跟踪。然后在执行effect函数的过程中,当访问响应式属性时,会调用track函数,进而调用trackEffects,设置newTracked的位,表示当前层级这个dep被跟踪了。

我们现在需要做的就是比较这两个标记,如果一个dep在之前被跟踪(wasTracked为真),但在当前没有被跟踪(newTracked为假),说明这个dep在当前层级不再被需要,因此需要从dep的集合中移除这个effect。这样我们就可以实现清理那些不再被依赖的effect,防止内存泄漏和无效的触发。

代码迭代如下:

// 用于管理嵌套 effect 的调用栈
const effectStack = []
let effectTrackDepth = 0
class ReactiveEffect {
    // 存储所有包含本 effect 的依赖集合(Set)
    // 用于实现 stop 功能时快速清理依赖
    deps = []
    constructor(fn) {
        // 包装的副作用函数(开发者传入的原始函数)
        this._fn = fn
    }
    // 执行副作用函数,并触发依赖收集
    run () {
        // 这里为什么要用try...finally呢?比如如果_fn中有错误,finally块仍然会执行,保证栈的平衡。
        try {
            // 1. 设置当前激活的 effect 为自身
            activeEffect = this;
            // 2. 压入 effect 调用栈(处理嵌套 effect 的关键)
            effectStack.push(this);
            // 
            effectTrackDepth++;
            // 初始化标记
            this.initDepMarkers();
            // 3. 执行原始函数,触发响应式属性的 getter,进行依赖收集
            return this._fn(); // 返回函数执行结果(支持 computed 等场景)
        } finally {
+            this.finalizeDepMarkers();
            effectTrackDepth--;
            // 4. 无论执行是否抛出异常,确保以下清理逻辑一定执行
            effectStack.pop(); // 当前 effect 出栈
            // 5. 恢复 activeEffect 为上一个 effect(栈顶元素)或 undefined
            activeEffect = effectStack.length > 0 ? effectStack[effectStack.length - 1] : undefined;
        }
    }
    // 停止当前 effect 的响应式追踪
    stop () {
      // 遍历所有关联的依赖集合,从中删除本 effect
      this.deps.forEach(dep => dep.delete(this))
    }
    // 初始化依赖的追踪状态标记
    initDepMarkers() {
        const { deps } = this
        if (deps.length) {
            for (let i = 0; i < deps.length; i++) {
                // 若某个依赖在 上一轮执行 中被追踪过,其对应的位会被标记到 wasTracked 中
                deps[i].wasTracked = deps[i].wasTracked | 1 << effectTrackDepth
            }
        }
    }
+    // 清理无效依赖 并 优化依赖集合
+    finalizeDepMarkers() {
+        const { deps } = this
+        if (deps.length) {
+            for (let i = 0; i < deps.length; i++) {
+                const dep = deps[i]
+                // 根据依赖的跟踪状态,清理不再需要的依赖
+                if (wasTracked(dep) && !newTracked(dep)) {
+                    // 移除当前 effect 对该 dep 的依赖
+                    dep.delete(this)
+                }
+            }
+        }
+    }
} 

我们再运行上面的测试代码,结果输出:条件二:child。我们接着改变 flag 的值, state.flag = true,结果输出:条件一:parent。这也是正确的输出结果。这时我们再改变 b 的值, state.b = '掘金签约作者',结果输出:条件一:parent,这个结果还是不是我们期待的,为什么呢?

主要是因为现在只要我们的依赖的层级只要被标记上了,就一直是这个状态了。假设当前层级为 2,上述测试代码中需要删除的 b 属性依赖的层级初始标记状态为:wasTracked = 0b100, newTracked = 0b100,那么后续 b 属性的层级状态就一直是这个状态了,当判断是否需要删除的时候,我们需要判断 wasTracked 是否为 true,因为已经被标记过了,所以为 true,同样判断 newTracked 是否为 false 时,因为已经被标记过了,所以为 true

所以在退出当前层级前,清除该层级对应的位掩码,确保下一层级的标记从干净状态开始。具体代码实现如下:

class ReactiveEffect {
    // ...
    // 清理无效依赖 并 优化依赖集合
     finalizeDepMarkers() {
        const { deps } = this
        if (deps.length) {
            for (let i = 0; i < deps.length; i++) {
                const dep = deps[i]
                // 根据依赖的跟踪状态,清理不再需要的依赖
                if (wasTracked(dep) && !newTracked(dep)) {
                    // 移除当前 effect 对该 dep 的依赖
                    dep.delete(this)
                }
+                // 清除该层级对应的位掩码
+                const trackOpBit = 1 << effectTrackDepth
+                dep.wasTracked = dep.wasTracked & ~trackOpBit
+                dep.newTracked = dep.newTracked & ~trackOpBit
            }
        }
    }
}

总的来说就是当 effect 执行完成后,通过比较 wasTrackednewTracked 的位掩码,可以快速确定哪些依赖在本次执行中没有被访问,从而进行清理。同时退出当前层级前,清除该层级对应的位掩码,确保下一层级的标记从干净状态开始。

递归层级限制30层的设计原因

Vue3 底层选择 30 层作为最大递归层级,因为 V8 引擎对 31/32 位整数直接存储于指针,无需堆分配,读写速度提升 10 倍,30 层限制确保位运算结果始终为 SMI,避免退化为堆内存对象导致性能退化,所以选择 30 层是为了确保现代JS引擎在所有平台上都能使用 SMI(小整数)优化。当超出 30 层时,回退到全量清理,保障极端场景稳定性。

代码优化迭代如下:

+ const maxMarkerBits = 30
class ReactiveEffect {
    // ...
    run () {
        try {
            // 1. 设置当前激活的 effect 为自身
            activeEffect = this;
            // 2. 压入 effect 调用栈(处理嵌套 effect 的关键)
            effectStack.push(this);
            effectTrackDepth++;
-            this.initDepMarkers()
+            if (effectTrackDepth <= maxMarkerBits) {
+                this.initDepMarkers()
+            } else {
+                // 当递归深度超过30层时,回退到完全清理模式
+                this.cleanup()
+            }
            // 初始化标记
            this.initDepMarkers();
            // 3. 执行原始函数,触发响应式属性的 getter,进行依赖收集
            return this._fn(); // 返回函数执行结果(支持 computed 等场景)
        } finally {
-            this.finalizeDepMarkers()
+            if (effectTrackDepth <= maxMarkerBits) {
+                this.finalizeDepMarkers()
+            }
            effectTrackDepth--;
            // 4. 无论执行是否抛出异常,确保以下清理逻辑一定执行
            effectStack.pop(); // 当前 effect 出栈
            // 5. 恢复 activeEffect 为上一个 effect(栈顶元素)或 undefined
            activeEffect = effectStack.length > 0 ? effectStack[effectStack.length - 1] : undefined;
        }
    }
    // ...
+    // 完全清理模式
+    cleanup() {
+        const { deps } = this
+        if (deps.length) {
+            for (let i = 0; i < deps.length; i++) {
+                deps[i].delete(this)
+            }
+            deps.length = 0
+        }
+    }
} 

SMI(Small Integer)优化的核心原理

我们上面提到 Vue3 底层选择 30 层作为最大递归层级,是为了确保现代JS引擎在所有平台上都能使用 SMI(小整数)优化。

首先,我们得看一下 SMI 的概念。SMI 代表 Small Integer,是 V8 引擎对特定范围内整数的优化存储方式。在 V8 引擎中,SMI(Small Integer)优化 的核心原理是通过 指针标签(Pointer Tagging)  技术,将小整数直接嵌入指针值中,而非存储在堆内存中。以下是其性能优势的详细解析:

指针的结构

  • 指针的本质
    指针是一个内存地址,通常用 32 位(32 位系统)或 64 位(64 位系统)表示。

  • 标签位(Tagging Bits)
    V8 利用指针的低位(如最低 1~2 位)作为 类型标记,例如:

    • 表示该指针是一个 SMI(直接存储整数值)。
    • 表示该指针是一个 堆对象地址(需要解引用获取实际值)。

SMI 的存储方式

直接嵌入指针
V8 将小整数的二进制值 左移 1 位(腾出最低位作为标签),然后存入指针。

堆分配的数字
若数字超出 SMI 范围(如大整数、浮点数),V8 会在堆内存中分配一个 Number 对象,并将指针指向该对象。

内存访问开销

  • SMI(指针存储)
    值直接存储在指针中,读取时 无需访问堆内存,直接解析指针值即可。

  • 堆分配的数字
    需要 两次内存访问

    1. 读取指针地址。
    2. 根据指针地址访问堆内存中的 Number 对象。

内存分配开销

  • SMI
    无堆内存分配和释放操作,避免 内存管理开销(如垃圾回收)。
  • 堆分配的数字
    需调用内存分配器,可能触发 垃圾回收(GC) ,增加延迟。

CPU 缓存友好性

  • SMI
    数值直接存储在指针中,与其他指针数据一起被 CPU 缓存,缓存命中率高
  • 堆分配的数字
    Number 对象分散在堆内存中,缓存局部性差,缓存未命中率高

指令优化

SMI 操作
通过简单的位运算(如移位、掩码)即可完成数值解析,CPU 指令周期短

堆分配数字操作
需要额外的解引用指令和类型检查,指令周期长

设计哲学

空间换时间

  • SMI:牺牲 1 位指针空间(用于标签),换取极致性能。
  • 堆分配:以内存和速度为代价,支持更大数值范围。

高频场景优化

  • 现实场景
    大多数 JavaScript 程序中的整数是小范围的(如循环计数器、数组索引),SMI 覆盖了 99% 的用例。
  • 收益最大化
    对高频操作(如依赖收集、循环计数)进行极致优化,显著提升整体性能。

综上所述,V8 通过 指针标签技术 将小整数(SMI)直接存储在指针中,实现了以下优势:

  1. 零内存分配:避免堆操作和垃圾回收开销。
  2. 直接访问:无需解引用,减少内存访问次数。
  3. CPU 友好:位运算指令快,缓存命中率高。

这些优化使得 SMI 的读写速度比堆分配的数字快 10 倍以上,成为 JavaScript 高性能引擎的核心技术之一。Vue3 的依赖收集系统正是基于此特性,通过位运算和层级限制,实现了高效的响应式更新。

总结

最后我们来总结一下,Vue3 通过位运算设计实现以下响应式系统的优化:

  • 层级化状态标记:通过位掩码精准管理递归层级依赖。
  • 动态清理机制:按位比对移除失效依赖,避免冗余触发。
  • 性能与内存平衡:SMI 优化保障操作速度,30 层限制避免边界问题。

这一机制在复杂组件、高频更新及深层嵌套场景下表现卓越,是 Vue3 响应式系统的核心创新之一。

我是程序员Cobyte,现在已转向研究 AI Agent,欢迎添加 v: icobyte,学习交流 AI Agent 应用开发。

再见吧CocoaPods,Swift Package Manager(SPM)即将在Flutter 3.44中成为默认依赖管理器

作者 JarvanMo
2026年5月7日 09:07

长久以来Futter一直以CocoaPods为作为默认的依赖管理器,而最近就在临近Google I/O了,Flutter官方却突然宣布,从下一个Flutter稳定版3.44开始,Swift Package Manager(SPM)取代CocoaPods,成为iOS和macOS应用的默认依赖管理器。

这意味你不必再为了跑通一个App而去一顿瞎折腾Ruby环境或者安装那个令人抓狂的CocoaPods了。

Flutter这么做一方面是因为SPM是Apple官方的依赖管理器,另一方面CocoaPods实际上早进入了**维护模式,并且CocoaPods trunk 明确会在 2026-12-02 进入只读**,所以今年年底 SwiftPM 就势必会完全取代 CocoaPods 这个老古董。

终于不用再忍受那蜗牛般的pod install了……

当然这个并不突然,毕竟Flutter 支持SPM这个事早就开始实施了,我自己的项目也早早已开启了SPM模式。前段时间,我自己的开源项目还收到了支持SPM的催更请求

Screenshot 2026-05-06 at 11.37.11.png

现在 Flutter 也开始正式提醒开发者,虽然现有版本还可以继续使用 CocoaPods,但是 2026-12-02 之后将不会在对 Pod 进行适配和支持 pod

那这对我们会有什么影响吗?

App 开发者

实际上,对于App开发者来说,Flutter的CLI会帮助你自动完成迁移处理。当你开始运行或构建 iOS 或 macOS 应用时,CLI 会自动更新 Xcode 项目使用全新的 Swift 包管理器。

如果 App 依赖的插件还没适配 SwiftPM,Flutter 会打印一条警告信息,列出所有不受支持的依赖项,同时暂时回退到 CocoaPods

当然这种回退并不会长久,毕竟Cocoapods都即将寿终正寝了,SPM又是Apple的太子,对Cocoapods的支持完全移除也在情理之中了。

当然,如果你真的遇到了SPM导致的构建问题,官方也提供了临时解决方案:

flutter:  
 config:  
   enable-swift-package-manager: false

pubspec.yaml中把SPM关掉就好了。

插件开发者

对于插件开发者,如果你的插件支持iOS/macOS,那你是逃不开了(我不会告诉你fluwx/tobias spm适配进展缓慢)。Flutter社区其实一直在推进插件对SPM的适配(比如我就收到了适配请求),目前排名前 100 的 iOS 插件里已经有 61% 完成了迁移,后续如果没添加 Swift Package Manager 支持的 Package ,在 pub.dev 评分中得分也会被拉低。

其实是SPM与CocoaPods是非常非常不同的,这也是导致Fluwx/Tobias等项目的SPM支持进展缓慢的原因,比如说在使用CocoaPods时,podspec可以在pod install的时候作为Ruby脚本执行,即可以入侵你的Flutter项目构建过程,fluwx中的no_pay就是基于该原理实现的。而SPM虽然也是可执行的 Swift 脚本,但 SPM 在 swift package resolve 阶段就锁定了依赖图,这个阶段与 Flutter 构建流程完全解耦,无法可靠地从外部注入状态,这就导致我们无法像cocoapods那样通过在pubspec.yaml切换到no_pay状态,这意味我们又要把fluwx拆分成双包即fluwxfluwx_no_pay

且不提我的项目问题,说回SPM和CocoaPods的迁移问题。

具体怎么迁移,大家可以参考Flutter migration docs for plugin authors对插件进行迁移与适配。

过往的Cocoapods就像我上面说的一样,Cocoapods已经为我们做很多了隐式的骚操作,比如:

  • 自动把 Flutter.framework 加进来
  • 自动处理 header / linker / search path
  • 不用关心 Flutter 引擎怎么进来的

但换成SwiftPM后就不一样了:

  • SwiftPM 是完全声明式依赖系统
  • 不会有 CocoaPods 那种“魔法注入”
  • 所有依赖都必须在 Package.swift 明确写出来

要知道,我们的插件代码其实是运行在 Flutter 引擎之上的,所以你需要在 Package.swift 里加这个依赖,以我正在迁移的Fluwx的Package.swift为例:

// swift-tools-version: 5.9
import PackageDescription


let package = Package(
    name: "fluwx",
    platforms: [.iOS("12.0")],
    products: [
        .library(name: "fluwx", targets: ["fluwx"])
    ],
    dependencies: [
         // 这是关键
        .package(name: "FlutterFramework", path: "../FlutterFramework"),
        .package(
            url: "https://github.com/JarvanMo/WechatOpenSDK-SPM", //
            from: "2.0.5"
        )
    ],
    targets: [
        .target(
            name: "fluwx",
            dependencies: [
                 // 这是关键
                .product(name: "FlutterFramework", package: "FlutterFramework"),
                .product(name: "WechatOpenSDK", package: "WechatOpenSDK-SPM")
            ],
            resources: [
                .process("Resources/PrivacyInfo.xcprivacy")
            ],
            cSettings: [
                .define("FLUWX_WITH_PAY"),
                .headerSearchPath("include")
            ],
            swiftSettings: [
                .define("FLUWX_WITH_PAY")
            ],
            linkerSettings: [
                .linkedFramework("CoreGraphics"),
                .linkedFramework("Security"),
                .linkedFramework("WebKit"),
                .unsafeFlags(["-ObjC", "-all_load"])
            ]
        )
    ]
)

另外如果在 2025 年做过迁移了插件,现在还也需要补充一个操作:在 Package.swift 文件中添加 FlutterFramework 作为依赖项,因为现在必须显式声明依赖。

Flutter 3.41开始,要把FlutterFramework作为一个依赖: 更新一下Package.swift以引入FlutterFramework:

dependencies: [
    .package(name: "FlutterFramework", path: "../FlutterFramework")
],
targets: [
    .target(
        // TODO: Update your target name.
        name: "plugin_name",
        dependencies: [
            .product(name: "FlutterFramework", package: "FlutterFramework")
        ],

别忘记更新 Dart/Flutter 版本

environment:
  sdk: ^3.11.0
  flutter: ">=3.41.0"

以我的切身体验来说,对 Flutter 来说这次变化影响最大的是插件生态。以前Flutter插件都是.podspec + Podfile + pod install接入,而且有不少插件会使用到魔法注入,但迁移到SPM后,这种魔法不复存在,会使一些插件的迁移难度增加。

SPM也是完全支持Objective-C 的,只是一些支持的力度和方式不太一样,比如 SwiftPM 不允许同一个 target 里同时混放 Swift 和 C-family 源码,target 可以包含 Swift、Objective-C/C++ 或 C/C++,但单个 target 不能混合 Swift 和 C-family 语言。

旧的纯OC插件/库迁移到SPM是完全可行的,只要目录结构要符合SPM规则。

真正麻烦的是那些OC+Swift混编的工程,通常来说他们要拆成多个target……

题外话

fluwx/tobais迁移到SPM的工作还在进行中, 主要就是iOS的变动,现在没了动态化的支持,很多功能变得比较麻烦。

image.png

如果大家对插件中引用本地framework可以参考我之前的文章

那么,你准备好迎接Swift Package Manager了吗?

平头哥发布首款智能网卡「磐脉 920」,补齐 AI 算力最后一块短板

2026年5月7日 09:00
作者|苏子华
编辑|郑玄
 

最近两年,AI 行业最热的词一直绕不开「算力」。

从大模型训练,到今年 Agent 的火热,再到各家云厂商不断扩建智算中心,行业讨论最多的,几乎都是 GPU、芯片和算力规模。好像只要卡够多,AI 就能继续往前跑。

但现实并不是。

不少做大模型训练和推理的人,已经越来越明显地感受到另一层问题:机器越来越贵,GPU 越来越强,但模型训练和推理的效率,却没有同步提升。

问题不一定出在算力本身,而可能出在另一件长期被忽视的事情上——网力。

平头哥产品总监李旭慧打了个比方:「如果把算力比作 AI 时代的石油,网力就是输油管道。算力提供动力,网力保障效率。」

4 月 28 日,在数字中国建设峰会上,平头哥发布首款智能网卡磐脉 920。这是国内首个内置 PCIe Switch 的 400G 智能网卡,最大支持 400Gbps 吞吐带宽,可应用于万卡智算集群、通算集群和高性能存储等场景,目前已经量产,并将率先部署在阿里云数据中心。

 

磐脉 920 想要解决的就是「网力」问题。

今天的大模型训练,一个训练任务,往往需要几千甚至上万张 GPU 同时协作。单张 GPU 性能再强,也必须跟整个集群保持同步。

问题在于,只要其中一部分节点慢下来,其他节点就只能等待。他观察到,目前行业里很多万卡级智算集群,GPU 实际利用率较低,「能做到 60%,已经算行业顶尖水平。」

过去行业更容易关注「有多少卡」,但实际在 AI 训练场景中,系统运行效率并不是由最强的硬件决定,而是受限于集群里最慢的那个节点。「领先节点的算力会持续闲置等待,造成大规模算力浪费。」

磐脉 920 的发布实际上也指明了一个方向:当 GPU 已经足够强之后,下一步到底该补哪里。

 

 

一张网卡,盘活整个智算集群

 

当 AI 智能体开始进入真实业务场景,推理业务的占比越来越高。

「在 Agent 应用爆发的背景下,推理业务的增长速度显著快于训练。」李旭慧表示。

大模型训练强调强同步,而推理面对的是大量突发、小包、高频请求,对低时延和稳定性的要求更高,这也意味着对「网力」的要求更高。

而概括一下磐脉 920 在做的事,就是尽量减少整系统里的「堵」和「等」,通过网力的提升来释放 AI 算力。

其背后的实现原理,有三个关键。

首先是支持多路径 RDMA,打破单一路径的局限。

通俗理解,就是原本只能走一条高速,现在变成多条路同时分流。更关键的是,这些数据虽然分开走,但最终还能按顺序准确拼回来。

李旭慧解释,磐脉 920 通过支持逐包喷洒、乱序接收和选择性重传,实现 RDMA 多路径。

从结果来看,这套方案带来的改善比较直接。

按照官方实测,磐脉 920 支持单 QP 打满 400G 带宽,而同类主流产品带宽大约只有其一半。同时,多路径能力可以把交换机端口缓冲区水线降低 90%,减少丢包和重传。

第二个关键设计,是把「绕路」变成「直连」。

磐脉 920 最大的亮点之一,是内置 PCIe Switch。这也是国内首个做到这一点的 400G 智能网卡。

传统服务器架构里,PCIe Switch 通常部署在主板上,数据需要绕多个节点转发。结果就是,有的路径长,有的路径短,时延不一致。

对于需要高度同步的 AI 训练任务来说,这种「不整齐」会直接影响效率。

李旭慧打了个比方:传统架构里,经常会出现「四个下行通道挤一个上行通道」的情况。

这很像四条支路同时汇入一条主干道,堵塞几乎不可避免。

磐脉 920 把 PCIe Switch 直接集成进芯片内部,让网卡与 CPU、GPU 形成更直接的连接关系。

少绕路,意味着更低时延;路径更统一,则意味着更稳定的同步效率。

根据平头哥实测,在相同集群规模和任务条件下,部署磐脉 920 后,大模型训练和推理任务完成时间可缩短 14%。

第三个关键,是让网络具备自己判断的能力。

传统网卡更多像搬运工。收到数据,搬过去,仅此而已。

据了解,磐脉 920 加入了细粒度网络感知和可编程拥塞控制能力。直白讲,就是它能主动避堵。让网络开始从被动传输,变成主动调度。

对于越来越复杂的 AI 集群来说,这种能力的重要性会越来越明显。

从这些设计能看出来,磐脉 920 并不是在追求参数上的简单提升,它做的事情很务实,让已经很贵的算力,少浪费一点,从而激发最大的潜能。

 

为何是阿里在定义 AI 基础设施?

 

除了性能本身,磐脉 920 背后更值得关注的,是平头哥和阿里的整体布局。

过去几年,很多公司做芯片,往往集中在单一环节,比如 GPU、AI 加速卡或者 CPU。

但平头哥的思路是做全栈。

目前,平头哥已经形成四条产品线:真武系列 AI 芯片、倚天服务器 CPU、镇岳存储主控芯片,以及这次发布的磐脉系列智能网卡。

对应的,正好是数据中心里的几个关键环节:算力、存力和网力。

李旭慧在采访中表示:「单一芯片产品无法解决全链路问题,只有打通算力、存力、网力,才能最大化释放 AI 硬件性能。」

这也是平头哥和很多单点芯片公司的区别。它不是只做一块性能更强的芯片,而是试图从整个系统角度去看问题。

很多芯片公司做产品,先做出来,再去找客户和落地场景。

但平头哥不是。

李旭慧告诉极客公园,磐脉 920 立项之时 AI 智能体尚未成为行业焦点。但他们从阿里云自身业务中判断,未来 AI 一定会推动数据中心网络能力升级。

「一线业务场景的实际需求,是芯片技术迭代与产品优化的核心驱动力。」先有阿里云的大规模业务场景,再从实际场景需求里倒推产品定义。

这就是磐脉 920 的商业路径。据透露,和平头哥倚天、真武、镇岳系列芯片一样,它会先部署在阿里云数据中心。

从这个角度看,磐脉 920 的发布,本身就是阿里「通云哥」协同能力的一次体现。

通义负责模型,阿里云负责场景,平头哥负责底层硬件。模型需求推动云基础设施升级,云场景又反向推动芯片演进。

这种全栈自研的闭环,在国内科技公司里并不多见。「通云哥」的模式虽然前期投入大、周期长,但一旦走通,护城河也极深。

AI 竞争走到今天,比拼的已经不是单点能力,而是这一整套系统能否顺畅运转。

随着 AI 越来越多从训练走向推理,模型、云与芯片之间形成的持续反馈循环,整体优势的显现可能才刚刚开始。

像素绽放PixelBloom完成C轮融资,全面发力AI办公解决方案Agent:从“一分钟生成PPT”到“交付商用级结果”

2026年5月7日 09:00

作者|梁键强

△图片来源:像素绽放PixelBloom

AI办公正在进入下一个阶段。

随着AI进入办公场景,写文档、做设计、生成PPT等原本耗时耗力的工作,开始被AI快速接手。但到目前为止,大多数AI办公产品仍停留在“生成内容”和“提升效率”的阶段。相比一个只会辅助完成任务的工具,市场更需要的,是一个能够直接理解需求、输出完整方案、交付结果的AI助手。

近日,像素绽放PixelBloom(旗下核心产品 AiPPT.com,以下简称像素绽放)

宣布完成C轮融资,本轮融资由国科投资与商汤国香资本联合领投,基石创投、大米创投跟投。新一轮资金将主要投向AI办公解决方案Agent的研发迭代、商业化落地以及全球化人才招募。

△图片来源:像素绽放PixelBloom

相比过去外界熟悉的“一句话一分钟一键生成PPT”的AiPPT.com,像素绽放正在做的事情是——从做AI工具,走向打造能够直接交付方案的AI员工。 

过去两年,像素绽放从PPT这一高频办公场景切入,在全球范围内积累了超过3000万注册用户,完成了从产品验证到用户心智建立的第一阶段。

△图片来源:像素绽放PixelBloom

在像素绽放CEO赵充看来,这还只是开始。本轮融资背后的核心目标,是把AI从“生成工具”升级为“交付结果的Agent”。当用户提出一个模糊的业务需求时,它可以自主完成需求拆解、素材调用、多模态编排与结果输出,最终交付一套可以直接用于汇报、提案甚至执行落地的完整方案。

△图片来源:像素绽放PixelBloom

承载这一目标的核心产品,是像素绽放近期推出的“小方同学(Fangan.com)”。这款产品在内部被定义为全球首个营销方案Agent,并已于今年年初正式上线,进入规模化商用阶段。

“小方同学”更像一个由多个AI角色组成的虚拟策划团队。当用户提出营销诉求时,系统会自动调用多个模型,进行市场调研、竞对扫描、策略推演等动作,最终共同产出一套完整的营销执行方案。内容不仅包括推广策略,还涵盖完整演讲稿、推广文案以及可编辑PPT在内的执行包。

△图片来源:像素绽放PixelBloom

这也是它与过去AI工具的最大区别。过去的工具本质上是提升效率,而小方的同学走的是“替人逻辑”,目标是直接交付结果。赵充表示:“我们把‘小方同学’定位为团队的AI数字员工,而不是另一个SaaS功能。”

在赵充看来,“小方同学”能够理解项目背景、记住历史偏好,甚至在方案中主动提示潜在执行风险。这意味着企业客户可以直接用它替代部分策划经理岗位,人力成本优化的ROI清晰可见。

根据像素绽放的测算,如果“小方同学”能够替代一个年薪二三十万元岗位中的核心工作能力,它所创造的商业价值将是传统工具模式的数十倍,甚至将近百倍。

该产品主要服务三类用户,亟需降本增效的广告从业者、需要大批量产出内容的大型企业市场部,以及缺乏专业策划团队的中小企业。

除策划Agent外,像素绽放还规划了营销Agent、分析师Agent、培训Agent等多条产品线,并将在年内陆续推出,逐步形成覆盖办公全链路的AI数字员工矩阵。

从市场反馈来看,“小方同学”跑出了理想的开局。其上线首日的收入,是AiPPT.com上线首日的10倍。围绕“小方同学”这一新业务方向,公司目前已获得超1亿元融资支持。

目前,小方同学正在推进“一纵一横”的产品布局。纵向继续深耕营销场景,围绕抖音、小红书等渠道,以及美业、餐饮、医疗等行业做深知识库;横向则从营销延伸至培训、医疗、商业计划书等更多专业场景,依托现有3000万用户基础,逐步扩展为多场景Agent矩阵。

在竞争逐渐升温的AI Agent市场,小方同学的核心优势,不单是生成能力,还是同时具备内容生产能力、行业知识沉淀和版权安全能力三重壁垒。

一方面,“小方同学”延续了AiPPT.com在内容组织上的技术积累,另一方面叠加了先前广告公司十几年的营销经验。

与此同时,在企业客户高度关注的版权合规问题上,像素绽放接入了视觉中国等正版素材库,为生成内容提供商业版权保障。 

像素绽放也逐渐在企业和政务市场建立起自己的基本盘。公司已经服务北京、上海、杭州等多地政府客户,以及拥有中信证券、安利中国、农夫山泉等在内的众多行业标杆客户。

其核心优势在于支持全信创环境下的私有化部署,确保数据不出域,同时能够快速接入OA等内部系统,员工无需离开日常办公界面即可调用AI生成方案。当前这一领域竞争相对较少,还处于蓝海阶段。

△图片来源:像素绽放PixelBloom

更大的增量市场则在海外。北美是海外发展的核心市场。产品已经支持英语、法语、德语等20多种语言,能够支撑全球绝大多数市场的本地化内容生成需求。按照公司预期,预计今年海外收入接近国内,明年有望实现反超。

△图片来源:像素绽放PixelBloom

对于未来,赵充有着明确的判断:全球办公软件的演进不会局限在某一个区域市场。像素绽放用了两年时间在中国市场验证产品价值,下一步,则是用同样的方法走向全球。

在他看来,语言和文化的边界,正是AI最擅长跨越的地方。

而国科投资董事长孙华表示:作为老股东,国科投资本轮继续追加投资,源于我们对像素绽放PixelBloom团队战略迭代能力的持续认可。过去两年,公司不仅以AiPPT.com在C端验证了AI视觉表达的市场刚需,更在B端和政务市场展现了极强的渗透力——从中信证券到多个一线城市政府机关,其私有化部署与系统集成能力已构筑起坚实的竞争壁垒。而“小方同学”的推出,标志着公司从“工具”向“数字劳动力”的关键一跃。当十余年4A方法论被代码化封装为可复用的Agent,我们看到的不仅是ARPU的跃升空间,更是一个办公操作系统级平台的雏形。我们相信,像素绽放PixelBloom正站在AI办公赛道质变的临界点上。

商汤国香资本合伙人李扬则表示:商汤国香资本始终关注AI技术与垂直场景的深度融合。像素绽放PixelBloom的独特价值,在于它没有停留在模型能力的浅层调用,而是将业内资深策划总监的思考框架、分析模型与标准作业程序等行业Know-how真正融入产品底层架构。这种“经验代码化”的能力,在营销这一万亿级市场中极为稀缺。同时,公司全球化能力让我们印象深刻:20余种语言的深度本地化、国内验证过的增长模型,以及向北美高客单价市场进军的清晰路径,构成了我们对全球化收入成为第二增长曲线的信心。我们期待与像素绽放PixelBloom一起,推动AI从“生成内容”走向“交付结果”的范式革命。

基石创投管理合伙人秦少博:基石创投深耕硬科技早期投资十余年,始终坚持“技术为刃、产业为钢、场景为王”的投资理念。我们看重的是技术能否在足够大的场景中创造真实价值。像素绽放PixelBloom恰恰做到了这一点——它以PPT这个高频刚需场景为锚点,在C端积累了3000万用户的产品验证,又在中信证券等头部企业及一线城市政府机关实现了深度的B端和G端渗透。这种“C端验证心智、B端深度嵌合”的路径,与我们寻找的“厚实本土场景叠加技术优势”的标的完美契合。更重要的是,“小方同学”将十余年行业经验代码化的能力,证明了团队具备从工具到数字劳动力的进化基因。我们相信,像素绽放PixelBloom正站在AI办公从“效率提升”走向“价值创造”的质变节点上。

大米创投创始人、董事长艾民表示:大米创投自2014年底成立以来,始终聚焦智能制造、人工智能等硬科技领域的早期投资,践行“在自己熟悉的有生态圈资源、有潜力的硬科技赛道,寻找最优秀的创始人”的投资策略。我们看好AI应用赛道未来巨大的潜力,看好赵充带领的像素绽放PixelBloom团队:兼具4A公司的行业底蕴与AI产品的工程化思维,这种复合型能力在AI应用层极为稀缺;我们看到了当年投禾赛、云鲸时同样的特质——创始人不仅懂技术,更懂如何将不可复制的行业经验转化为可规模化的产品能力并不断快速迭代。同时,像素绽放PixelBloom“生而全球化”的战略布局,从20余种语言的深度本地化到北美市场的清晰路径,展现了中国AI企业在全球市场的雄心与执行力。我们期待陪伴像素绽放PixelBloom从国内领军者成长为全球AI应用领域的行业龙头。

欢迎交流~

日本最高外汇官员:国际货币基金组织规则并不限制干预次数

2026年5月7日 08:54
日本最高外汇官员三村淳5月7日表示,国际货币基金组织(IMF)的规则并未限制各当局干预外汇市场的频率。日元周三大幅上涨后,三村淳对记者说,政府将继续带着高度紧迫感密切关注汇率走势。他拒绝就这些波动发表评论。三村淳称,外汇市场的投机活动仍在继续,当局正在关注各个市场,以寻求可能的应对措施。他拒绝评论日元汇率是否存在一条防线。他也拒绝就美国财政部长斯科特·贝森特即将进行的访问发表评论。(界面)

两市融资余额增加410.83亿元

2026年5月7日 08:48
36氪获悉,截至5月6日,上交所融资余额报13935.28亿元,较前一交易日增加225.99亿元;深交所融资余额报13347.05亿元,较前一交易日增加184.84亿元;两市合计27282.33亿元,较前一交易日增加410.83亿元。

天舟九号货运飞船已受控再入大气层

2026年5月7日 08:47
据中国载人航天工程办公室消息,天舟九号货运飞船已于北京时间2026年5月7日7时49分受控再入大气层,飞船少量残骸落入预定安全海域。天舟九号货运飞船于2025年7月15日在文昌航天发射场发射入轨,为空间站送去航天员在轨驻留消耗品、推进剂、应用实(试)验装置等物资。(新华社)

Vue 3 Composition API 深度解析

作者 Csvn
2026年5月7日 08:42

引言

Vue 3 的 Composition API 是 Vue 框架最具革命性的更新之一。它解决了 Options API 在大型项目中代码组织、逻辑复用和类型推导等方面的痛点。本文将深入讲解 Composition API 的核心概念、使用技巧以及最佳实践。

什么是 Composition API?

Composition API 是一组基于函数的 API,允许我们将相关逻辑组织在一起,而不是将代码分散在 datamethodscomputed 等选项中。

对比:Options API vs Composition API

Options API (Vue 2 风格):

export default {
  data() {
    return {
      count: 0,
      todos: []
    }
  },
  methods: {
    increment() {
      this.count++
    },
    fetchTodos() {
      // 获取待办事项
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  mounted() {
    this.fetchTodos()
  }
}

Composition API (Vue 3 风格):

import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const todos = ref([])
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    const fetchTodos = async () => {
      // 获取待办事项
    }
    
    onMounted(() => {
      fetchTodos()
    })
    
    return {
      count,
      todos,
      doubleCount,
      increment,
      fetchTodos
    }
  }
}

核心 API 详解

1. ref() - 响应式基础类型

ref() 用于创建响应式的基本类型值。

import { ref } from 'vue'

const count = ref(0)

// 访问和修改
console.log(count.value) // 0
count.value = 1

// 在模板中自动解包,不需要 .value

关键点:

  • 在 JavaScript 中需要通过 .value 访问
  • 在模板中自动解包
  • 支持任意类型的值

2. reactive() - 响应式对象

reactive() 用于创建响应式对象。

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: '张三',
    age: 25
  }
})

// 直接访问,不需要 .value
state.count++
state.user.age = 26

ref vs reactive

  • ref 可以处理任意类型,包括基本类型
  • reactive 只能处理对象类型
  • ref 需要 .valuereactive 直接访问

3. computed() - 计算属性

import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

const fullName = computed(() => {
  return firstName.value + lastName.value
})

// 只读计算属性
console.log(fullName.value) // '张三'

// 可写计算属性
const fullNameWritable = computed({
  get: () => firstName.value + lastName.value,
  set: (val) => {
    const [first, last] = val.split(' ')
    firstName.value = first
    lastName.value = last
  }
})

4. watch() 和 watchEffect()

watch() - 精确监听:

import { ref, watch } from 'vue'

const count = ref(0)

// 监听单个值
watch(count, (newVal, oldVal) => {
  console.log(`从 ${oldVal} 变为 ${newVal}`)
})

// 监听多个值
watch([count, firstName], ([newCount, newName]) => {
  console.log('变化:', newCount, newName)
})

// 监听对象属性(深度监听)
const user = reactive({ profile: { name: '张三' } })
watch(
  () => user.profile,
  (newVal) => {
    console.log('profile 变化', newVal)
  },
  { deep: true }
)

watchEffect() - 自动追踪依赖:

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  // 自动追踪 count 的变化
  console.log('count 是:', count.value)
  // 这里不需要指定监听谁,Vue 自动分析依赖
})

5. 生命周期钩子

Composition API 中的生命周期钩子需要在 setup() 中调用:

import { 
  onMounted, 
  onUpdated, 
  onUnmounted,
  onBeforeMount,
  onBeforeUpdate,
  onBeforeUnmount,
  onErrorCaptured
} from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    onUnmounted(() => {
      console.log('组件已卸载')
    })
    
    // 错误捕获
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err)
      return false // 阻止错误继续传播
    })
  }
}

逻辑复用:组合式函数

Composition API 最大的优势在于逻辑复用。我们可以将相关逻辑封装成组合式函数(Composables)。

示例:useCounter

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const double = computed(() => count.value * 2)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    double,
    increment,
    decrement,
    reset
  }
}

使用:

import { useCounter } from './composables/useCounter'

export default {
  setup() {
    const { count, double, increment, decrement } = useCounter(10)
    
    return {
      count,
      double,
      increment,
      decrement
    }
  }
}

示例:useFetch

// composables/useFetch.js
import { ref, onMounted } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)
  
  const fetchData = async () => {
    try {
      loading.value = true
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  onMounted(() => {
    fetchData()
  })
  
  return {
    data,
    loading,
    error,
    refresh: fetchData
  }
}

使用:

import { useFetch } from './composables/useFetch'

export default {
  setup() {
    const { data, loading, error, refresh } = useFetch('/api/users')
    
    return {
      data,
      loading,
      error,
      refresh
    }
  }
}

实际应用场景

1. 表单处理

// composables/useForm.js
import { ref, reactive } from 'vue'

export function useForm(initialValues = {}, validators = {}) {
  const form = reactive({ ...initialValues })
  const errors = reactive({})
  const touched = reactive({})
  
  const validate = (field) => {
    if (validators[field]) {
      const error = validators[field](form[field])
      errors[field] = error
      return !error
    }
    return true
  }
  
  const validateAll = () => {
    return Object.keys(validators).every(validate)
  }
  
  const setField = (field, value) => {
    form[field] = value
    touched[field] = true
  }
  
  return {
    form,
    errors,
    touched,
    validate,
    validateAll,
    setField
  }
}

2. 响应式路由

// composables/useRoute.js
import { ref, computed } from 'vue'
import { useRoute as useVueRoute } from 'vue-router'

export function useRoute() {
  const route = useVueRoute()
  
  const queryParams = computed(() => route.query)
  const params = computed(() => route.params)
  const fullPath = computed(() => route.fullPath)
  
  return {
    route,
    queryParams,
    params,
    fullPath
  }
}

最佳实践

1. 逻辑组织

将相关的逻辑组织在一起:

export default {
  setup() {
    // 状态定义
    const count = ref(0)
    const loading = ref(false)
    
    // 计算属性
    const doubleCount = computed(() => count.value * 2)
    
    // 方法
    const increment = () => count.value++
    
    // 生命周期
    onMounted(() => {
      console.log('mounted')
    })
    
    // 返回
    return {
      count,
      doubleCount,
      increment
    }
  }
}

2. 使用 <script setup>

Vue 3.2+ 引入了 <script setup>,让 Composition API 更加简洁:

<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

const increment = () => count.value++

onMounted(() => {
  console.log('mounted')
})
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

3. 类型推导

Composition API 对 TypeScript 支持更好:

import { ref } from 'vue'

// 类型自动推导
const count = ref(0) // Ref<number>
const user = ref({ name: '张三', age: 25 }) // Ref<{ name: string, age: number }>

// 显式类型
const count2 = ref<number>(0)
interface User {
  name: string
  age: number
}
const user2 = ref<User>({ name: '张三', age: 25 })

总结

Composition API 为 Vue 3 带来了:

  1. 更好的逻辑组织 - 相关代码放在一起,而不是分散在多个选项中
  2. 更强的逻辑复用 - 通过组合式函数实现代码复用
  3. 更好的 TypeScript 支持 - 类型推导更准确
  4. 更小的打包体积 - 更好的 tree-shaking

虽然学习曲线稍陡,但对于中大型项目来说,Composition API 是更好的选择。建议从简单的 refcomputedwatch 开始,逐步掌握组合式函数的编写技巧。

华友钴业:拟2.1亿美元收购大西洋锂业100%股权

2026年5月7日 08:38
36氪获悉,华友钴业公告,公司拟以2.1亿美元(不含资本利得税)收购Atlantic Lithium Limited(简称"大西洋锂业")100%股权,最终收购价款将根据加纳税务局(GRA)申请资本利得税确定。本次交易完成后,公司将取得标的公司100%股权,大西洋锂业纳入公司合并报表范围。本次交易不构成关联交易,亦不构成《上市公司重大资产重组管理办法》规定的上市公司重大资产重组。

中信证券:头部券商优势进一步巩固,关注头部券商以及中型券商

2026年5月7日 08:34
36氪获悉,中信证券研报表示,26Q1上市券商扣非归母净利润同比+39.9%、扣非ROE回升至2.06%(同比+0.46ppts),轻重资本业务占比均为46%、结构趋于均衡。头部券商优势进一步巩固,扣非ROE达2.35%、利润集中度达74.7%。各业务条线差异化修复:26Q1资管、利息净收入已超越25Q3,经纪、投行净收入接近25Q3水平,投资交易受市场波动拖累但非方向性布局较深的头部券商表现稳健;管理费率优化与信用减值释放共同支撑利润弹性。
❌
❌