news 2026/10/17 16:19:28

React Native开发必知:JS、JSX、TS、TSX四者区别与选型指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React Native开发必知:JS、JSX、TS、TSX四者区别与选型指南

最近在React Native社区里,经常看到有人把js、jsx、ts、tsx混着用,也看到不少刚入门的朋友问“这四个到底有啥区别?”。我最早接触RN的时候也走过弯路:项目里既有.js又有.jsx,后来升级TypeScript又冒出来.ts和.tsx,当时被这些文件后缀整得有点晕。

这四种扩展名看着相似,实际背后是两条不同的轴线在起作用:一条是“有没有JSX语法”,一条是“有没有TypeScript类型系统”。搞懂这两条轴线,这四个后缀的关系就非常清晰了。今天这篇就从这两条轴线下手,结合React Native的实际开发场景,把每种文件的定位、用法、选型讲透,顺便分享一些我在真实项目里踩过并沉淀下来的坑。

1. 拆开看:JSX和TypeScript是两个维度的东西

先说结论:js、jsx、ts、tsx不是四个并列的概念,而是由两个独立维度组合出来的四种结果。

第一个维度是“是否使用JSX语法”,第二个维度是“是否使用TypeScript类型系统”。把这个两个维度正交组合,就得到了我们看到的四种文件后缀:

  • 不用JSX、不用TS:就是原始的JavaScript,后缀.js
  • 用JSX、不用TS:带JSX语法的JavaScript,后缀.jsx
  • 不用JSX、用TS:带类型系统的JavaScript超集,后缀.ts
  • 用JSX、用TS:两者都用,后缀.tsx

这个四象限关系可以用一张表格直接看明白:

是否使用JSX是否使用TypeScript文件后缀RN项目里的位置
否否.js极少数纯逻辑工具文件
是否.jsx早期RN组件文件
否是.ts类型定义、纯工具函数、接口定义
是是.tsx新项目里的主力组件文件

好,接下来挨个解释清楚每个维度到底是什么。

1.1 JSX是什么,为什么React和RN都在用

JSX是一种JavaScript的语法扩展,它允许你在JavaScript代码里直接写类似HTML的标签结构。比如:

const element = <Text style={styles.title}>Hello World</Text>;

这段代码里出现了<Text>这样的标签,这在纯JavaScript语法里是不合法的。但JSX把这段“像HTML又不是HTML”的结构编译成React.createElement调用,最终生成React元素。

用生活类比:JSX像是一种“模板化的JavaScript”。它让你在写逻辑的同时能“手写界面结构”,比纯JavaScript拼接字符串、堆对象直观得多。React Native里每个界面都是由View、Text、Image、ScrollView这些核心组件嵌套出来的,用JSX描述这些嵌套结构,代码读起来几乎和看HTML一样清晰。

关键点是:JSX不是一个独立的新语言,它只是JavaScript的一个超集编译阶段。Babel这类编译器会把JSX转换成标准JavaScript,这一步在React Native的打包链路里是自动完成的。

1.2 TypeScript是什么,它解决了什么问题

TypeScript(简称TS)是微软推出的JavaScript超集,核心贡献是给JavaScript加入了静态类型系统。所谓“静态类型”,简单理解就是变量和函数的输入输出参数,在代码写下来的时候是确定可见的,而不是等到运行期才暴露问题。

比如下面这段纯JavaScript代码:

function formatPrice(price) { return `¥${price.toFixed(2)}`; }

如果你不小心传入了一个字符串的"99",.toFixed方法就会在运行期直接抛错。而TypeScript版本:

function formatPrice(price: number): string { return `¥${price.toFixed(2)}`; }

编辑器会在你写代码的时候直接提示“你传错了类型”,这就把一大批运行期错误提前到了编辑期。

用生活类比:JavaScript就像没有菜单的餐厅,你只有等菜端上桌才知道点错了;TypeScript则是带图片、带配料表、带热量标注的菜单,上来前就能知道这道菜长什么样、含什么成分。

