阅读视图

发现新文章,点击刷新页面。

民航局:实施通用航空经营许可与运行许可“两证合一”

19日下午,民航局举行例行新闻发布会,民航局相关负责人就新颁布的《中华人民共和国民用航空法》进行了整体介绍和重点解读。新修订的民用航空法共16章262条,从飞行安全、适航管理、通用航空和低空经济等多个方面,对现行法律进行了全面修订。新修订的民用航空法明确提出,国家鼓励发展通用航空,加快培育通用航空市场,构建通用航空基础设施网络,县级以上地方人民政府根据本地区发展实际,采取措施支持通用航空产业发展。统筹发展和安全,实施通用航空经营许可与运行许可“两证合一”,要求组织实施通用航空飞行的主体确保作业安全,从事通用航空定期运输的主体要制定民用航空器事故家属援助计划。(央视新闻)

中国中免:全资孙公司拟收购DFS股权,拟增发H股股份

36氪获悉,中国中免公告,公司第五届董事会第二十八次会议审议通过多项议案。一是同意全资孙公司收购DFS大中华区零售业务相关股权及资产;二是同意公司行使一般性授权在港交所主板增发H股股份;三是确定2025年度财务报告审计费用为401万元,其中H股审计费60万元,与2024年持平。

报道:过去一年谷歌Gemini模型授权业务规模实现爆发式增长

谷歌旗下Gemini人工智能模型的性能升级,正持续拉动公司营收增长。三位熟悉Gemini销售情况的知情人士透露,过去一年间,谷歌Gemini模型的授权业务规模实现爆发式增长,这一态势也印证了该系列模型的性能正稳步提升。另有一位了解谷歌销售策略的人士表示,由于客户在人工智能业务上的投入往往会带动其在谷歌其他产品上的支出,Gemini的热销有望进一步提振谷歌云核心服务器销售业务的营收。(新浪财经)

科大智能递表港交所

36氪获悉,港交所文件显示,科大智能科技股份有限公司向港交所提交上市申请书,联席保荐人为华泰国际、国元国际。

云动智能递表港交所

36氪获悉,港交所文件显示,杭州云动智能汽车技术股份有限公司向港交所提交上市申请书,独家保荐人为中金公司。

君乐宝递表港交所

36氪获悉,港交所文件显示,君乐宝乳业集团股份有限公司向港交所提交上市申请书,联席保荐人为中金公司、摩根士丹利。

德国重启电动汽车购车补贴

德国政府19日宣布,对新购入电动汽车的家庭提供最高可达6000欧元的补贴,以推动其国内电动汽车产业发展。这是德国在2023年底终止原有电动汽车购车补贴政策后,再次提供类似补贴。德国环境部当天发布公告说,该补贴面向私人消费者,适用于自2026年1月1日起新注册的纯电动汽车、部分插电式混动汽车及增程式电动汽车。根据政府设定的分级标准,按车辆类型、家庭人口和收入水平,补贴金额为1500欧元至6000欧元之间。(新华社)

*ST立方:公司股票仍存在重大违法强制退市风险,将于2026年1月20日(星期二)起复牌

36氪获悉,*ST立方公告,公司股票因市场传闻于1月19日停牌一天,1月20日开市起复牌。公司股票可能被实施交易类或重大违法强制退市,截至1月16日,其收盘价已连续四个交易日低于1元;公司收到《行政处罚及市场禁入事先告知书》,可能触及重大违法强制退市情形。2025年1-9月,公司营收20333.04万元,同比降0.44%;净利润-6220.90万元,同比降20.59%,预计2025年全年净利润仍为负。目前年审工作未开展,存在审计风险。

*ST奥维提前锁定市值退市

36氪获悉,*ST奥维发布关于公司股票存在可能因市值终止上市的第四次风险提示公告。公告称,截至2026年1月19日,公司股票收盘总市值已连续12个交易日均低于5亿元。根据《深圳证券交易所股票上市规则》(简称《股票上市规则》)第9.2.1条的规定,若公司股票连续20个交易日收盘总市值均低于5亿元,公司股票将被深圳证券交易所(简称“深交所”)终止上市交易。、据计算,即使未来8个交易日每日涨停,其总市值也难以回升至5亿元,提前锁定市值退市。

每日一题-构造最小位运算数组 I🟢

给你一个长度为 n 的质数数组 nums 。你的任务是返回一个长度为 n 的数组 ans ,对于每个下标 i ,以下 条件 均成立:

  • ans[i] OR (ans[i] + 1) == nums[i]

除此以外,你需要 最小化 结果数组里每一个 ans[i] 。

如果没法找到符合 条件 的 ans[i] ,那么 ans[i] = -1 。

质数 指的是一个大于 1 的自然数,且它只有 1 和自己两个因数。

 

示例 1:

输入:nums = [2,3,5,7]

输出:[-1,1,4,3]

解释:

  • 对于 i = 0 ,不存在 ans[0] 满足 ans[0] OR (ans[0] + 1) = 2 ,所以 ans[0] = -1 。
  • 对于 i = 1 ,满足 ans[1] OR (ans[1] + 1) = 3 的最小 ans[1] 为 1 ,因为 1 OR (1 + 1) = 3 。
  • 对于 i = 2 ,满足 ans[2] OR (ans[2] + 1) = 5 的最小 ans[2] 为 4 ,因为 4 OR (4 + 1) = 5 。
  • 对于 i = 3 ,满足 ans[3] OR (ans[3] + 1) = 7 的最小 ans[3] 为 3 ,因为 3 OR (3 + 1) = 7 。

示例 2:

输入:nums = [11,13,31]

输出:[9,12,15]

解释:

  • 对于 i = 0 ,满足 ans[0] OR (ans[0] + 1) = 11 的最小 ans[0] 为 9 ,因为 9 OR (9 + 1) = 11 。
  • 对于 i = 1 ,满足 ans[1] OR (ans[1] + 1) = 13 的最小 ans[1] 为 12 ,因为 12 OR (12 + 1) = 13 。
  • 对于 i = 2 ,满足 ans[2] OR (ans[2] + 1) = 31 的最小 ans[2] 为 15 ,因为 15 OR (15 + 1) = 31 。

 

提示:

  • 1 <= nums.length <= 100
  • 2 <= nums[i] <= 1000
  • nums[i] 是一个质数。

出海也有 “斩杀线”:哪吒汽车陷20亿泰债困局

出品 | 虎嗅汽车组

作者 | 魏微

头图 | 视觉中国


当“斩杀线”从游戏术语演变为描述经济脆弱性的热词,它精准捕捉了一种临界状态——个体或组织因资源、信用耗尽,陷入系统性崩溃的风险。


在汽车出海的复杂战场上,这样一击致命的“斩杀”机制同样存在。


近日,泰国财政部宣布计划起诉哪吒汽车泰国子公司“NETA Auto Thailand”,追讨其自2022年起累计获得的20亿泰铢(约合4亿元人民币)电动汽车购车补贴,对这家正处在破产重整关键阶段的中国新势力而言,这无异雪上加霜。


值得关注的是,哪吒汽车在泰国的遭遇,并非源于惨烈的市场肉搏,而是因母公司“残血”失能,触发了东道国产业政策的高压红线。


本期车圈脉动VOL.19,我们聚焦哪吒汽车的20亿泰债困局,并试图解析该事件对其他当地中国车企的影响。


从市场黑马到被政府追债


哪吒汽车在泰国市场一度展现出黑马势头。


2022年进入泰国市场后,哪吒与当地企业BGAC合作生产,初期投放NETA X(哪吒X)和NETA AYA(哪吒V)两款车型,精准契合当地消费者需求。


2024年3月哪吒汽车泰国工厂开启试生产,图片源自哪吒汽车原CEO张勇社交媒体


据当地媒体数据,哪吒汽车2024年在泰国纯电动新能源汽车市场的占有率一度冲至国际品牌第二,全年占有率达到11.4%。


哪吒汽车的品质在当地收获不少好评,甚至成为许多当地消费者的首款电动车。据虎嗅汽车从熟悉哪吒汽车人士了解,在业务停摆前,哪吒汽车在泰国快速积累了超过2万名用户。


繁荣的表象下,危机已潜伏。


根据泰国EV 3.0政策要求,哪吒汽车2025年本地生产目标为19000辆,然而受母公司经营危机拖累,截至2025年6月,其实际产量仅约4000辆,目标达成率不足25%,远未满足本地化生产承诺。


在2025年6月,哪吒汽车泰国子公司就因未能履行在泰国的生产承诺,失去了获得泰国政府EV3.0补贴的资格。


而这背后,正是国内母公司合众新能源正在“失血”。2025年合众新能源深陷财务危机,国内员工围堵上海总部向创始人方运舟讨薪的场景,成为公司资金链断裂的公开写照,随后公司正式进入破产重整程序。


受此影响,哪吒在泰国的经销商渠道急剧萎缩。据外媒报道,2025年7月时,哪吒汽车的泰国经销商数量已从60多家锐减到约20家。售后保障能力大幅下滑。


“(这件事)主要对部分哪吒车主有影响,涉及到他们车辆的保养、维修和二手车残值,其他的影响不大。”知情人士对虎嗅汽车表示。


另据当地媒体报道,哪吒泰国的中方管理层曾尝试以12亿—20亿泰铢的估值,将公司整体出售给泰资企业,但潜在投资者因品牌风险过高、持续经营能力存疑,最终放弃交易。


因此,在多次要求哪吒退还补贴未果后,泰国财政部于2026年1月宣布将启动诉讼,追讨全部20亿泰铢补贴,并计划冻结、扣押哪吒在泰资产(包括银行账户),通过法院强制执行退款。此次诉讼对象直接指向哪吒汽车在泰法人实体,不过泰国财政部尚未明确提起诉讼的具体时间表。


在相关消息爆出后,虎嗅汽车尝试联系哪吒汽车原泰国公司相关负责人,但截至发稿未获回应。


目前,哪吒汽车母公司的重整工作正进入关键阶段。



合众新能源重整已进入关键阶段,图片源自合众新能源管理人公众号


2025年9月,合众新能源管理人对外披露,破产重整程序唯一合格意向投资人完成报名,该主体已提交完整材料并缴纳5000万元保证金,进入方案谈判阶段。尽管有多家媒体将该意向投资方指向山子高科。然而,山子高科官方后续多次表示“还未最终确定”、“需以管理人公布为准”。截至目前,合众新能源管理人,以及受理该案的嘉兴中院也尚未发布最终裁定。


若重整顺利落地,或为哪吒泰国业务带来转机。“哪吒在泰国依旧有不错的用户基础,很多用户都希望哪吒能恢复正常。如果新的投资人能继续运营下去的话,一定会好起来的。”上述知情人士表示。


哪吒汽车让泰国紧急补bug


为推动电动汽车产业发展、实现2030年新能源汽车占比30%的目标,泰国政府自2022年起实施“EV 3.0”补贴计划,核心逻辑是明确的“以市场换产业”——通过政策红利吸引海外车企落地,推动本地制造与供应链升级。


该计划的补贴政策有明确门槛:针对售价不超过200万泰铢的纯电动乘用车,单车最高补贴15万泰铢(约合3万元人民币),同时配套免征进口关税等优惠。


