普通视图
中国中免:全资孙公司拟收购DFS股权,拟增发H股股份
报道:过去一年谷歌Gemini模型授权业务规模实现爆发式增长
OpenAI计划于2026年推出首款硬件设备
云英谷科技股份有限公司递表港交所
大族数控通过港交所上市聆讯
德国重启电动汽车购车补贴
*ST立方:公司股票仍存在重大违法强制退市风险,将于2026年1月20日(星期二)起复牌
*ST奥维提前锁定市值退市
每日一题-构造最小位运算数组 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 <= 1002 <= 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$。计算方法如下。
-
计算 $\textit{nums}[i]$ 的二进制表示的最低连续 $1$ 的最大位数 $k$,满足二进制表示最低位有 $k$ 个 $1$ 且从低到高第 $k$ 位等于 $0$(位数从 $0$ 开始计数)。
-
计算 $\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)$。注意返回值不计入空间复杂度。
O(1) 计算每个数(Python/Java/C++/Go)
本题和 3315. 构造最小位运算数组 II 是一样的,请看 我的题解。
数学
解法:数学
考虑 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方法,直接查看UIManagerBinding的get方法:
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 源码集集成。
其流程可以分为五个阶段:
-
设置输出目录和 JS 根目录
-
创建 Schema 生成任务
-
创建代码生成任务
-
集成到 Android 构建系统
-
挂载到构建生命周期
这里概括一下整个流程:
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 编译生成的代码
这里的两个关键任务分别是generateCodegenSchemaFromJavaScript和generateCodegenArtifactsFromSchema。前者从 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 代码。需要注意一下,这里libraryName和codegenJavaPackageName的取值,代码中是通过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.generate和generateComponentDescriptorCpp.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的取值需要弄清楚。实际上这里的componentDefinitions和className都来自于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类中并未实现Handle和Name方法,继续查看父类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 ¶ms) {
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 浏览器。
![]()