一个 .jsx 文件从写出到在浏览器里跑起来,和 Vue 有什么不同
React 用 JSX 语法,不是 HTML,也不是标准 JS。浏览器完全不认识 <div> 这种写法出现在 JS 文件里——JSX 是 Babel 发明的语法糖,必须经过编译。
| React 文件 | 和 Vue 的区别 |
|---|---|
| .jsx / .tsx | Vue 用 .vue(三段式分开)。React 把 HTML、JS、CSS 混在一个 JS 文件里——JSX 写在 return() 里,CSS 用 import 引入 |
| JSX | Vue 的 <template> 有独立标签。React 直接在 JS 里写 <div>,靠 Babel 编译成 React.createElement() |
| CSS | Vue 用 <style scoped>。React 通常用 import './xxx.css'(全局)或 CSS Modules(style.className),或 styled-components(CSS-in-JS) |
class 是 JS 的保留字(ES6 的类)。JSX 本质是 JS,写 class="xxx" 会报语法错误。所以 React 要求用 className 代替。编译后最终还是生成 DOM 的 class 属性。
这是 React 和 Vue 最大的区别:Vue 用专用编译器(@vitejs/plugin-vue),React 用通用编译器 Babel(或 SWC/esbuild)。Babel 只做一件事——把 JSX 翻译成 React.createElement() 调用。
import React from 'react'(因为编译后调用 React.createElement)。React 17+ 改为自动引入 jsx() 运行时,你不再需要在文件顶部写 import React。
React 通常用 Webpack(CRA 脚手架)或 Vite。打包结果和 Vue 类似,但 chunk 拆分策略不同。
lazy() 等价于 Vue 的 () => import() 路由懒加载。但 React 必须包一层 <Suspense> 来处理加载中的状态(fallback)。Vue 不需要,路由自己处理。
这是 React 独有的概念。Vue 也有虚拟 DOM,但 React 16+ 重写了核心算法——Fiber 架构。
| React Fiber | Vue 响应式 | |
|---|---|---|
| 触发更新的方式 | setState/useState → 调度更新 → 重新 render | ref.value = xxx → Proxy 拦截 → 派发更新 |
| 虚拟 DOM diff | Fiber 树对比,可中断分片 | 静态提升 + Patch Flag,编译时已优化大部分 |
| 更新粒度 | 组件级(组件 re-render) | 属性级(精确知道哪个属性变了) |
| 性能 | 靠 Fiber 分片避免卡顿 | 靠编译时优化,不需要分片(量少) |
| F12 面板 | React 特有的 |
|---|---|
| Elements | 看到渲染后的 DOM。React 不会像 Vue 加 data-v-xxx 属性(除非你用了 CSS Modules 的 .module.css)。DOM 结构和你写的 JSX 几乎一致 |
| Console | React 的警告(key 缺失、useEffect 依赖)在这里。错误堆栈指向 .jsx 文件 |
| Sources | 能看到打包后的 main.[hash].js。有 SourceMap 时能看到 .jsx/.tsx 源码,直接打断点 |
| Network | 首屏加载 main.[hash].js + chunk.[hash].js。React.lazy 触发时会多出 chunk 请求 |
| React DevTools | 💡 额外装的浏览器插件!有专门的 Components 和 Profiler 面板——这是 Vue DevTools 的 React 版 |
| 对比维度 | Vue | React |
|---|---|---|
| 源码文件 | .vue(template + script + style 三段式) | .jsx / .tsx(全在 JS 里) |
| 编译器 | @vue/compiler-sfc(专用) | Babel + @babel/preset-react(通用) |
| 模板 → JS | <div> → _createVNode("div") | <div> → _jsx("div") |
| 编译优化 | 静态提升 + PatchFlag(编译时标记动态节点) | 不优化模板(JSX 太灵活,编译时无法分析) |
| 运行时性能 | 更新时只对比动态节点(量少) | 整个组件 re-render + Fiber diff(量大但可中断) |
| CSS 隔离 | <style scoped>(编译器加 data-v 属性) | CSS Modules(.module.css)或 styled-components |
| SourceMap | .vue → .js 映射 | .jsx → .js 映射 |
| 打包结果 | dist/assets/index-[hash].js + CSS | dist/static/js/main.[hash].js + CSS(结构几乎一样) |
React 16 及之前,JSX 编译后调用 React.createElement(),所以必须 import React。React 17+ 改为编译后调用 react/jsx-runtime 里的 jsx() 函数,Babel 会自动引入,你的代码里不再需要手动 import React。
但如果你用了 React.Fragment、React.memo 等 API,还是需要按需 import。
纯运行时。Hooks 是 React 内部维护的一个链表结构——每次组件 render 时按顺序调用 useState/useEffect,React 按调用顺序把 hook 存在 Fiber 节点上。编译器不参与。
这就是为什么 Hooks 不能放在 if/for 里——调用顺序变了,React 就对不上了。
因为 React.lazy(() => import()) 和 import() 动态导入会让 Webpack/Vite 把这些代码拆成单独 chunk。你有多少个 lazy 组件,就有多少个 chunk 文件。用户访问对应路由时才下载。
可以用 webpack-bundle-analyzer 或 Vite 的 rollup-plugin-visualizer 可视化各 chunk 的内容。
可以!npm create vite@latest my-app -- --template react 就行。Vite 开发时用 esbuild 编译 JSX(比 Babel 快 10-100 倍),生产构建用 Rollup 打包。CRA(create-react-app)已停止维护,现在 React 官方推荐用 Vite 或 Next.js。