对于React Native这种跑在移动端的场景,TypeScript的价值更大——因为手机上没法像浏览器那样随时打开DevTools调试,编译期拦截错误能省掉大量真机排查时间。

1.3 为什么RN项目选型要看两条轴线

理解了JSX和TS之后,再看那四个后缀就非常清楚了。两条轴线分别解决两个完全不同的需求:

  • JSX解决的是“界面怎么描述”的问题
  • TypeScript解决的是“类型怎么约束”的问题

一个RN项目里,这两种需求往往是同时存在的:既要用JSX写界面,又想用TypeScript约束类型。于是就有了.tsx。

也有人问,既然.js也能写JSX(很多老项目就这么干的),为什么还要分.jsx?这在工具链层面有实际意义:.jsx扩展名能明确告诉Babel和编辑器“这个文件里可能有JSX语法”,从而启用对应的语法解析插件;而.tsx则同时告诉TypeScript编译器“这个文件里既可能有JSX又可能有TypeScript语法,需要组合解析”。

扩展名不是摆设,它是整个工具链识别文件内容的“身份证”。

2. 四种文件各管什么:React Native里的真实定位

四种扩展名对应四种不同的“角色分工”。理解了每个文件的典型使用场景,你写代码的时候自然会形成一种“该用谁就用谁”的条件反射。

2.1 纯js文件:React Native里的“遗留角色”

在当前的RN开发体系里,.js文件其实是比较尴尬的。新的RN官方模板已经从纯JavaScript切换到了TypeScript,微软和Meta官方都在推动TS成为默认选择。那么.js文件还有存在感吗?有,但基本都是下面这几种情况:

  • 老项目从早期RN版本带过来的存量代码
  • 一些第三方npm包本身用纯JS编写
  • 原生模块桥接层里偶尔出现的纯逻辑脚本
  • 配置文件,比如metro.config.js、babel.config.js这些

从迁移的角度看,纯.js文件的维护成本会越来越高。因为一旦你引入了TypeScript,.js文件就处于“类型游离状态”——它既不受类型检查约束,又无法为其他.ts/.tsx文件提供类型推导依据。

个人实测的经验是:老项目里的.js组件文件在迁移过程中是最容易出问题的。它们里头的props往往没有类型定义,一旦周围的.ts文件引用它们,TypeScript编译器会推断出一个宽泛的any类型,久而久之类型检查形同虚设。

所以我对RN新老项目的一个通用建议是:新写代码一律不要用.js,除非你是写metro.config.js这类不带业务逻辑的配置文件。

2.2 纯ts文件:RN项目的“逻辑层和类型层”

.ts文件在一流RN架构里承担的是“不涉及界面描述”的职责。它主要出现在这些地方:

  • 类型定义types.ts
  • 网络请求封装api.ts
  • 工具函数库utils.ts
  • 状态管理store(比如zustand或redux的reducer、selector)
  • 自定义Hook逻辑(如果这个Hook只处理数据、不返回JSX)

.ts是纯TypeScript,没有任何JSX标签。它的优势是类型系统完整、意图明确、可复用性强。比如你的项目里有一个UserInfo接口,定义在types.ts里,那么这个接口可以被很多.tsx组件引用,公共类型有了唯一出口,这就避免了每个组件各自写一遍松散的类型声明。

在RN项目里我基本遵循一个原则:不需要写界面结构的文件就用.ts,需要写界面结构的文件就用.tsx。这个原则不需要死记,写代码时本能反应就对了。因为一旦你在.ts文件里写了一个<View />标签,编辑器会直接报语法错误,反过来也一样——.tsx文件里如果没有任何JSX标签,TypeScript编译器也能正常解析,只是没什么必要。

2.3 纯jsx文件:RN项目里的“过渡产物”

.jsx文件是标准的“带JSX语法的JavaScript文件”,它在React Native早期(2015–2018年左右)是组件文件的主力。那时候的RN模板就是.js和.jsx混着用,没有TypeScript做类型检查。