但享受补贴的前提是严苛的生产配比要求:海外车企自2024年起需遵循1:1的进口与本地生产比例;若2025年才启动本地生产,该比例则上调至1:1.5。


政策红利下,中国车企纷纷布局泰国市场。截至2025年6月,比亚迪、广汽埃安、长安汽车、奇瑞汽车、上汽名爵、长城汽车等7家中国车企已在泰建厂,累计产能规划超60万辆,成为泰国新能源市场的核心力量。


也正是在2025年6月,哪吒汽车泰国子公司在当地的补贴资格被叫停后,泰国财政部紧急要求国家电动汽车政策委员会修改相关规定。财政部提议新增规则,要求电动汽车制造商每两个月提交一份补偿生产计划,若企业未能遵守,政府有权暂停补贴发放。


这也意味着,泰国电动汽车市场的政策也正在向更精细化管理的方向调整。对此,另有业内人士对虎嗅汽车表示:“哪吒汽车的事是个例,对其他车企影响不大,只要兑现投资承诺,‘填上坑’就行。”


咨询公司安永-博智隆的报告显示,印度尼西亚、马来西亚、泰国、新加坡等东南亚地区的电动汽车市场将迎来爆发式增长,预计到2035年,这些市场的电动汽车销售总额将从2021年的20亿美元增至800亿至1000亿美元,潜力巨大。而中国产品在泰国电动汽车市场的占比已高达75%,成为当地市场的绝对主力。


中汽协数据也印证了中国汽车出海的上升势头:2025年中国汽车出口量达到709.8万辆,同比增长21.1%;2026年预计将进一步增至740万辆。


下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

3314. 构造最小位运算数组 I

解法一

思路和算法

数组 $\textit{nums}$ 的长度是 $n$。对于 $0 \le i < n$ 的每个下标 $i$,满足 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) = \textit{nums}[i]$。由于 $\textit{nums}[i]$ 是质数,即 $\textit{nums}[i]$ 至少等于 $2$,因此 $\textit{ans}[i]$ 一定是正整数。

最直观的计算 $\textit{ans}[i]$ 的方法是从小到大遍历每个正整数 $x$,寻找满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$ 的最小正整数 $x$ 填入 $\textit{ans}[i]$,如果不存在符合要求的正整数 $x$ 则 $\textit{ans}[i] = -1$。

根据按位或运算的性质,必有 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) \ge \textit{ans}[i]$,因此当 $\textit{ans}[i] > \textit{nums}[i]$ 时必有 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) > \textit{nums}[i]$,计算 $\textit{ans}[i]$ 时只需要遍历不超过 $\textit{nums}[i]$ 的值。

代码

###Java

class Solution {
    public int[] minBitwiseArray(List<Integer> nums) {
        int n = nums.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = find(nums.get(i));
        }
        return ans;
    }

    public int find(int num) {
        for (int i = 1; i <= num; i++) {
            if ((i | (i + 1)) == num) {
                return i;
            }
        }
        return -1;
    }
}

###C#

public class Solution {
    public int[] MinBitwiseArray(IList<int> nums) {
        int n = nums.Count;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = Find(nums[i]);
        }
        return ans;
    }

    public int Find(int num) {
        for (int i = 1; i <= num; i++) {
            if ((i | (i + 1)) == num) {
                return i;
            }
        }
        return -1;
    }
}

复杂度分析

  • 时间复杂度:$O(nm)$,其中 $n$ 是数组 $\textit{nums}$ 的长度,$m$ 是数组 $\textit{nums}$ 中的最大元素。答案数组中的每个元素的计算时间都是 $O(m)$。

  • 空间复杂度:$O(1)$。注意返回值不计入空间复杂度。

解法二

思路和算法

当存在正整数 $x$ 满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$ 时,由于 $x$ 和 $x + 1$ 当中一定有一个奇数,因此 $\textit{nums}[i]$ 一定是奇数。如果 $\textit{nums}[i]$ 是偶数,则不存在符合要求的 $x$,对应 $\textit{ans}[i] = -1$。当 $\textit{nums}[i]$ 是奇数时,可以取 $x = \textit{nums}[i] - 1$,则满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$,因此如果 $\textit{nums}[i]$ 是奇数,则一定存在符合要求的 $x$。

由于 $\textit{nums}[i]$ 是质数,唯一的偶质数是 $2$,因此当 $\textit{nums}[i] = 2$ 时 $\textit{ans}[i] = -1$。当 $\textit{nums}[i] > 2$ 时 $\textit{ans}[i] > 0$,需要计算 $\textit{ans}[i]$。

考虑正整数 $x$ 和 $x + 1$ 的二进制表示的如下两种情况。

  • 当 $x$ 是偶数时,$x$ 的二进制表示的最低位是 $0$,$x + 1$ 的二进制表示等于 $x$ 的二进制表示的最低位从 $0$ 变成 $1$。

  • 当 $x$ 是奇数时,$x$ 的二进制表示最低位有 $k$ 个 $1$ 且从低到高第 $k$ 位等于 $0$(位数从 $0$ 开始计数),其中 $k \ge 1$,$x + 1$ 的二进制表示等于 $x$ 的二进制表示的最低 $k$ 位从 $1$ 变成 $0$ 且从低到高第 $k$ 位从 $0$ 变成 $1$。

因此,$x ~|~ (x + 1)$ 的二进制表示的结果等于 $x$ 的二进制表示的最右边的 $0$ 变成 $1$。

为了使 $\textit{ans}[i]$ 的值最小,需要找到 $\textit{nums}[i]$ 的二进制表示中的可以变成 $0$ 的最左边的 $1$,满足将 $0$ 变成 $1$ 之后在其右侧没有任何 $0$。计算方法如下。

  1. 计算 $\textit{nums}[i]$ 的二进制表示的最低连续 $1$ 的最大位数 $k$,满足二进制表示最低位有 $k$ 个 $1$ 且从低到高第 $k$ 位等于 $0$(位数从 $0$ 开始计数)。

  2. 计算 $\textit{ans}[i] = \textit{nums}[i] - 2^{k - 1}$。

根据位运算的性质,计算方法如下:计算 $\textit{lowbit} = ((\textit{nums}[i] + 1) ~&~ (-\textit{nums}[i] - 1)) >> 1$,则 $\textit{lowbit}$ 等于上述 $2^{k - 1}$,$\textit{ans}[i] = \textit{nums}[i] - \textit{lowbit}$。

代码

###Java

class Solution {
    public int[] minBitwiseArray(List<Integer> nums) {
        int n = nums.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = find(nums.get(i));
        }
        return ans;
    }

    public int find(int num) {
        if (num % 2 == 0) {
            return -1;
        } else {
            int lowbit = ((num + 1) & (-num - 1)) >> 1;
            return num - lowbit;
        }
    }
}

###C#

