Skip to content
This page has been auto-translated and may contain errors.View in English

建立 React 项目

React 应用需要一些工具配置才能运行代码。需要有东西把 JSX 转换成浏览器能执行的 JavaScript,还要有东西在你开发时提供这些代码。Vite 做这两件事,大约三十秒内就能搭建一个可用的项目。这章会带你从一个空文件夹走到一个运行的应用,然后追踪最重要的流程:一个组件是如何最终显示在屏幕上的。

创建项目

Vite 是一个内置项目生成器的构建工具。用 npm create 运行它,回答两个提示,你就有了一个项目:

bash
npm create vite@latest my-react-app
cd my-react-app
npm install
npm run dev

第一个命令会问你要用哪个框架(选择 React)和哪个变种(选择 JavaScript,或者如果你用的话选择 TypeScript)。它会生成一个叫 my-react-app 的文件夹,里面有 React 项目需要的一切。npm install 下载 package.json 中列出的依赖,React 本身就来自这里。npm run dev 启动开发服务器并打印本地地址,通常是 http://localhost:5173。在浏览器中打开这个地址,启动应用就在运行了。

生成的文件夹有一些早期重要的文件:

  • index.html:浏览器加载的单个 HTML 页面。
  • src/main.jsx:入口点,React 在这里附加到页面上。
  • src/App.jsx:你的顶层组件,也是你最先编辑的文件。
  • src/assets/:你导入到组件中的图片和其他静态文件。
  • package.json:依赖和 devbuildpreview 脚本。
  • vite.config.js:构建配置,你可以长期不用管它。

各部分是如何连接的

三个文件以直线方式相互交接。从 index.html 开始,它故意几乎是空的:

html
<body>
  <div id="root"></div>
  <!-- 后面跟着一个 <script type="module" src="/src/main.jsx"> 标签 -->
</body>

那个空的 <div id="root"> 是页面上 React 允许填充的位置。你的应用渲染的所有东西都放在它里面。下面的 module <script> 标签加载 main.jsx,这是 React 接手的地方:

jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
)

从右往左读这个调用。document.getElementById('root') 找到空 div。createRoot 把它包装在 React 根中,意思是 React 现在拥有那个元素的内容,并且会让它与你的组件保持同步。.render(...) 告诉它放什么在那里:App 组件。

<StrictMode> 是一个仅在开发中的包装器,对它里面的所有东西运行额外检查,并对可能导致 bug 的模式发出警告。它不给页面添加任何东西,在生产构建中会关闭。它最明显的效果出现在 Effects 中,它故意在开发中挂载每个组件两次。

App.jsx 是一个导出自己的普通组件:

jsx
export default function App() {
  return <h1>Hello from React</h1>
}

这就是整个流程。浏览器加载 index.html,脚本加载 main.jsxmain.jsx 在根 div 上调用 createRoot 并渲染 <App />App 返回成为你看到的标题的 JSX。从现在开始你写的每个组件都坐在 App 内部的某个地方,所以它通过同样的路径到达页面。main.jsx 通常只写一次,很少再被改动;几乎所有你的工作都发生在 App.jsx 和它引入的组件中。

版本说明

React 17 及更早版本使用 ReactDOM.render(<App />, document.getElementById('root')) 作为入口点。React 18 引入了 createRoot,它打开了并发渲染,并让旧的调用继续工作,但带着弃用警告。React 19 删除了它,所以 ReactDOM.render 现在会抛出错误。在仍然使用它的教程中,createRoot 是应该写在它位置上的行。详见 历史和版本 了解什么改变了。

开发服务器

npm run dev 启动 Vite 的开发服务器,在你开发时它会一直在终端运行。它的工作是为你的应用提供服务并对你的编辑做出反应。

保存对 App.jsx 的更改,浏览器会几乎立即更新,不需要手动刷新。这是热模块替换:Vite 把改变的模块推送到页面并交换到位,让应用的其余部分继续运行。一个你点到七的计数器在编辑后通常仍然读七,所以你在调整周围的标记时保留已经设置的任何状态。有些更改仍然会强制完整重新加载,Vite 会为你决定。

项目还有另外两个脚本。npm run builddist 文件夹中产生一个优化的包,这是你部署的东西。npm run preview 本地提供那个构建的输出,所以你可以在发货前检查它。在开发期间,npm run dev 是你唯一需要的。