放在今天的RN项目里,.jsx的处境有点像“夹生饭”:你说它能用吧,它确实能用;你说它现代吧,它又少了类型系统这层保护。所以我的判断是——新项目不要再新建.jsx文件,老项目里的.jsx文件在迁移时应该顺手转成.tsx。

可能有人会问:“我项目里已经有一堆.jsx了,能先放着不管吗?”短期看可以,长期看是负担。因为你在新代码里是.tsx,老代码里是.jsx,整个项目就存在两套智商体系。编辑器里老文件的代码提示永远是残缺的,因为你没写类型,它也没法自动推导出比any更具体的东西。

如果你是React Native初学者,建议直接绕开.jsx这个阶段,从.tsx开始写。JSX语法本身你需要学,TypeScript类型注释你也需要学,但没必要非要在.jsx这个带JSX但不带TS的“过渡形态”里花费时间。

2.4 tsx文件:React Native开发的当前主力

.tsx文件是目前RN开发里最主流、最推荐的文件格式。它同时具备JSX的界面描述能力和TypeScript的类型约束能力,一个文件里既能写组件标签,又能定义props类型、状态类型、返回类型。

举个实际例子,一个用.tsx写的RN按钮组件:

import React from 'react'; import { Pressable, Text, StyleSheet } from 'react-native'; type CustomButtonProps = { label: string; onPress: () => void; variant?: 'primary' | 'secondary'; disabled?: boolean; }; export default function CustomButton({ label, onPress, variant = 'primary', disabled = false }: CustomButtonProps) { return ( <Pressable style={[ styles.base, variant === 'primary' ? styles.primary : styles.secondary, disabled && styles.disabled ]} onPress={onPress} disabled={disabled} > <Text style={styles.label}>{label}</Text> </Pressable> ); } const styles = StyleSheet.create({ base: { paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, }, primary: { backgroundColor: '#2563EB', }, secondary: { backgroundColor: '#E5E7EB', }, disabled: { opacity: 0.5, }, label: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', textAlign: 'center', }, });

这里CustomButtonProps定义了组件的对外接口:label必须是字符串,onPress必须是函数,variant只能是两个枚举值之一。任何用这个组件的人把onPress传错成字符串、把variant写成枚举外的值,编辑器直接报红。这就是.tsx给React Native开发带来最大的直观价值:你写的组件变成一个“带说明书的API”,而不是一个对方用错了才炸的封装。

React Native官方模板目前也是基于TypeScript搭建的,如果你用react-native init创建新项目并选择TypeScript模板,生成的文件里组件部分基本全是.tsx。

3. 选型与实操:React Native项目要怎么做才是最优配置

说完四者的定位,落到实际项目里,桩桩件件都是配置细节。这一节我用“从一个空目录到可运行的RN项目”的视角,梳理一份最推荐的文件组织方案。

3.1 新项目:从项目模板开始就锁定三种扩展名

如果你用现代方式创建RN项目,npx @react-native-community/cli init MyApp --template react-native-template-typescript,生成的项目里几乎看不到.jsx文件,组件的默认扩展名是.tsx,逻辑文件是.ts,配置类文件是.js。

个人实测下来,这个模板组合是当前RN项目最舒服的配置,原因是:

  1. 组件统一.tsx,类型统一由.ts导出,全项目只有两套扩展名,认知负担最低
  2. 工具链(Babel、Metro、TypeScript编译器)全部默认支持这种组合,不需要额外折腾任何解析器配置
  3. 官方模板维护团队会持续跟进RN底层升级,你不用操心依赖版本匹配问题

在一个体积适中的RN项目里,规范文件职责后,目录结构通常长这样:

src/ ├── components/ │ ├── Button/index.tsx # 组件本体 │ ├── Button/types.ts # 组件对外接口类型 │ └── ProductCard/index.tsx ├── screens/ │ ├── HomeScreen/index.tsx │ └── ProfileScreen/index.tsx ├── services/ │ ├── api.ts # 网络请求封装 │ └── storage.ts ├── hooks/ │ └── useUserData.ts # 无JSX,纯Hook逻辑 ├── types/ │ └── models.ts # 业务模型类型定义 └── utils/ └── format.ts

你注意这个结构里,只有“带界面的东西”才会出现.tsx。而api.ts、storage.ts、format.ts、types.ts这些文件里没有任何一个<View>,所以它们用.ts就够了。

3.2 老项目迁移:从.jsx到.tsx的稳妥路线事

如果你手上是一个从早期RN版本留下来的老项目,里面全是.js和.jsx文件,直接一次性换成.tsx风险较高,特别是大项目。我建议分三步走:

第一步,先把jsconfig.json升级成tsconfig.json,让TypeScript编译器介入但不强制报错(strict: false),保证存量代码能编译。这一步相当于给老项目装了一个“类型检查的观察镜”,先看不改。

第二步,把纯逻辑文件(工具函数、API封装、常量定义)从.js改成.ts,因为它们不涉及JSX迁移,最安全。改完跑一遍全量编译,确认没有类型错误后再继续。

第三步,从叶子组件往上迁组件文件。先把不依赖其他组件的纯展示组件从.jsx改成.tsx,给props补上类型,再处理容器组件。最后再处理页面级组件。每一次迁移范围控制在最小,都跑一遍测试和编译。

老项目迁移过程中最常遇到的一个坑是:一个.jsx组件被很多地方引用,但作者没有写过任何props类型。你把它改成.tsx后,每个引用点都会爆出一堆类型错误。这里我的经验是:不要硬着头皮全改,先给这个组件写一个外部的类型定义(比如ComponentName.types.ts),然后逐个修正引用点。如果涉及外部依赖的props实在没有类型定义,可以先给一个合理的联合类型或Partial类型兜底,等后续慢慢收紧。

3.3 tsconfig配置要点:别让四类文件产生编译误差

保证.js、.jsx、.ts、.tsx四类文件能和谐共处,核心在tsconfig.json里几项配置:allowJs、checkJs、jsx、strict。

一个RN项目常用的tsconfig.json参考配置如下:

{ "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "isolatedModules": true, "jsx": "react-jsx", "lib": ["esnext"], "moduleResolution": "node", "noEmit": true, "strict": true, "target": "esnext" }, "extends": "@react-native/typescript-config/tsconfig.json" }

这里重点解释几个关键项:

  • allowJs: true允许TypeScript编译器处理.js文件,这样你老项目里的存量.js文件不会被编译器忽略,可以参与模块解析
  • jsx: "react-jsx"是React Native新版本的推荐配置,它让编译器自动转换JSX
  • noEmit: true只做类型检查不输出编译产物,因为RN项目真正的打包走的是Metro/Babel,TS编译仅用于类型校验

需要留意的是checkJs这个配置项。如果你设了checkJs: true,TypeScript会对.js文件也做类型检查,那老项目里的几百个.js文件全部会被类型错误淹没,别开。

3.4 为什么不是“无脑全上.tsx”

说完推荐配置,也说说反直觉的部分。很多人会觉得“那我是不是干脆所有文件都叫.tsx算了?”真不推荐,原因有二:

第一,.tsx文件里如果误写了一个带有泛型语法的表达式,会和JSX冲突。比如说你有个工具函数:

// 这个写法在.ts文件里合法 function identity<T>(value: T): T { return value; }

这个泛型箭头函数写法如果放在.tsx文件里,TypeScript编译器会把<T>理解为JSX的开始标签,直接报语法错误。对应的修复办法是给泛型参数加逗号尾缀或者改用函数表达式,但这些别扭写法的出现就是在提醒你:纯逻辑根本不该放.tsx里。

第二,.tsx文件越多,编译器每次类型检查时需要解析的JSX标签越多,编译速度会更慢。虽然现在机器性能很可观,但大项目里这一点点差异还是会积少成多的。

需求很纯粹:界面归.tsx,逻辑归.ts,配置归.js。

4. 常见报错与排查:这几种坑我从实际项目里都踩过

类型系统和JSX语法一起用,能拦截很多问题,但配置不当也会制造不少幺蛾子。下面几个是RN项目里最常见的,基本属于必踩坑系列。

4.1JSX element implicitly has type 'any'报错怎么办

这个报错出现频率极高,尤其是在老项目里刚把文件改成.tsx的时候。原因是一个组件从另一个.js文件导入,而这个导入源没有类型定义,TypeScript推导不出它的类型,最终整个组件实例被推断为any。

典型场景:

// ProductCard.tsx import ProductBadge from './ProductBadge'; // 这个是.jsx文件,没有类型

解决办法分为三种:

  1. 把被导入的.jsx文件顺带改成.tsx并补上类型定义,治本
  2. 暂时在目标文件顶部写一行// @ts-ignore,跳过这条检查,后续再修
  3. 在src目录下放一个declarations.d.ts文件,给无类型的模块声明兜底
// declarations.d.ts declare module '*.jsx' { const component: React.ComponentType<any>; export default component; }

我自己遇到这种情况时,优先执行方案1,如果因为工作量排期问题没法立刻改完,就用方案3做个全局兜底。用@ts-ignore是下策,因为它会掩盖同一文件里其他真实的类型问题。

4.2Cannot find module './xxx' or its corresponding type declarations报错

这种报错常见于从.js文件导入图片、音频等资源的时候。在纯JavaScript场景下没有类型检查概念,但.tsx里导入一张图片:

import bannerImage from './assets/banner.png';

如果没有declarations.d.ts里的资源类型声明,TypeScript会直接报“找不到模块”,但实际上编译是能通过的。解决方案是补一个全局资源声明:

declare module '*.png' { const value: number; // React Native里图片资源pad.png导入后是一个数字ID export default value; }

顺带提一个RN特性:React Native中import img from './a.png'得到的不是一个URL字符串,而是一个打包后的资源模块引用(通常是数字或对象),所以类型声明时不要照搬web项目的string写法。

4.3 运行正常但Editor里到处是红波浪线的“假阳性错误”

有一种情况非常让人迷惑:项目能正常编译、真机运行完全没问题,但编辑器里一堆红波浪线。大概率是tsconfig.json配置与Metro/Babel的解析规则不一致导致的。最常见的是路径别名配置不一致。

比如你项目用Babel配置了@/作为src目录的别名,但tsconfig.json里没同步配paths:

// Babel能解析 import { api } from '@/services/api'; // 但TypeScript不知道@是什么,于是报错

解决办法是让两边配置对齐:

{ "paths": { "@/*": ["src/*"] } }

这个问题的根源在于RN项目存在两套解析体系:打包走的是Metro和Babel,类型检查走的是tsc。两边配置不一致,就会出现“编译能过但编辑器报红”这种割裂状态。我在实际操作中的习惯是,每改一次babel.config.js或新增一个别名,都会同步检查tsconfig.json。

4.4 组件能运行但props完全没有类型提示

有一种更隐蔽的情况:你确认自己在写.tsx,也写了props类型,但编辑器在另一个文件引用该组件时,依然完全不提示props属性。这通常不是类型定义的问题,而是组件导出方式的问题。

常见写法:

// 反例:默认导出一个匿名组件,类型推导效果差 export default function ProductCard(props) { // props没有标注类型,编辑器无法推导 }

不规范的props会导致编辑器在你调用这个组件的时候只能提示“这个东西接受一个props”,但具体props里有什么字段一概推不出来。正确的写法是定义接口:

type ProductCardProps = { title: string; price: number; onPress: () => void; }; export default function ProductCard({ title, price, onPress }: ProductCardProps) { // ... }

约定优先于配置。只要项目里三分之二的组件都遵循“props先定义type再拆解”的写法,整体开发体验就能上去一个层级。

4.5 RN新架构模式下TypeScript的额外价值

最后补充一个贴近当前工程化的视角——React Native从新架构(New Architecture)逐步铺开之后,TypeScript的作用进一步变大了。新架构中组件和原生模块之间的接口定义涉及跨语言类型生成(即所谓的Codegen),而Codegen直接依托TypeScript类型定义来生成原生语言代码。这意味着.ts和.tsx里写下的类型不再只是“开发期约束”,它们还承担着自动生成桥接代码的职责。

这带来的直接后果是:如果你的业务接口类型定义写得乱,新架构的Codegen阶段就报错,越早用TS、类型定义越清晰,越早摆脱老式桥接手动维护的繁琐。从长期看,在RN生态里深耕的开发者很难绕过这个方向。

5. 一点个人经验

在RN项目里,文件后缀看起来是一件很小的“命名问题”,但它在项目进入维护期后,直接影响可读性、可维护性和团队协作效率。我自己写过纯js裸奔的老项目,也写过全量tsx的从零启动项目,对比下来最直观的感受是:类型系统在刚开始写的时候确实多花了一点时间,但到了开发中后期,几乎每天都能感受到它省下的时间和减少的崩溃排查。

给刚起步的RN开发者一个最实用的建议:新项目直接从.tsx开始,纯逻辑放.ts,配置项用.js。别回头去看.jsx时代的旧写法,也别迷信四种后缀有什么高深莫测的门道。这两条轴线想明白,后缀的选用完全是顺手的事。

再顺手分享一个我在团队里的约定:任何新建的组件文件,如果没有特殊原因,统一用.tsx;任何新建的纯函数文件,统一用.ts;任何提测Merge Request里如果出现.jsx文件,review时必须追问一句“为什么这里不迁成.tsx”。执行这一条约定几个月后,团队的项目里基本就看不到.jsx了,大家写代码时的类型提示也稳定在一个比较舒服的水平上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 16:19:23

H5幸运刮刮乐系统:免公众号、轻量级分佣抽奖方案

简介&#xff1a;这是一套开箱即用的H5幸运刮刮乐抽奖系统源码&#xff0c;面向中小商家、营销运营人员及PHP全栈开发者&#xff0c;解决微信生态内无需公众号资质即可快速部署抽奖活动的痛点&#xff0c;支持多级分佣与直运营模式。资源包共2013个文件&#xff0c;以518个JSON…

作者头像 李华
网站建设 2026/10/17 16:19:15

AI检测原理与降AI工具搭配:让英文论文更自然

这几年我帮不少硕士博士生看过英文论文&#xff0c;频率最高的一个诉求不是"帮我改改语法"&#xff0c;而是"能不能把Turnitin的AI检测标红降下去"。这个词被大家简化成了"降AI"&#xff0c;对应的工具也越来越卷&#xff0c;SpeedAI科研小助手就…

作者头像 李华
网站建设 2026/10/17 16:16:22

AI辅助教材写作如何把查重率从28%降到8%?低查重编写策略全解析

我最近在整理一本实操类教材&#xff0c;初稿交出去后被查重打回来&#xff0c;重复率28%。看到报告第一眼我是不服的——内容明明是我们团队一个个案例跑出来的&#xff0c;怎么会有这么多红字&#xff1f;后来把报告下载下来逐段看&#xff0c;才发现标红最多的是三块&#x…

作者头像 李华
网站建设 2026/10/17 16:14:33

实时换脸工具LF2.0部署调优实战:延迟、画质与稳定性平衡

前两天有个玩AI绘画的同事问我&#xff0c;换脸工具到底是不是那种“轻轻一点就能用”的玩具。我说看你怎么定义“能用”。我这边曾经拿 Live Face Swap2.0 的实时预览模式跟一台旧笔记本较劲了两天&#xff0c;才把“满屏漂移的橡皮脸”调成“能稳定播出十分钟不出错”的画面。…

作者头像 李华
网站建设 2026/10/17 16:13:54

Sora2.0视频生成技术实战:1分钟出片的创作管线与落地经验

做视频的人应该都有同感&#xff1a;最折磨人的从来不是拍摄本身&#xff0c;而是“脑子里已经想好的画面&#xff0c;最后却交不出一版能看的片子”。一个15秒的广告&#xff0c;从写脚本、找参考、凑素材&#xff0c;到粗剪、反复改字幕、调色调&#xff0c;三四个工作日搭进…

作者头像 李华