public class Solution {
    public int[] MinBitwiseArray(IList<int> nums) {
        int n = nums.Count;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = Find(nums[i]);
        }
        return ans;
    }

    public int Find(int num) {
        if (num % 2 == 0) {
            return -1;
        } else {
            int lowbit = ((num + 1) & (-num - 1)) >> 1;
            return num - lowbit;
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。答案数组中的每个元素的计算时间都是 $O(1)$。

  • 空间复杂度:$O(1)$。注意返回值不计入空间复杂度。

数学

解法:数学

考虑 x or (x + 1) 是一个怎样的数。可以发现,x + 1 的二进制表示和 x 相比,其实就是把 x 从最低位开始的连续 $1$ 都变成 $0$,然后把下一位变成 $1$,更高位都不变。

因此我们找出 nums[i] 从最低位开始的连续 $1$,把连续 $1$ 的最后一位变成 $0$,就是答案。复杂度 $\mathcal{O}(n\log X)$,其中 $X = 10^9$ 是取值范围。

参考代码(c++)

###cpp

class Solution {
public:
    vector<int> minBitwiseArray(vector<int>& nums) {
        vector<int> ans;
        for (int x : nums) {
            if (x == 2) ans.push_back(-1);
            else {
                // 求从最低位开始的连续 1
                int p;
                for (p = 0; x >> p & 1; p++);
                // 把连续 1 的最后一位变成 0
                ans.push_back(x ^ (1 << (p - 1)));
            }
        }
        return ans;
    }
};

React Native之Android端Fabric 架构源码分析(下)

React Native之Android端Fabric 架构源码分析(下)

由于单章字数限制,这里将本文拆成上下两篇,上篇《React Native之Android端Fabric 架构源码分析(上)》

Fabric 组件

声明组件

要开发一个Fabric 组件,首先需要在TS代码中声明规范。我们使用npx create-react-native-library@latest创建一个Fabric组件库,然后参考根据官方文档的示例添加如下内容:

import type {
  CodegenTypes,
  HostComponent,
  ViewProps,
} from 'react-native';
import {codegenNativeComponent} from 'react-native';

type WebViewScriptLoadedEvent = {
  result: 'success' | 'error';
};

export interface NativeProps extends ViewProps {
  sourceURL?: string;
  onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}

export default codegenNativeComponent<NativeProps>(
  'CustomWebView',
) as HostComponent<NativeProps>;

这里主要有三部分:

  • WebViewScriptLoadedEvent 是一种支持的数据类型,用于存储事件需要从原生代码传递到 JavaScript 的数据。

  • NativeProps 定义了可以设置的组件属性。

  • codegenNativeComponent 允许我们为自定义组件生成代码,并为组件定义一个与原生实现相匹配的名称。

其中,对codegenNativeComponent函数进行分析,源码react-native/packages/react-native/Libraries/Utilities/codegenNativeComponent.js:

// 如果这个函数运行,说明视图配置没有在构建时通过 `GenerateViewConfigJs.js` 生成。
// 因此我们需要使用 `requireNativeComponent` 从视图管理器获取视图配置。
// `requireNativeComponent` 在 Bridgeless 模式下不可用。
// 例如:如果 `codegenNativeComponent` 不是从以 NativeComponent.js 结尾的文件中调用,
// 这个函数就会在运行时执行。
function codegenNativeComponent<Props: {...}>(
  componentName: string,
  options?: NativeComponentOptions,
): NativeComponentType<Props> {
  if (global.RN$Bridgeless === true && __DEV__) {
    console.warn(
      `Codegen didn't run for ${componentName}. This will be an error in the future. Make sure you are using @react-native/babel-preset when building your JavaScript code.`,
    );
  }
  // 确定基础组件名称
  let componentNameInUse =
    options && options.paperComponentName != null
      ? options.paperComponentName
      : componentName;

  // 省略部分代码......

  return (requireNativeComponent<Props>(
    // $FlowFixMe[incompatible-type]
    componentNameInUse,
  ): HostComponent<Props>);
}

根据注释可知,在新架构中,这个函数几乎没有实际意义,因为新架构是通过GenerateViewConfigJs.js 来生成实际的视图配置。

我们通过搜索codegenNativeComponent字符串,很容易定位到react-native/packages/babel-plugin-codegen/index.js文件,这里babel-plugin-codegen包就是检测codegenNativeComponent调用,解析TypeScript/Flow类型定义的工具包:

module.exports = function ({parse, types: t}) {
  return {
    pre(state) {
      this.code = state.code;
      this.filename = state.opts.filename;
      this.defaultExport = null;
      this.commandsExport = null;
      this.codeInserted = false;
    },

    // 省略部分代码.....
      ExportDefaultDeclaration(path, state) {
        if (isCodegenDeclaration(path.node.declaration)) {
          this.defaultExport = path;
        }
      },

      // 程序退出时进行替换
      Program: {
        exit(path) {
          if (this.defaultExport) {
            // 1. 生成ViewConfig代码
            const viewConfig = generateViewConfig(this.filename, this.code);
            // 2. 解析为AST
            const ast = parse(viewConfig, {
              babelrc: false,
              browserslistConfigFile: false,
              configFile: false,
            });

            // 3. 完全替换原始导出
            this.defaultExport.replaceWithMultiple(ast.program.body);

            if (this.commandsExport != null) {
              this.commandsExport.remove();
            }

            this.codeInserted = true;
          }
        },
      },
    },
  };
};


function generateViewConfig(filename /*: string */, code /*: string */) {
  // 解析TypeScript/Flow类型
  const schema = parseFile(filename, code);
  // 提取组件信息
  const libraryName = basename(filename).replace(
    /NativeComponent\.(js|ts)$/,
    '',
  );
  // 调用Codegen生成器
  return RNCodegen.generateViewConfig({
    libraryName,
    schema,
  });
}

function isCodegenDeclaration(declaration) {
  if (!declaration) {
    return false;
  }

  if (
    declaration.left &&
    declaration.left.left &&
    declaration.left.left.name === 'codegenNativeComponent'
  ) {
    return true;
  } else if (
    declaration.callee &&
    declaration.callee.name &&
    declaration.callee.name === 'codegenNativeComponent'
  ) {
    return true;
  } 
  // 省略......
  return false;
}

继续跟踪RNCodegen.generateViewConfig的实现,源码react-native/packages/react-native-codegen/src/generators/RNCodegen.js

const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');


module.exports = {
  allGenerators: ALL_GENERATORS,
  // 省略部分代码......
  generateViewConfig({
    libraryName,
    schema,
  }: Pick<LibraryOptions, 'libraryName' | 'schema'>): string {
    schemaValidator.validate(schema);

    const result = generateViewConfigJs
      .generate(libraryName, schema)
      .values()
      .next();

    if (typeof result.value !== 'string') {
      throw new Error(`Failed to generate view config for ${libraryName}`);
    }

    return result.value;
  },
};

最终是调用的GenerateViewConfigJs.js中的generate,源码react-native/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js

module.exports = {
  generate(libraryName: string, schema: SchemaType): FilesOutput {
    try {
      const fileName = `${libraryName}NativeViewConfig.js`;
      const imports: Set<string> = new Set();

      const moduleResults = Object.keys(schema.modules)
        .map(moduleName => {
          const module = schema.modules[moduleName];
          if (module.type !== 'Component') {
            return;
          }

          const {components} = module;

          return Object.keys(components)
            .map((componentName: string) => {
              const component = components[componentName];

              if (component.paperComponentNameDeprecated) {
                imports.add(UIMANAGER_IMPORT);
              }

              const replacedTemplate = ComponentTemplate({
                componentName,
                paperComponentName: component.paperComponentName,
                paperComponentNameDeprecated:
                  component.paperComponentNameDeprecated,
              });
      // 省略部分代码...... 

      return new Map([[fileName, replacedTemplate]]);
    } catch (error) {
      console.error(`\nError parsing schema for ${libraryName}\n`);
      console.error(JSON.stringify(schema));
      throw error;
    }
  },
};

这里我们根据ComponentTemplate中的模板,大概就能还原出生成的代码是什么样子:

// 输入:
// libraryName: "MyComponent"
// schema: { 解析后的TypeScript/Flow类型信息 }

// 输出:完整的JavaScript代码字符串


'use strict';

const {UIManager} = require("react-native")

let nativeComponentName = 'MyComponent';

export const __INTERNAL_VIEW_CONFIG = {
  uiViewClassName: 'MyComponent',
  bubblingEventTypes: {},
  directEventTypes: {},
  validAttributes: {
    opacity: true,
    backgroundColor: { process: require('react-native/Libraries/StyleSheet/processColor').default },
    // ... 其他属性
  }
};

export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);

这里最关键的就是最后一行,它将开发者编写的Fabric 组件规范进行了代码替换:

// 开发者编写
const CustomView = codegenNativeComponent<Props>('CustomView');

// Babel插件替换后
const CustomView = NativeComponentRegistry.get('CustomView', () => __INTERNAL_VIEW_CONFIG);

需要注意,babel-plugin-codegen工具并不像Codegen工具,会生成实际的代码文件。它是对AST语法树进行的动态修改和替换,也就是说它修改的是内存中的语法树,并不会写文件。

现在来重点追踪NativeComponentRegistry.get的实现,首先是导出位置react-native/packages/react-native/index.js

  get NativeComponentRegistry() {
    return require('./Libraries/NativeComponent/NativeComponentRegistry');
  },

定位到方法实现react-native/packages/react-native/Libraries/NativeComponent/NativeComponentRegistry.js

/**
 * 获取一个可以被 React Native 渲染的 `NativeComponent`。
 *
 * 提供的 `viewConfigProvider` 可能会被调用和使用,也可能不会,
 * 这取决于 `setRuntimeConfigProvider` 是如何配置的。
 */
export function get<Config: {...}>(
  name: string,
  viewConfigProvider: () => PartialViewConfig,
): HostComponent<Config> {
  // 注册ViewConfig到全局注册表
  ReactNativeViewConfigRegistry.register(name, () => {
    // 这里的回调函数会在React需要组件配置时被调用
    const {native, verify} = getRuntimeConfig?.(name) ?? {
      native: !global.RN$Bridgeless,   // 关键:新架构检测
      verify: false,
    };

    let viewConfig: ViewConfig;
    if (native) {
      // 旧架构:原生ViewManager
      viewConfig =
        getNativeComponentAttributes(name) ??
        createViewConfig(viewConfigProvider());
    } else {
      // 新架构:优先静态ViewConfig
      viewConfig =
        createViewConfig(viewConfigProvider()) ??
        getNativeComponentAttributes(name);
    }
    // 省略部分代码......
    return viewConfig;
  });

  // $FlowFixMe[incompatible-type] `NativeComponent` 实际上是字符串!
  return name;
}

继续跟踪ReactNativeViewConfigRegistry.register实现。源码react-native/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js

const viewConfigCallbacks = new Map<string, ?() => ViewConfig>();


export function register(name: string, callback: () => ViewConfig): string {
  // 省略部分代码......
  viewConfigCallbacks.set(name, callback);
  return name;
}

这里基本上就是将返回ViewConfig的闭包给存了起来。

查找组件

JS层

在前面启动渲染一节我们知道了启动渲染的最终调用是JS层的AppRegistry.runApplication方法。沿着这条线,我们来分析一下JS层的组件加载与处理流程。源码react-native/packages/react-native/Libraries/ReactNative/AppRegistry.js

import * as AppRegistry from './AppRegistryImpl';

// 省略部分代码......
global.RN$AppRegistry = AppRegistry;

registerCallableModule('AppRegistry', AppRegistry);

export {AppRegistry};

继续跟踪源码react-native/packages/react-native/Libraries/ReactNative/AppRegistryImpl.js

const runnables: Runnables = {};


/**
 * Loads the JavaScript bundle and runs the app.
 *
 * See https://reactnative.dev/docs/appregistry#runapplication
 */
export function runApplication(
  appKey: string,
  appParameters: AppParameters,
  displayMode?: number,
): void {
  if (appKey !== 'LogBox') {
    const logParams = __DEV__ ? ` with ${JSON.stringify(appParameters)}` : '';
    const msg = `Running "${appKey}"${logParams}`;
    console.log(msg);
  }

  SceneTracker.setActiveScene({name: appKey});
  runnables[appKey](appParameters, coerceDisplayMode(displayMode));
}


/**
 * Registers an app's root component.
 *
 * See https://reactnative.dev/docs/appregistry#registercomponent
 */
export function registerComponent(
  appKey: string,
  componentProvider: ComponentProvider,
  section?: boolean,
): string {
  const scopedPerformanceLogger = createPerformanceLogger();
  runnables[appKey] = (appParameters, displayMode) => {
    const renderApplication = require('./renderApplication').default;
    renderApplication(
      componentProviderInstrumentationHook(
        componentProvider,
        scopedPerformanceLogger,
      ),
      appParameters.initialProps,
      appParameters.rootTag,
      wrapperComponentProvider && wrapperComponentProvider(appParameters),
      rootViewStyleProvider && rootViewStyleProvider(appParameters),
      appParameters.fabric,
      scopedPerformanceLogger,
      appKey === 'LogBox', // is logbox
      appKey,
      displayMode,
    );
  };
  if (section) {
    sections[appKey] = runnables[appKey];
  }
  return appKey;
}

可以看到,runApplication调用的是runnables对象中注册的闭包。而在我们React Native JS层的Bundle包中,首先就需要调用AppRegistry.registerComponent(appName, () => App)完成最根组件的注册。所以runApplication方法中调用的闭包,就是在registerComponent中注册的闭包。

继续跟踪renderApplication方法的实现,源码react-native/packages/react-native/Libraries/ReactNative/renderApplication.js

export default function renderApplication<Props: Object>(
  RootComponent: React.ComponentType<Props>,
  initialProps: Props,
  rootTag: any,
  WrapperComponent?: ?React.ComponentType<any>,
  rootViewStyle?: ?ViewStyleProp,
  fabric?: boolean,
  scopedPerformanceLogger?: IPerformanceLogger,
  isLogBox?: boolean,
  debugName?: string,
  displayMode?: ?DisplayModeType,
  useOffscreen?: boolean,
) {

  const performanceLogger = scopedPerformanceLogger ?? GlobalPerformanceLogger;

  // 构建React元素树
  // 外层:PerformanceLoggerContext.Provider - 提供性能监控上下文
  // 中层:AppContainer - React Native的根容器组件
  // 内层:RootComponent - 用户注册的应用组件(如App.js)
  let renderable: React.MixedElement = (
    <PerformanceLoggerContext.Provider value={performanceLogger}>
      <AppContainer
        rootTag={rootTag}
        fabric={fabric}
        WrapperComponent={WrapperComponent}
        rootViewStyle={rootViewStyle}
        initialProps={initialProps ?? Object.freeze({})}
        internal_excludeLogBox={isLogBox}>
        <RootComponent {...initialProps} rootTag={rootTag} />
      </AppContainer>
    </PerformanceLoggerContext.Provider>
  );

  // 开发模式调试包装
  if (__DEV__ && debugName) {
    const RootComponentWithMeaningfulName = getCachedComponentWithDebugName(
      `${debugName}(RootComponent)`,
    );
    renderable = (
      <RootComponentWithMeaningfulName>
        {renderable}
      </RootComponentWithMeaningfulName>
    );
  }

  //  实验性离屏渲染支持
  if (useOffscreen && displayMode != null) {
    // $FlowFixMe[incompatible-type]
    // $FlowFixMe[prop-missing]
    // $FlowFixMe[missing-export]
    const Activity: ActivityType = React.unstable_Activity;

    renderable = (
      <Activity
        mode={displayMode === DisplayMode.VISIBLE ? 'visible' : 'hidden'}>
        {renderable}
      </Activity>
    );
  }

  // 我们希望在使用 Fabric 时始终启用 concurrentRoot 功能
  const useConcurrentRoot = Boolean(fabric);

  // 省略部分性能日志打印......

  // 进入React渲染系统
  Renderer.renderElement({
    element: renderable,
    rootTag,
    useFabric: Boolean(fabric),
    useConcurrentRoot,
  });
}

继续跟踪Renderer.renderElement方法实现。源码react-native/packages/react-native/Libraries/ReactNative/RendererImplementation.js

export function renderElement({
  element,
  rootTag,
  useFabric,
  useConcurrentRoot,
}: {
  element: React.MixedElement,
  rootTag: number,
  useFabric: boolean,
  useConcurrentRoot: boolean,
}): void {
  if (useFabric) {
    if (cachedFabricRender == null) {
      // 获取实际渲染器
      cachedFabricRender = getFabricRenderer().render;
    }

    cachedFabricRender(element, rootTag, null, useConcurrentRoot, {
      onCaughtError,
      onUncaughtError,
      onRecoverableError,
    });
  } else {
    // 省略旧架构......
  }
}

function getFabricRenderer(): ReactFabricType {
  if (cachedFabricRenderer == null) {
    cachedFabricRenderer = require('../Renderer/shims/ReactFabric').default;
  }
  return cachedFabricRenderer;
}

继续跟踪react-native/packages/react-native/Libraries/Renderer/shims/ReactFabric.js

import {BatchedBridge} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

import type {ReactFabricType} from './ReactNativeTypes';

let ReactFabric: ReactFabricType;

if (__DEV__) {
  ReactFabric = require('../implementations/ReactFabric-dev');
} else {
  ReactFabric = require('../implementations/ReactFabric-prod');
}

global.RN$stopSurface = ReactFabric.stopSurface;

if (global.RN$Bridgeless !== true) {
  BatchedBridge.registerCallableModule('ReactFabric', ReactFabric);
}

export default ReactFabric;

这里根据开发环境选择了不同的渲染器。开发环境的渲染器体积更大,包含了许多调试、性能日志等信息,而生产环境的移除了注释和空白,变量名被压缩代码经过优化,减少包体积。

继续跟踪生产环境的渲染器,因为代码更加简洁,可更好的聚焦于调用的链路和流程。源码react-native/packages/react-native/Libraries/Renderer/implementations/ReactFabric-prod.js

exports.render = function (
  element,
  containerTag,
  callback,
  concurrentRoot,
  options
) {
  var root = roots.get(containerTag);
  if (!root) {
    // 创建新的FiberRootNode
    // 省略部分代码......

    initializeUpdateQueue(concurrentRoot);
    roots.set(containerTag, root);
  }
  // 启动渲染 
  updateContainer(element, root, null, callback);
  // 返回渲染后的公共实例引用
  a: if (((element = root.current), element.child))
    switch (element.child.tag) {
      case 27:
      case 5:
        element = getPublicInstance(element.child.stateNode);
        break a;
      default:
        element = element.child.stateNode;
    }
  else element = null;
  return element;
};

function updateContainer(element, container, parentComponent, callback) {
  parentComponent = container.current;
  // / 获取更新优先级
  var lane = requestUpdateLane(parentComponent);
  null === container.context
    ? (container.context = emptyContextObject)
    : (container.pendingContext = emptyContextObject);

  // 创建更新对象
  container = createUpdate(lane);
  container.payload = { element: element };
  callback = void 0 === callback ? null : callback;
  null !== callback && (container.callback = callback);

  // 将更新加入队列
  element = enqueueUpdate(parentComponent, container, lane);

  // 调度更新
  null !== element &&
    (scheduleUpdateOnFiber(element, parentComponent, lane),
    entangleTransitions(element, parentComponent, lane));
  return lane;
}

以上调用都比较简单,这里的核心是scheduleUpdateOnFiber方法,它负责调度更新。由于代码量较大,我们后面只追求核心逻辑,将省略大部分代码:

function scheduleUpdateOnFiber(root, fiber, lane) {
  // 省略......

  // 标记更新并触发调度
  markRootUpdated$1(root, lane);
  if (/* 不在渲染中 */) {
      ensureRootIsScheduled(root);                    // 确保根节点被调度
      if (/* 同步更新 */) {
        flushSyncWorkAcrossRoots_impl(0, !0);         // 立即执行同步工作
      }
   }
}


function flushSyncWorkAcrossRoots_impl(syncTransitionLanes, onlyLegacy) {
  if (!isFlushingWork && mightHavePendingSyncWork) {
    isFlushingWork = !0;
    do {
      var didPerformSomeWork = !1;
      // 遍历所有根节点执行同步工作
      for (var root = firstScheduledRoot; null !== root; ) {
        if (!onlyLegacy || 0 === root.tag)
          //省略......
          // 常规同步更新 
          performSyncWorkOnRoot(root, JSCompiler_inline_result));

        root = root.next;
      }
    } while (didPerformSomeWork);
    isFlushingWork = !1;
  }
}


function performSyncWorkOnRoot(root, lanes) {
  if (flushPendingEffects()) return null;
  performWorkOnRoot(root, lanes, !0);
}


function performWorkOnRoot(root$jscomp$0, lanes, forceSync) {
   // 选择同步或并发渲染
   var shouldTimeSlice = (!forceSync && /* 条件判断 */);
   var exitStatus = shouldTimeSlice
      ? renderRootConcurrent(root, lanes)            // 并发渲染
      : renderRootSync(root, lanes, !0);             // 同步渲染

   // 省略......
}

继续追踪同步渲染renderRootSync方法实现:

function renderRootSync(root, lanes, shouldYieldForPrerendering) {
   // 省略......

   // 同步工作循环
   workLoopSync();
   // 省略......
}


function workLoopSync() {
  for (; null !== workInProgress; ) performUnitOfWork(workInProgress);
}


function performUnitOfWork(unitOfWork) {
  // 处理单个Fiber节点
  var next = beginWork(unitOfWork.alternate, unitOfWork, entangledRenderLanes);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  null === next ? completeUnitOfWork(unitOfWork) : (workInProgress = next);
}


function completeUnitOfWork(unitOfWork) {
  var completedWork = unitOfWork;
  do {
    // 省略......
    unitOfWork = completedWork.return;
    var next = completeWork(
      completedWork.alternate,
      completedWork,
      entangledRenderLanes
    );
    if (null !== next) {
      workInProgress = next;
      return;
    }
    completedWork = completedWork.sibling;
    if (null !== completedWork) {
      workInProgress = completedWork;
      return;
    }
    workInProgress = completedWork = unitOfWork;
  } while (null !== completedWork);
  0 === workInProgressRootExitStatus && (workInProgressRootExitStatus = 5);
}

最终是调用的completeWork方法完成渲染工作:

function completeWork(current, workInProgress, renderLanes) {
  var newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case 28:
    case 16:
    case 15:
    case 0:
    case 11:
    case 7:
    case 8:
    case 12:
    case 9:
    case 14:
      return bubbleProperties(workInProgress), null;
    case 1:
      return bubbleProperties(workInProgress), null;
    case 3:
      // 省略......
      // return  
    case 26:
    case 27:
    case 5:
      popHostContext(workInProgress);
      var type = workInProgress.type;
      if (null !== current && null != workInProgress.stateNode)
        // 省略......
      else {
        // 首次挂载
        if (!newProps) {  
          if (null === workInProgress.stateNode)
            throw Error(
              "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."
            );
          bubbleProperties(workInProgress);
          return null;
        }
        current = rootInstanceStackCursor.current;
        renderLanes = nextReactTag;
        nextReactTag += 2;
        // 获取ViewConfig
        type = getViewConfigForType(type);
        var updatePayload = ReactNativePrivateInterface.createAttributePayload(
          newProps,
          type.validAttributes
        );
        // 创建原生节点
        current = {
          node: createNode(
            renderLanes,
            type.uiViewClassName,  //  使用ViewConfig中的原生类名
            current.containerTag,
            updatePayload,
            workInProgress
          ),
          canonical: {
            nativeTag: renderLanes,
            viewConfig: type,       // 保存ViewConfig引用
            currentProps: newProps,
            internalInstanceHandle: workInProgress,
            publicInstance: null,
            publicRootInstance: current.publicInstance
          }
        };
        workInProgress.flags |= 8;
        appendAllChildren(current, workInProgress, !1, !1);
        workInProgress.stateNode = current;
      }
      bubbleProperties(workInProgress);
      workInProgress.flags &= -16777217;
      return null;
    // 省略部分代码......
  }
}

此方法中的case都是整数,为了弄清楚其含义,我们可以查看React 仓库中的Tag定义。源码位于 ReactWorkTags.js

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3; // 宿主树的根节点。它可以嵌套在另一个节点内部
export const HostPortal = 4; // 一个子树。它可以是通往不同渲染器的入口点
export const HostComponent = 5;
// 省略......

很显然,我们需要查看的是React Native的Tag类型,也就是HostComponent,它的值是5,因此对应到completeWork中的处理代码就是我截取的这部分。这段代码中有一个关键的方法,就是getViewConfigForType,查看其定义,显示为getViewConfigForType = ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get

可看到,这里就完全与前面声明组件一节最后分析到的ReactNativeViewConfigRegistry.js对应上了,我们前面分析的是ViewConfig的注册,现在来看一下get方法:

/**
 * 获取指定视图的配置。如果这是第一次使用该视图,此配置将从UIManager延迟加载
 */
export function get(name: string): ViewConfig {
  // 从viewConfigs Map中查找已缓存的ViewConfig
  let viewConfig = viewConfigs.get(name);
  if (viewConfig == null) {
    // 获取该组件的配置生成回调函数
    const callback = viewConfigCallbacks.get(name);
    if (typeof callback !== 'function') {
      // 省略日志......
    }
    viewConfig = callback();
    invariant(viewConfig, 'View config not found for component `%s`', name);

    processEventTypes(viewConfig);
    viewConfigs.set(name, viewConfig);

    // 配置设置后清除回调,这样我们就不会在注册过程中掩盖任何错误。
    viewConfigCallbacks.set(name, null);
  }
  return viewConfig;
}

通过ViewConfig,可以得到uiViewClassName,也就是声明的组件名称,我们继续查看原生节点创建的方法:

var _nativeFabricUIManage = nativeFabricUIManager,
  createNode = _nativeFabricUIManage.createNode,          


 createNode(
   renderLanes,
   type.uiViewClassName,  //  使用ViewConfig中的原生类名
   current.containerTag,
   updatePayload,
   workInProgress
 )

createNode方法来自于全局对象nativeFabricUIManager,通过变量名就知道,这个应该是来自于JSI定义的对象,代码不在JS层。

C++ 层

继续在C++中搜索nativeFabricUIManager,定位到源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

void UIManagerBinding::createAndInstallIfNeeded(
    jsi::Runtime& runtime,
    const std::shared_ptr<UIManager>& uiManager) {
  auto uiManagerModuleName = "nativeFabricUIManager";

  auto uiManagerValue =
      runtime.global().getProperty(runtime, uiManagerModuleName);
  if (uiManagerValue.isUndefined()) {
    // 全局命名空间中没有该绑定的实例;我们需要创建、安装并返回它
    auto uiManagerBinding = std::make_shared<UIManagerBinding>(uiManager);
    auto object = jsi::Object::createFromHostObject(runtime, uiManagerBinding);
    runtime.global().setProperty(
        runtime, uiManagerModuleName, std::move(object));
  }
}

可见这里的nativeFabricUIManager是一个jsi::HostObject对象,我们要查找其createNode方法,直接查看UIManagerBindingget方法:

jsi::Value UIManagerBinding::get(
    jsi::Runtime& runtime,
    const jsi::PropNameID& name) {
  auto methodName = name.utf8(runtime);

  // 将 shared_ptr<UIManager> 转换为原始指针
  // 为什么这样做?原因如下:
  // 1) UIManagerBinding 强引用(strongly retains)UIManager。
  //    JS VM 通过 JSI 强引用 UIManagerBinding。
  //    这些函数是 JSI 函数,只能通过 JS VM 调用;如果 JS VM 被销毁,
  //    这些函数无法执行,这些 lambda 也不会执行。
  // 2) UIManager 只有在所有对它的引用都被释放后才会被析构,包括
  //    UIManagerBinding。这只有在 JS VM 被析构时才会发生。因此,原始指针是安全的。
  //
  // 即使这样做是安全的,为什么不直接使用 shared_ptr 作为额外的保险呢?
  // 1) 在不需要的情况下使用 shared_ptr 或 weak_ptr
  // 是一种性能劣化(pessimisation)。
  //    在这种情况下,它会执行更多指令,但不会带来任何额外价值。
  // 2) 这些 lambda 的确切释放时机和方式很复杂。向它们添加 shared_ptr 会导致
  //    UIManager 可能存活更长时间,这是不必要的、复杂的认知负担。
  // 3) 有强烈怀疑认为,从这些 C++ lambda 中保留 UIManager(这些 lambda 被
  //    JSI 持有的对象所保留),在 Scheduler 和 JS VM 析构时导致了一些崩溃。
  //    如果 C++ 语义导致这些 lambda 在 JS VM 被析构后一个 CPU
  //    时钟周期(或更久) 才被释放,就可能发生这种情况。
  UIManager* uiManager = uiManager_.get();

  // Semantic: Creates a new node with given pieces.
  if (methodName == "createNode") {
    auto paramCount = 5;
    return jsi::Function::createFromHostFunction(
        runtime,
        name,
        paramCount,
        [uiManager, methodName, paramCount](
            jsi::Runtime& runtime,
            const jsi::Value& /*thisValue*/,
            const jsi::Value* arguments,
            size_t count) -> jsi::Value {
          try {
            validateArgumentCount(runtime, methodName, paramCount, count);

            auto instanceHandle =
                instanceHandleFromValue(runtime, arguments[4], arguments[0]);
            if (!instanceHandle) {
              react_native_assert(false);
              return jsi::Value::undefined();
            }

            return valueFromShadowNode(
                runtime,
                uiManager->createNode(
                    tagFromValue(arguments[0]),
                    stringFromValue(runtime, arguments[1]),
                    surfaceIdFromValue(runtime, arguments[2]),
                    RawProps(runtime, arguments[3]),
                    std::move(instanceHandle)),
                true);
          } catch (const std::logic_error& ex) {
            LOG(FATAL) << "logic_error in createNode: " << ex.what();
          }
        });
  }
  // 省略部分代码......

  return jsi::Value::undefined();
}

继续跟踪react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp中的createNode实现:

std::shared_ptr<ShadowNode> UIManager::createNode(
    Tag tag,                  // 节点标签
    const std::string& name,  // 组件名称
    SurfaceId surfaceId,
    RawProps rawProps,        // 原始属性
    InstanceHandle::Shared instanceHandle) const {
  TraceSection s("UIManager::createNode", "componentName", name);

  // 根据组件名称获取对应的ComponentDescriptor
  auto& componentDescriptor = componentDescriptorRegistry_->at(name);
  auto fallbackDescriptor =
      componentDescriptorRegistry_->getFallbackComponentDescriptor();

  PropsParserContext propsParserContext{surfaceId, *contextContainer_};

  // 创建ShadowNodeFamily,用于管理同一组件的不同实例
  auto family = componentDescriptor.createFamily(
      {.tag = tag,
       .surfaceId = surfaceId,
       .instanceHandle = std::move(instanceHandle)});

  // 解析和克隆属性
  const auto props = componentDescriptor.cloneProps(
      propsParserContext, nullptr, std::move(rawProps));
  // 创建初始状态
  const auto state = componentDescriptor.createInitialState(props, family);

  // 创建ShadowNode
  auto shadowNode = componentDescriptor.createShadowNode(
      ShadowNodeFragment{
          .props = fallbackDescriptor != nullptr &&
                  fallbackDescriptor->getComponentHandle() ==
                      componentDescriptor.getComponentHandle()
              ? componentDescriptor.cloneProps(
                    propsParserContext,
                    props,
                    RawProps(folly::dynamic::object("name", name)))
              : props,
          .children = ShadowNodeFragment::childrenPlaceholder(),
          .state = state,
      },
      family);

  if (delegate_ != nullptr) {
    delegate_->uiManagerDidCreateShadowNode(*shadowNode);
  }
  if (leakChecker_) {
    leakChecker_->uiManagerDidCreateShadowNodeFamily(family);
  }

  return shadowNode;
}

可以看到,这里通过componentDescriptorRegistry_来查找Fabric 组件描述对象,至于注册的地方,下一节注册组件会专门分析。

这里还有一点要注意,createShadowNode方法只是创建了虚拟的ShadowNode,并没有创建真正的原生视图。ShadowNode是Fabric中的虚拟DOM节点,用于布局计算。也就是说当完成布局和diff计算后,会生成MountItem指令。

Kotlin层

在前面启动渲染一节中,我们有分析到createViewUnsafe方法,回顾一下安卓原生组件的查找:

ViewManager viewManager = mViewManagerRegistry.get(componentName);

跟踪源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.kt

  @Synchronized
  public fun get(className: String): ViewManager<*, *> {
    // 1. Try to get the manager without the prefix.
    viewManagersMap[className]?.let {
      return it
    }

    // 2. Try to get the manager with the RCT prefix.
    val rctViewManagerName = "RCT$className"
    viewManagersMap[rctViewManagerName]?.let {
      return it
    }

    if (viewManagerResolver != null) {

      // 1. Try to get the manager without the prefix.
      val resolvedManager = getViewManagerFromResolver(className)
      if (resolvedManager != null) {
        return resolvedManager
      }

      // 2. Try to get the manager with the RCT prefix.
      val rctResolvedManager = getViewManagerFromResolver(rctViewManagerName)
      if (rctResolvedManager != null) {
        return rctResolvedManager
      }

      throw IllegalViewOperationException(
          "Can't find ViewManager '$className' nor '$rctViewManagerName' in ViewManagerRegistry, " +
              "existing names are: ${viewManagerResolver.getViewManagerNames()}"
      )
    }

    throw IllegalViewOperationException("No ViewManager found for class $className")
  }

  private fun getViewManagerFromResolver(className: String): ViewManager<*, *>? {
    val viewManager = viewManagerResolver?.getViewManager(className)
    if (viewManager != null) {
      viewManagersMap[className] = viewManager
    }
    return viewManager
  }

新架构的情况下,是通过getViewManagerFromResolver方法来查找,其中viewManagerResolver的类型是BridgelessViewManagerResolver,它是一个内部类,定义在ReactInstance.kt文件中:

private class BridgelessViewManagerResolver(
      private val reactPackages: List<ReactPackage>,
      private val context: BridgelessReactContext,
  ) : ViewManagerResolver {
    private val lazyViewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()

    override fun getViewManager(viewManagerName: String): ViewManager<*, *>? {
      // 从懒加载包中查找
      val viewManager = getLazyViewManager(viewManagerName)
      if (viewManager != null) {
        return viewManager
      }
      // 如果通过延迟加载在所有 React 包中都找不到视图管理器,则回退到默认实现:立即初始化所有视图管理器
      return eagerViewManagerMap[viewManagerName]
    }

    private lateinit var _eagerViewManagerMap: Map<String, ViewManager<*, *>>

    @get:Synchronized
    val eagerViewManagerMap: Map<String, ViewManager<*, *>>
      get() {
        if (::_eagerViewManagerMap.isInitialized) {
          return _eagerViewManagerMap
        }

        val viewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()
        for (reactPackage in reactPackages) {
          if (reactPackage is ViewManagerOnDemandReactPackage) {
            continue
          }

          val viewManagersInPackage = reactPackage.createViewManagers(context)
          for (viewManager in viewManagersInPackage) {
            // TODO(T173624687): Should we throw/warn when the same view manager name is registered
            // twice?
            viewManagerMap[viewManager.name] = viewManager
          }
        }

        _eagerViewManagerMap = viewManagerMap
        return viewManagerMap
      }

    @Synchronized
    fun getLazyViewManager(viewManagerName: String): ViewManager<*, *>? {
      // 先查缓存 
      if (lazyViewManagerMap.containsKey(viewManagerName)) {
        return lazyViewManagerMap[viewManagerName]
      }

      // 缓存未命中则遍历所有 reactPackages,调用 createViewManager(name) 创建
      for (reactPackage in reactPackages) {
        if (reactPackage is ViewManagerOnDemandReactPackage) {
          val viewManager = reactPackage.createViewManager(context, viewManagerName)
          if (viewManager != null) {
            // TODO(T173624687): Should we throw/warn when the same view manager name is registered
            // twice?
            lazyViewManagerMap[viewManagerName] = viewManager
            return viewManager
          }
        }
      }

      return null
    }

    // 省略部分代码......
  }

注册组件

JS工具

React Native新架构使用了很多代码生成工具,以致于成了黑箱操作,这对于Turbo Module和Fabric组件的注册流程造成了理解上的困难。为此,我们不得不研究这些CLI工具。首先研究@react-native-community/cli工具,源码 cli。这里我们使用的版本是v20.0.2

查看源码cli/packages/cli-config-android/src/config/index.ts

export function dependencyConfig(
  root: string,
  userConfig: AndroidDependencyParams | null = {},
): AndroidDependencyConfig | null {
  if (userConfig === null) {
    return null;
  }

  const src = userConfig.sourceDir || findAndroidDir(root);

  if (!src) {
    return null;
  }

  const sourceDir = path.join(root, src);
  const manifestPath = userConfig.manifestPath
    ? path.join(sourceDir, userConfig.manifestPath)
    : findManifest(sourceDir);
  const buildGradlePath = findBuildGradle(sourceDir, '');
  const isPureCxxDependency =
    userConfig.cxxModuleCMakeListsModuleName != null &&
    userConfig.cxxModuleCMakeListsPath != null &&
    userConfig.cxxModuleHeaderName != null &&
    !manifestPath &&
    !buildGradlePath;

  if (!manifestPath && !buildGradlePath && !isPureCxxDependency) {
    return null;
  }

  let packageImportPath = null,
    packageInstance = null;

  if (!isPureCxxDependency) {
    const packageName =
      userConfig.packageName || getPackageName(manifestPath, buildGradlePath);
    const packageClassName = findPackageClassName(sourceDir);

    /**
     * This module has no package to export
     */
    if (!packageClassName) {
      return null;
    }

    packageImportPath =
      userConfig.packageImportPath ||
      `import ${packageName}.${packageClassName};`;

    packageInstance = userConfig.packageInstance || `new ${packageClassName}()`;
  }

  const buildTypes = userConfig.buildTypes || [];
  const dependencyConfiguration = userConfig.dependencyConfiguration;
  const libraryName =
    userConfig.libraryName || findLibraryName(root, sourceDir);
  const componentDescriptors =
    userConfig.componentDescriptors || findComponentDescriptors(root);
  let cmakeListsPath = userConfig.cmakeListsPath
    ? path.join(sourceDir, userConfig.cmakeListsPath)
    : path.join(sourceDir, 'build/generated/source/codegen/jni/CMakeLists.txt');
  const cxxModuleCMakeListsModuleName =
    userConfig.cxxModuleCMakeListsModuleName || null;
  const cxxModuleHeaderName = userConfig.cxxModuleHeaderName || null;
  let cxxModuleCMakeListsPath = userConfig.cxxModuleCMakeListsPath
    ? path.join(sourceDir, userConfig.cxxModuleCMakeListsPath)
    : null;

  if (process.platform === 'win32') {
    cmakeListsPath = cmakeListsPath.replace(/\\/g, '/');
    if (cxxModuleCMakeListsPath) {
      cxxModuleCMakeListsPath = cxxModuleCMakeListsPath.replace(/\\/g, '/');
    }
  }

  return {
    sourceDir,
    packageImportPath,
    packageInstance,
    buildTypes,
    dependencyConfiguration,
    libraryName,
    componentDescriptors,
    cmakeListsPath,
    cxxModuleCMakeListsModuleName,
    cxxModuleCMakeListsPath,
    cxxModuleHeaderName,
    isPureCxxDependency,
  };
}

dependencyConfig函数非常重要,它主要用于生成example/android/build/generated/autolinking/autolinking.json文件。autolinking.json文件我们在前面的TurboModule的文章已经介绍过了,其中的信息是指导代码生成的关键。这里重点关注componentDescriptors字段的生成,因为它是Fabric组件代码生成的起始。

组件开发者通常并不会在react-native.config.js文件中主动配置componentDescriptors字段,一般都是默认值,那么就要查看一下findComponentDescriptors函数的实现逻辑了,源码cli/packages/cli-config-android/src/config/findComponentDescriptors.ts

export function findComponentDescriptors(packageRoot: string) {
  let jsSrcsDir = null;
  try {
    const packageJson = fs.readFileSync(
      path.join(packageRoot, 'package.json'),
      'utf8',
    );
    jsSrcsDir = JSON.parse(packageJson).codegenConfig.jsSrcsDir;
  } catch (error) {
    // no jsSrcsDir, continue with default glob pattern
  }
  const globPattern = jsSrcsDir
    ? `${jsSrcsDir}/**/*{.js,.jsx,.ts,.tsx}`
    : '**/*{.js,.jsx,.ts,.tsx}';
  const files = glob.sync(globPattern, {
    cwd: unixifyPaths(packageRoot),
    onlyFiles: true,
    ignore: ['**/node_modules/**'],
  });
  const codegenComponent = files
    .map((filePath) =>
      fs.readFileSync(path.join(packageRoot, filePath), 'utf8'),
    )
    .map(extractComponentDescriptors)
    .filter(Boolean);

  // Filter out duplicates as it happens that libraries contain multiple outputs due to package publishing.
  // TODO: consider using "codegenConfig" to avoid this.
  return Array.from(new Set(codegenComponent as string[]));
}

这里主要逻辑其实是从package.json中获取到jsSrcsDir配置,从而定位到源码所在目录。我们关注的是codegenComponent的生成,此处是通过读取每个源文件内容,然后调用 extractComponentDescriptors 提取组件描述符。继续查看cli/packages/cli-config-android/src/config/extractComponentDescriptors.ts

const CODEGEN_NATIVE_COMPONENT_REGEX =
  /codegenNativeComponent(<.*>)?\s*\(\s*["'`](\w+)["'`](,?[\s\S]+interfaceOnly:\s*(\w+))?/m;

export function extractComponentDescriptors(contents: string) {
  const match = contents.match(CODEGEN_NATIVE_COMPONENT_REGEX);
  if (!(match?.[4] === 'true') && match?.[2]) {
    return `${match[2]}ComponentDescriptor`;
  }
  return null;
}

到这里就很清楚了,扫描所有源码,使用正则找到我们在声明组件一节中提到的export default codegenNativeComponent<NativeProps>('CustomviewView');这行代码,这里的正则就是匹配关键字codegenNativeComponent。这里的match?.[2]就是提前出组件名,也就是我们示例中的CustomviewView。最终返回的字符串是拼接后的,这里就是CustomviewViewComponentDescriptor。也就是说componentDescriptors字段的默认值是CustomviewViewComponentDescriptor

接下来我们回顾前文《Android端TurboModule分析》,其中提到但略过的React Gradle脚本的configureCodegen方法:

  private fun configureCodegen(
      project: Project,
      localExtension: ReactExtension,
      rootExtension: PrivateReactExtension,
      isLibrary: Boolean,
  ) {
    // 首先,我们需要设置 Codegen 的输出目录
    val generatedSrcDir: Provider<Directory> =
        project.layout.buildDirectory.dir("generated/source/codegen")

    // 我们为 jsRootDir(JS根目录)指定默认值(约定)。
    // 对于 App 来说是根文件夹(即 Gradle 项目的 ../../)
    // 对于 Library 来说是包文件夹(即 Gradle 项目的 ../)
    if (isLibrary) {
      localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
    } else {
      localExtension.jsRootDir.convention(localExtension.root)
    }

    // 我们创建任务以从 JS 文件生成 Schema
    val generateCodegenSchemaTask =
        project.tasks.register(
            "generateCodegenSchemaFromJavaScript",
            GenerateCodegenSchemaTask::class.java,
        ) { it ->
          it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
          it.codegenDir.set(rootExtension.codegenDir)
          it.generatedSrcDir.set(generatedSrcDir)
          it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

          // 我们在配置阶段读取 package.json,以便正确地填充此任务的 `jsRootDir` @Input 属性
          // 以及 onlyIf 条件。因此,parsePackageJson 应该在这个 lambda 表达式内部被调用。
          val packageJson = findPackageJsonFile(project, rootExtension.root)
          val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }

          val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
          val includesGeneratedCode =
              parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
          if (jsSrcsDirInPackageJson != null) {
            it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
          } else {
            it.jsRootDir.set(localExtension.jsRootDir)
          }
          it.jsInputFiles.set(
              project.fileTree(it.jsRootDir) { tree ->
                tree.include("**/*.js")
                tree.include("**/*.jsx")
                tree.include("**/*.ts")
                tree.include("**/*.tsx")

                tree.exclude("node_modules/**/*")
                tree.exclude("**/*.d.ts")
                // 我们希望排除 build 目录,以避免在执行规避检查时选中它们
                tree.exclude("**/build/**/*")
              }
          )

          val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
          it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
        }

    // 我们创建任务以从 Schema 生成 Java 代码
    val generateCodegenArtifactsTask =
        project.tasks.register(
            "generateCodegenArtifactsFromSchema",
            GenerateCodegenArtifactsTask::class.java,
        ) { task ->
          task.dependsOn(generateCodegenSchemaTask)
          task.reactNativeDir.set(rootExtension.reactNativeDir)
          task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
          task.generatedSrcDir.set(generatedSrcDir)
          task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
          task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
          task.libraryName.set(localExtension.libraryName)
          task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

          // 请注意,appNeedsCodegen 会触发在配置阶段读取 package.json,
          // 因为我们需要填充此任务的 onlyIf 条件。
          // 因此,appNeedsCodegen 需要在这个 lambda 表达式内部被调用
          val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
          val packageJson = findPackageJsonFile(project, rootExtension.root)
          val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
          val includesGeneratedCode =
              parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
          task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
        }

    // 我们更新 Android 配置以包含生成的源码
    // 这相当于以下的 DSL:
    //
    // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
    if (isLibrary) {
      project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
        ->
        ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
      }
    } else {
      project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
          ext ->
        ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
      }
    }

    // `preBuild` 是 AGP 自动注册的基础任务之一
    // 这将在编译整个项目之前调用 Codegen
    project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
  }

configureCodegen 是 React Native Gradle 插件的核心方法,负责配置 Fabric/TurboModule 新架构的代码生成(Codegen)流程。该方法在 Gradle 配置阶段执行,设置两个关键任务和 Android 源码集集成。

其流程可以分为五个阶段:

  1. 设置输出目录和 JS 根目录

  2. 创建 Schema 生成任务

  3. 创建代码生成任务

  4. 集成到 Android 构建系统

  5. 挂载到构建生命周期

这里概括一下整个流程:

1. Gradle 配置阶段
   ├─> 设置 generatedSrcDir = "build/generated/source/codegen"
   ├─> 设置 jsRootDir 默认值(Library: "../", App: root)
   ├─> 读取 package.json(配置阶段)
   │   ├─> 检查 codegenConfig.jsSrcsDir
   │   └─> 检查 codegenConfig.includesGeneratedCode
   ├─> 注册 generateCodegenSchemaTask
   │   ├─> 配置 Node.js 环境
   │   ├─> 设置 JS 输入文件(**/*.{js,jsx,ts,tsx})
   │   └─> 设置 onlyIf 条件
   ├─> 注册 generateCodegenArtifactsTask
   │   ├─> 依赖 generateCodegenSchemaTask
   │   └─> 设置 onlyIf 条件
   ├─> 配置 Android SourceSets
   │   └─> 添加 generatedSrcDir/java 到 main SourceSet
   └─> 建立 preBuild 依赖关系

2. Gradle 执行阶段(运行 ./gradlew build)
   ├─> preBuild 任务执行
   │   └─> generateCodegenArtifactsTask 执行
   │         ├─> generateCodegenSchemaTask 执行
   │         │     ├─> 扫描 JS/TS 文件
   │         │     └─> 生成 schema.json
   │         └─> 根据 schema.json 生成 Java/C++ 代码
   └─> compileJava 编译生成的代码

这里的两个关键任务分别是generateCodegenSchemaFromJavaScriptgenerateCodegenArtifactsFromSchema。前者从 JS/TS 生成 Schema JSON文件,后者从 Schema 生成 Java/C++ 代码。

Schema JSON主要是JS/TS的接口描述,生成路径是Your Project/node_modules/Third-party Lib/android/build/generated/source/codegen/schema.json,可以自行查看,这里我们重点关注generateCodegenArtifactsFromSchema,源码react-native/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt

abstract class GenerateCodegenArtifactsTask : Exec() {
  @get:Internal abstract val reactNativeDir: DirectoryProperty
  @get:Internal abstract val generatedSrcDir: DirectoryProperty
  @get:InputFile abstract val packageJsonFile: RegularFileProperty   // package.json 文件路径
  @get:Input abstract val nodeWorkingDir: Property<String>
  @get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
  @get:Input abstract val codegenJavaPackageName: Property<String>
  @get:Input abstract val libraryName: Property<String>

  @get:InputFile
  val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")

  @get:OutputDirectory val generatedJavaFiles: Provider<Directory> = generatedSrcDir.dir("java")
  @get:OutputDirectory val generatedJniFiles: Provider<Directory> = generatedSrcDir.dir("jni")

  override fun exec() {
    val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
    setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
    super.exec()
  }

  internal fun resolveTaskParameters(): Pair<String, String> {
    val parsedPackageJson =
        if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
          JsonUtils.fromPackageJson(packageJsonFile.get().asFile)
        } else {
          null
        }
    val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
    val resolvedCodegenJavaPackageName =
        parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
    return resolvedLibraryName to resolvedCodegenJavaPackageName
  }

  internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
    val workingDir = File(nodeWorkingDir.get())
    commandLine(
        windowsAwareCommandLine(
            *nodeExecutableAndArgs.get().toTypedArray(),
            reactNativeDir.file("scripts/generate-specs-cli.js").get().asFile.cliPath(workingDir),
            "--platform",
            "android",
            "--schemaPath",
            generatedSchemaFile.get().asFile.cliPath(workingDir),
            "--outputDir",
            generatedSrcDir.get().asFile.cliPath(workingDir),
            "--libraryName",
            libraryName,
            "--javaPackageName",
            codegenJavaPackageName,
        )
    )
  }
}

该类主要用于执行外部 Node.js 命令,依据 schema.json文件,生成 Java/JNI 代码。需要注意一下,这里libraryNamecodegenJavaPackageName的取值,代码中是通过resolveTaskParameters方法提取出来的。回顾一下package.json文件的配置,大概是以下结构:

{
  "codegenConfig": {
    "name": "MyLibrary",
    "android": {
      "javaPackageName": "com.example.mylibrary"
    }
  }
}

那么libraryName就是"MyLibrary"codegenJavaPackageName就是"com.example.mylibrary"

现在再还原一下命令,其实就是以下形式:

node <reactNativeDir>/scripts/generate-specs-cli.js \
  --platform android \
  --schemaPath <generatedSrcDir>/schema.json \
  --outputDir <generatedSrcDir> \
  --libraryName <resolvedLibraryName> \
  --javaPackageName <resolvedCodegenJavaPackageName>

我们定位到该js文件react-native/packages/react-native/scripts/generate-specs-cli.js

const executor = require('./codegen/generate-specs-cli-executor');
// 省略......

function main() {
  executor.execute(
    // $FlowFixMe[prop-missing]
    argv.platform,
    // $FlowFixMe[prop-missing]
    argv.schemaPath,
    // $FlowFixMe[prop-missing]
    argv.outputDir,
    // $FlowFixMe[prop-missing]
    argv.libraryName,
    // $FlowFixMe[prop-missing]
    argv.javaPackageName,
    // $FlowFixMe[prop-missing]
    argv.libraryType,
  );
}

main();

继续跟踪react-native/packages/react-native/scripts/codegen/generate-specs-cli-executor.js

const utils = require('./codegen-utils');

const GENERATORS /*: {[string]: {[string]: $ReadOnlyArray<string>}} */ = {
  all: {
    android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
    ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
  },
  components: {
    android: ['componentsAndroid'],
    ios: ['componentsIOS'],
  },
  modules: {
    android: ['modulesAndroid', 'modulesCxx'],
    ios: ['modulesIOS', 'modulesCxx'],
  },
};

function generateSpecFromInMemorySchema(
  platform /*: string */,
  schema /*: string */,
  outputDirectory /*: string */,
  libraryName /*: string */,
  packageName /*: string */,
  libraryType /*: string */,
  useLocalIncludePaths /*: boolean */,
) {
  validateLibraryType(libraryType);
  createOutputDirectoryIfNeeded(outputDirectory, libraryName);
  const includeGetDebugPropsImplementation =
    libraryName.includes('FBReactNativeSpec'); //only generate getDebugString for React Native Core Components
  utils.getCodegen().generate(
    {
      libraryName,
      schema,
      outputDirectory,
      packageName,
      assumeNonnull: platform === 'ios',
      useLocalIncludePaths,
      includeGetDebugPropsImplementation,
    },
    {
      generators: GENERATORS[libraryType][platform],
    },
  );

  if (platform === 'android') {
    // Move all components C++ files to a structured jni folder for now.
    // Note: this should've been done by RNCodegen's generators, but:
    // * the generators don't support platform option yet
    // * this subdir structure is Android-only, not applicable to iOS
    const files = fs.readdirSync(outputDirectory);
    const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
    fs.mkdirSync(jniOutputDirectory, {recursive: true});
    files
      .filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
      .forEach(f => {
        fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
      });
  }
}

这里核心是调用utils.getCodegen().generate来执行代码生成逻辑,但是这里有一个传参需要注意一下,generators: GENERATORS[libraryType][platform],我们观察GENERATORS的定义就会发现,这里的参数配置正好对应package.json中的codegen配置,由于我们现在研究的是Fabric组件注册,那么这里的参数应该是componentsAndroid

继续查找generate方法的实现,源码react-native/packages/react-native/scripts/codegen/codegen-utils.js

/**
* 用于抽象实际代码生成过程的包装器。 
* 之所以需要这个包装器,是因为在 Sandcastle 中运行测试时,并非所有环境都像通常那样设置。 
* 例如,`@react-native/codegen` 库就不存在。 
*
* 借助这个包装器,我们可以模拟代码生成器的 getter 方法,使其返回一个自定义对象,该对象模拟了 Codegen 接口。 
*
* @return 一个可以为新架构生成代码的对象。 
*/
function getCodegen() /*: $FlowFixMe */ {
  let RNCodegen;
  try {
    // $FlowFixMe[cannot-resolve-module]
    RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js');
  } catch (e) {
    // $FlowFixMe[cannot-resolve-module]
    RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js');
  }
  if (!RNCodegen) {
    throw new Error('RNCodegen not found.');
  }
  return RNCodegen;
}

继续跟踪react-native/packages/react-native-codegen/lib/generators/RNCodegen.js

const LIBRARY_GENERATORS = {
  descriptors: [
    generateComponentDescriptorCpp.generate,
    generateComponentDescriptorH.generate,
  ],
  events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
  states: [generateStateCpp.generate, generateStateH.generate],
  props: [
    generateComponentHObjCpp.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],
  // TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
  componentsAndroid: [
    // JNI/C++ files
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
    // Java files
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],
  componentsIOS: [
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generateComponentHObjCpp.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
  ],
  modulesAndroid: [
    generateModuleJniCpp.generate,
    generateModuleJniH.generate,
    generateModuleJavaSpec.generate,
  ],
  modulesCxx: [generateModuleH.generate],
  modulesIOS: [generateModuleObjCpp.generate],
  tests: [generateTests.generate],
  'shadow-nodes': [
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
  ],
};


module.exports = {
  allGenerators: ALL_GENERATORS,
  generate(
    {
      libraryName,
      schema,
      outputDirectory,
      packageName,
      assumeNonnull,
      useLocalIncludePaths,
      includeGetDebugPropsImplementation = false,
      libraryGenerators = LIBRARY_GENERATORS,
    },
    {generators, test},
  ) {
    schemaValidator.validate(schema);
    const defaultHeaderPrefix = 'react/renderer/components';
    const headerPrefix =
      useLocalIncludePaths === true
        ? ''
        : `${defaultHeaderPrefix}/${libraryName}/`;
    function composePath(intermediate) {
      return path.join(outputDirectory, intermediate, libraryName);
    }
    const componentIOSOutput = composePath(
      useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
    );
    const modulesIOSOutput = composePath('./');
    const outputFoldersForGenerators = {
      componentsIOS: componentIOSOutput,
      modulesIOS: modulesIOSOutput,
      descriptors: outputDirectory,
      events: outputDirectory,
      props: outputDirectory,
      states: outputDirectory,
      componentsAndroid: outputDirectory,
      modulesAndroid: outputDirectory,
      modulesCxx: outputDirectory,
      tests: outputDirectory,
      'shadow-nodes': outputDirectory,
    };
    const generatedFiles = [];
    for (const name of generators) {
      for (const generator of libraryGenerators[name]) {
        generator(
          libraryName,
          schema,
          packageName,
          assumeNonnull,
          headerPrefix,
          includeGetDebugPropsImplementation,
        ).forEach((contents, fileName) => {
          generatedFiles.push({
            name: fileName,
            content: contents,
            outputDir: outputFoldersForGenerators[name],
          });
        });
      }
    }
    return checkOrWriteFiles(generatedFiles, test);
  },
  // 省略......
};

可以看到,这里for (const name of generators)遍历的generators就是我们前面强调过的GENERATORS参数处理。那么此时name的值就是componentsAndroid

既然确定了参数,那么从libraryGenerators中遍历出来的generator就是以下这些生成器:

  componentsAndroid: [
    // JNI/C++ files
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
    // Java files
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],

这里先研究一下generateComponentDescriptorH.generategenerateComponentDescriptorCpp.generate

源码react-native/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js

const FileTemplate = ({
  libraryName,
  componentDefinitions,
  headerPrefix,
}: {
  libraryName: string,
  componentDefinitions: string,
  headerPrefix: string,
}) => `
/**
 * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
 *
 * Do not edit this file as changes may cause incorrect behavior and will be lost
 * once the code is regenerated.
 *
 * ${'@'}generated by codegen project: GenerateComponentDescriptorH.js
 */

#pragma once

${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

${componentDefinitions}

void ${libraryName}_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);

} // namespace facebook::react
`;

const ComponentDefinitionTemplate = ({className}: {className: string}) =>
  `
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();

// 省略部分代码......

这里我们只简单看一下用于头文件生成的字符串模版。libraryName参数取值我们上面已经分析过了,来自package.json中的配置项。那么还有一个关键参数className的取值需要弄清楚。实际上这里的componentDefinitionsclassName都来自于schema.json。具体看一下className生成逻辑:

    const componentDefinitions = Object.keys(schema.modules)
      .map(moduleName => {
        const module = schema.modules[moduleName];
        if (module.type !== 'Component') {
          return;
        }

        const {components} = module;
        // No components in this module
        if (components == null) {
          return null;
        }

        return Object.keys(components)
          .map(componentName => {
            if (components[componentName].interfaceOnly === true) {
              return;
            }

            return ComponentDefinitionTemplate({className: componentName});
          })
          .join('\n');
      })
      .filter(Boolean)
      .join('\n');

    const replacedTemplate = FileTemplate({
      libraryName,
      componentDefinitions,
      headerPrefix: headerPrefix ?? '',
    });

可以看到,实际上是在遍历schema.json中的components字段。在声明组件一节已经创建了Demo工程,现在构建项目生成schema.json,查看相关内容:

{
  "libraryName": "",
  "modules": {
    "CustomWebView": {
      "type": "Component",
      "components": {
        "CustomWebView": {
          "extendsProps": [
            {
              "type": "ReactNativeBuiltInType",
              "knownTypeName": "ReactNativeCoreViewProps"
            }
          ],
          "events": [省略......],
          "props": [省略......],
          "commands": []
        }
      }
    }
  }
}

那么className就是componentName,也就是自定义的组件名CustomWebView

C++ 层

弄清楚代码生成的逻辑之后,接下来我们可以直接查看生成的文件内容,主要是ComponentDescriptors.h

#pragma once

#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

using CustomWebViewComponentDescriptor = ConcreteComponentDescriptor<CustomWebViewShadowNode>;

void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
}

ComponentDescriptors.cpp

#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
registry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
}

}

头文件定义了一个类型别名CustomWebViewComponentDescriptor,然后在实现文件中注册了这个Provider。我们看一下concreteComponentDescriptorProvider函数的实现,源码react-native/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProvider.h:

/*
* 为给定的 `ComponentDescriptor` 类创建一个 `ComponentDescriptorProvider`
*/
template <typename ComponentDescriptorT>
ComponentDescriptorProvider concreteComponentDescriptorProvider()
{
  static_assert(
      std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
      "ComponentDescriptorT must be a descendant of ComponentDescriptor");

  return {
      ComponentDescriptorT::ConcreteShadowNode::Handle(),
      ComponentDescriptorT::ConcreteShadowNode::Name(),
      nullptr,
      &concreteComponentDescriptorConstructor<ComponentDescriptorT>};
}

返回值是一个ComponentDescriptorProvider类型实例,继续跟踪一下类定义:

/*
 * 提供了一种统一的方式来构造特定存储的 `ComponentDescriptor` 类的实例。 
 * C++ 不允许创建指向构造函数的指针,因此我们必须使用这样的数据结构来操作一组类。
 *
 * 注意:某些组件的 `handle` 和 `name` 的实际值取决于 `flavor`。 
 * 如果使用给定的 `flavor` 通过 `constructor` 对象实例化后,
 * Provider暴露的 `handle` 和 `name` 值与预期值相同,则该提供者有效。 
 */
class ComponentDescriptorProvider final {
 public:
  ComponentHandle handle;
  ComponentName name;
  ComponentDescriptor::Flavor flavor;
  ComponentDescriptorConstructor *constructor;
};

这里大量使用了C++模版,要想查看真正的handle和name值,需要当实际的模版类型中查找。这里先查看源码react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h

namespace facebook::react {

/*
 * Default template-based implementation of ComponentDescriptor.
 * Use your `ShadowNode` type as a template argument and override any methods
 * if necessary.
 */
template <typename ShadowNodeT>
class ConcreteComponentDescriptor : public ComponentDescriptor {
  static_assert(std::is_base_of<ShadowNode, ShadowNodeT>::value, "ShadowNodeT must be a descendant of ShadowNode");

  using SharedShadowNodeT = std::shared_ptr<const ShadowNodeT>;

 public:
  using ConcreteShadowNode = ShadowNodeT;

  // 省略代码......
} // namespace facebook::react

可以看到,ConcreteShadowNode实际上只是一个类型别名,具体的要看模版的实际参数,那么ConcreteShadowNode::Handle就相当于CustomWebViewShadowNode::Handle。这里CustomWebViewShadowNode也是自动生成的代码,我们直接查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.h

/*
 * `ShadowNode` for <CustomWebView> component.
 */
using CustomWebViewShadowNode = ConcreteViewShadowNode<
    CustomWebViewComponentName,
    CustomWebViewProps,
    CustomWebViewEventEmitter,
    CustomWebViewState>;

继续查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.cpp

#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>

namespace facebook::react {

extern const char CustomWebViewComponentName[] = "CustomWebView";

} 

现在跟踪一下react-native/packages/react-native/ReactCommon/react/renderer/components/view/ConcreteViewShadowNode.h

namespace facebook::react {

/*
 * Template for all <View>-like classes (classes which have all same props
 * as <View> and similar basic behaviour).
 * For example: <Paragraph>, <Image>, but not <Text>, <RawText>.
 */
template <
    const char *concreteComponentName,
    typename ViewPropsT = ViewProps,
    typename ViewEventEmitterT = ViewEventEmitter,
    typename StateDataT = StateData>
  requires(std::is_base_of_v<ViewProps, ViewPropsT>)
class ConcreteViewShadowNode : public ConcreteShadowNode<
                                   concreteComponentName,
                                   YogaLayoutableShadowNode,
                                   ViewPropsT,
                                   ViewEventEmitterT,
                                   StateDataT> {
  // 省略代码......
};

} // namespace facebook::react

ConcreteViewShadowNode类中并未实现HandleName方法,继续查看父类react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h

namespace facebook::react {

/*
 * Base templace class for all `ShadowNode`s which connects exact `ShadowNode`
 * type with exact `Props` type.
 * `ConcreteShadowNode` is a default implementation of `ShadowNode` interface
 * with many handy features.
 */
template <
    ComponentName concreteComponentName,
    typename BaseShadowNodeT,
    typename PropsT,
    typename EventEmitterT = EventEmitter,
    typename StateDataT = StateData>
class ConcreteShadowNode : public BaseShadowNodeT {

 protected:
  using ShadowNode::props_;
  using ShadowNode::state_;

 public:
  using BaseShadowNodeT::BaseShadowNodeT;
  // 省略......

  static ComponentName Name()
  {
    return ComponentName(concreteComponentName);
  }

  static ComponentHandle Handle()
  {
    return ComponentHandle(concreteComponentName);
  }

  // 省略......
};

} // namespace facebook::react

到这里就很清晰了,Name和Handle方法返回值内部是持有的相同的concreteComponentName,而这个模版参数,根据前面的传参,实际是就是

CustomWebViewComponentName,也就是"CustomWebView"

扫描Fabric 组件库,生成代码的逻辑其实已经很清楚了,最后只剩下一个问题,真正调用注册的代码在哪里?事实上,安卓中并未真正通过CustomviewViewSpec_registerComponentDescriptorsFromCodegen函数去注册,而是使用了autolinking机制。这部分在《Android端TurboModule分析》一文有详细介绍了,可以去回顾一下GenerateAutolinkingNewArchitecturesFileTask脚本的分析,其中生成的信息源就来自我们前面费了半天劲分析的autolinking.json中的

现在来看一下Gradle脚本生成的autolinking.cpp

#include "autolinking.h"
#include <CustomviewViewSpec.h>
#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>

namespace facebook {
namespace react {

std::shared_ptr<TurboModule> autolinking_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams &params) {
auto module_CustomviewViewSpec = CustomviewViewSpec_ModuleProvider(moduleName, params);
if (module_CustomviewViewSpec != nullptr) {
return module_CustomviewViewSpec;
}
  return nullptr;
}

std::shared_ptr<TurboModule> autolinking_cxxModuleProvider(const std::string moduleName, const std::shared_ptr<CallInvoker>& jsInvoker) {

  return nullptr;
}

void autolinking_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry) {
providerRegistry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
  return;
}

} // namespace react
} // namespace facebook

这里生成了autolinking_registerProviders方法,这才是真正注册组件的地方。而此处的代码是由Gradle脚本生成,其中的关键信息就来自autolinking.json中的componentDescriptors字段,也就是前面我们费了半天劲才分析出该字段的默认值的地方。整个React Native的代码生成其实是有些混乱的,会生成一些并不会使用的代码,对理解产生干扰。

关于autolinking_registerProviders函数的调用链,在前面的文章也分析涉及过,这里再回顾一下调用流程,源码react-native/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp

namespace facebook::react {

void registerComponents(
    std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
  // 自定义 Fabric 组件放在这里。您可以在此处注册来自您的应用程序或第三方库的自定义组件
  // providerRegistry->add(concreteComponentDescriptorProvider<
  //        MyComponentDescriptor>());

  // We link app local components if available
#ifdef REACT_NATIVE_APP_COMPONENT_REGISTRATION
  REACT_NATIVE_APP_COMPONENT_REGISTRATION(registry);
#endif

  // And we fallback to the components autolinked
  autolinking_registerProviders(registry);
}

// 省略部分代码......

} // namespace facebook::react

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
  return facebook::jni::initialize(vm, [] {
    facebook::react::DefaultTurboModuleManagerDelegate::cxxModuleProvider =
        &facebook::react::cxxModuleProvider;
    facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider =
        &facebook::react::javaModuleProvider;
    facebook::react::DefaultComponentsRegistry::
        registerComponentDescriptorsFromEntryPoint =
            &facebook::react::registerComponents;
  });
}

可见,注册的起点也是JNI_OnLoad函数。

Kotlin层

使用npx create-react-native-library@latest工具创建一个Fabric组件库时,会生成一些模版代码,其中包括我们上面提到的CustomWebViewManager类,现在我们来看一下CustomWebViewPackage类:

class CustomWebViewPackage : ReactPackage {
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
    val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
    viewManagers.add(CustomWebViewManager())
    return viewManagers
  }

  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
    return emptyList()
  }
}

这里createViewManagers方法会返回一个ViewManager的实例列表。根据我们在《ReactNative新架构之Android端TurboModule机制完全解析》一文中分析GeneratePackageListTask任务的结果,我们知道最终会生成一个PackageList文件,其中会注入每个三方TurboModule或Fabric组件包中的ReactPackage实现类。

现在来查看一下我们示例工程生成的example/android/app/build/generated/autolinking/src/main/java/com/facebook/react/PackageList.java文件:

public class PackageList {
  private Application application;
  private ReactNativeHost reactNativeHost;
  private MainPackageConfig mConfig;

  // 省略部分代码......

  public ArrayList<ReactPackage> getPackages() {
    return new ArrayList<>(Arrays.<ReactPackage>asList(
      new MainReactPackage(mConfig),
      new CustomWebViewPackage()
    ));
  }
}

回顾一下本文开头的初始化部分,我们提到过以下代码

fabricUIManager =
    FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)

现在回顾一下《React Native新架构之Android端初始化源码分析》一文,在ReactInstance类构造时,有如下初始化逻辑:

    viewManagerResolver = BridgelessViewManagerResolver(reactPackages, context)

    ComponentNameResolverBinding.install(
        unbufferedRuntimeExecutor,
        object : ComponentNameResolver {
          override val componentNames: Array<String>
            get() {
              val viewManagerNames = viewManagerResolver.getViewManagerNames()
              if (viewManagerNames.isEmpty()) {
                FLog.e(TAG, "No ViewManager names found")
                return arrayOf()
              }
              return viewManagerNames.toTypedArray<String>()
            }
        },
    )

结合上一节查找组件 kotlin层实现的分析,整个流程都十分清晰了。

Win11 如何打开 IE11 浏览器

大家好,我是前端架构师,关注微信公众号【@程序员大卫】免费领取前端精品资料。

前言

有时候在调试老项目时,仍然需要使用 IE11 浏览器。但在 Windows 11 中,直接打开 IE 时,系统通常会自动跳转到 Microsoft Edge,导致无法真正进入 IE11。

下面教你一种简单、有效的方法,在 Win11 中直接打开 IE11。

1. 搜索并打开 cmd

在开始菜单中搜索 cmd,然后点击打开。

2. 粘贴并执行下面的脚本

将以下命令粘贴到 cmd 中并回车执行:

powershell -command "$pf86=[Environment]::GetFolderPath('ProgramFilesX86');$s=New-Object -ComObject WScript.Shell;$sc=$s.CreateShortcut(\"$env:USERPROFILE\Desktop\Internet Explorer.lnk\");$sc.TargetPath=\"$pf86\Internet Explorer\iexplore.exe\";$sc.Arguments='about:blank -Embedding';$sc.IconLocation=\"$pf86\Internet Explorer\iexplore.exe, 0\";$sc.Save()"

这段脚本会在桌面创建一个 IE 浏览器的快捷方式

3. 双击桌面的 IE 图标即可打开

执行完成后,桌面会出现一个 Internet Explorer 图标,双击它即可成功打开 IE11 浏览器。

❌