导入静态资源

图片放在 src/assets 下,通过 import 进入组件:

jsx
import logo from './assets/logo.png'

export default function Header() {
  return <img src={logo} alt="Company logo" />
}

导入给你一个变量,它持有那个图片的最终 URL,然后你在 src 中用花括号把它传递进去。把 src="./assets/logo.png" 写成纯字符串往往会出问题,因为你的源文件夹中的路径很少是构建输出中的路径。

导入文件而不是这样做会让构建工具参与进来。它看到你的代码依赖 logo.png,把它复制到输出中,并把那个文件最终在的 URL 给你回来。把你的组件移到不同的文件夹,相对导入仍然能解析。拼错文件名,你会立即发现,因为导入失败了,而不是在生产中发现一个破碎的图片。同样的模式适用于 SVG、字体和任何构建工具知道如何处理的其他文件。

浏览器不知道什么是 .jsx 文件。任何 JavaScript 引擎中都没有 JSX 解析器,<App /> 在纯 JavaScript 中是语法错误。浏览器接收的已经被转换过了。Vite 通过 esbuild 运行每个 .jsx 文件,它把 JSX 改写成函数调用:在 React 19 的自动运行时下,<App /> 变成类似 _jsx(App, {}) 的调用。你得到哪个辅助函数取决于模式。生产转换从 react/jsx-runtime 导入 jsx,而开发转换从 react/jsx-dev-runtime 导入 jsxDEV 并传递额外参数,带着源文件和行号,这就是你控制台中的 React 警告如何能指向你代码中的确切位置。.jsx 扩展主要是对工具的信号,说这个文件包含需要那种转换的语法。到任何东西到达页面时,它是普通的 JavaScript 模块调用普通的函数。

开发服务器和生产构建到那个结果的路由不同。在开发中,Vite 提供原生 ES 模块并在请求时转换每个文件,所以启动时间几乎不随项目大小增长,单个编辑只是使那一个模块重新失效。npm run build 切换到 Rollup,它打包整个图形,摇树删除未使用的导出,压缩,并把代码分割成块。两者之间的行为略有不同,这就是 npm run preview 存在的原因。

图片导入工作是因为打包工具也把非 JavaScript 文件作为模块图的一部分。import logo from './assets/logo.png' 不是一个真正的 JavaScript 模块导入。Vite 拦截它,把文件发出到 dist/assets,名字里有内容哈希,并用那个最终路径的字符串字面量替换导入,类似 /assets/logo-4f2a1c8b.png。哈希是让积极缓存变得安全的原因:改变图片,文件名就跟着改变,所以没有陈旧副本在 CDN 中存活。低于大小阈值的文件(默认 4KB)完全跳过单独的请求,作为 base64 数据 URL 被内联。在开发中同样的导入解析为开发服务器提供的纯路径,这就是为什么你在 devtools 中看到的 URL 在 npm run dev 和真实构建之间不同。

Juno从页面到你的组件的一条路径 建立只需四个命令,然后你可以忘记它。值得牢记的是这条链:index.html 有一个 id 为 root 的空 div,main.jsxcreateRoot 抓住那个 div 并把 <App /> 渲染进去,App.jsx 是说出什么出现的组件。一旦你能追踪那条路径,你就确切知道你自己的代码在哪里接入。
Juno从页面到你的组件的一条路径npm create vite@latest,选择 React,安装,npm run dev。从那里是 index.htmlmain.jsxApp.jsx:入口文件在根 div 上调用一次 createRoot 并渲染你的顶层组件,然后你花剩下的时间在那条线下面。导入图片而不是硬编码路径,这样构建工具就为你解析并给它们指纹。
Juno从页面到你的组件的一条路径 存在的设置是因为 JSX 没有运行时:esbuild 把 <App /> 改写成运行时调用,构建中来自 react/jsx-runtimejsx 和开发中来自 react/jsx-dev-runtimejsxDEV,在任何东西到达浏览器之前,资源导入解析到打包工具发出的哈希 URL。开发通过 esbuild 运行未打包的 ES 模块,生产运行 Rollup,所以把 npm run preview 当作检查两者一致的方式。createRoot 是 React 18 入口点,它是什么把你的树选入并发渲染。

接下来:组件,你将用来填充那个 App.jsx 的构建块。