a
React 简介
我们现在将开始入门的可能是本课程最重要的主题,即React库。让我们从制作一个简单的React应用开始,同时了解React的核心概念。
到目前为止,最简单的方法是使用一个叫做Vite的工具来开始。
让我们用create-vite创建一个新的应用:
npm create vite@latest
让我们按照下列方式回答create-vite提出的问题:

我们现在创建了一个名为part1的应用。如果我们在回答问题“Install with npm and start now?”时选“Yes”的话,create-vite还会自动安装需要的依赖并启动应用。但是我们打算手动执行这些步骤,这样我们可以看看他们是怎么做的。
接下来,让我们进入应用的目录并安装需要的库:
cd part1
npm install
该应用的打开方式如下
npm run dev
控制台显示该应用已在本地主机的5173端口运行,地址为http://localhost:5173/:

Vite默认在端口5173启动应用。如果这个端口被占用,Vite使用下一个空闲端口号。
打开浏览器和文本编辑器,这样你就能在屏幕上同时看到代码和网页。

应用的代码位于src文件夹中。让我们简化默认代码,使文件main.jsx的内容如下所示:
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
而文件App.jsx看起来是这样的:
const App = () => {
return (
<div>
<p>Hello world</p>
</div>
)
}
export default App
文件App.css、index.css和目录assets可以删除,因为我们现在的应用并不需要它们。
组件
文件App.jsx现在定义了一个名为App的React组件。文件main.jsx的最后一行命令
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
将其内容渲染到div-元素中,该元素在文件public/index.html中定义,其id值为'root'。
默认情况下,文件index.html不包含任何我们在浏览器中可见的HTML标记:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>part1</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
你可以试着在该文件中添加一些HTML。但当使用React时,所有需要渲染的内容通常被定义为React组件。
让我们仔细看一下定义组件的代码:
const App = () => (
<div>
<p>Hello world</p>
</div>
)
正如你可能猜到的,这个组件将被渲染成一个包裹着p-标签的div-标签,而p-标签包含文本Hello world。
从技术角度来说,该组件被定义为一个JavaScript函数。下面是一个函数(它不接收任何参数):
() => (
<div>
<p>Hello world</p>
</div>
)
然后这个函数被赋值给一个常量App。
const App = ...
在JavaScript中定义函数有多种方法。这里我们将使用箭头函数,它是在较新的JavaScript版本ECMAScript 6,又称ES6中引入的。
因为函数只由一个表达式组成,所以我们使用了简写来表示这一段代码:
const App = () => {
return (
<div>
<p>Hello world</p>
</div>
)
}
换句话说,该函数返回表达式的值。
定义该组件的函数可以包含任何种类的JavaScript代码。把你的组件修改成:
const App = () => {
console.log('Hello from component')
return (
<div>
<p>Hello world</p>
</div>
)
}
export default App
然后观察控制台中发生了什么

前端开发的第一规矩:
始终打开控制台
让我们一起重复一遍:我保证在课程中,以及在我接下来的人生中,在开发网页时始终打开控制台。
也可以在一个组件内渲染动态内容。
将组件修改成:
const App = () => {
const now = new Date()
const a = 10
const b = 20
return (
<div>
<p>Hello world, it is {now.toString()}</p>
<p>
{a} plus {b} is {a + b}
</p>
</div>
)
}
大括号内的任何JavaScript代码都会被计算,计算的结果会被嵌入到组件产生的HTML中的定义位置。
注意不要丢掉组件底下的这行代码
export default App
export语句在教材的大多数示例中都被省略了。但如果没有export语句,组件和整个应用都会无法运行。
还记得之前保证过始终打开控制台吗?去掉这一行后,控制台打印出了什么?
JSX
看起来React组件返回的是HTML标记。然而,事实并非如此。React组件的布局大多是用JSX编写的。虽然JSX看起来像HTML,但我们实际上是在写JavaScript。在底层,由React组件返回的JSX会被编译成JavaScript。
编译后,我们的应用如下所示:
const App = () => {
const now = new Date()
const a = 10
const b = 20
return React.createElement(
'div',
null,
React.createElement(
'p', null, 'Hello world, it is ', now.toString()
),
React.createElement(
'p', null, a, ' plus ', b, ' is ', a + b
)
)
}
编译是由Babel处理的。用Vite创建的项目会自动编译。我们将在本课程的第7章节中学习更多关于这个主题的内容。
也可以把React写成“纯JavaScript”而不使用JSX。虽然没有正常人会这样做的。
实际上,JSX很像HTML,区别在于通过JSX,你可以在大括号内编写适当的JavaScript来轻松嵌入动态内容。JSX的理念与许多模板语言非常相似,例如和Java Spring一起在服务端使用的Thymeleaf。
JSX是“类XML”语言,这意味着每个标签都需要关闭。例如,换行是一个空元素,在HTML中可以这样写:
<br>
但在编写JSX时,标签需要关闭:
<br />
多个组件
让我们修改App.js文件如下:
const Hello = () => { return ( <div> <p>Hello world</p> </div> )}
const App = () => {
return (
<div>
<h1>Greetings</h1>
<Hello /> </div>
)
}
我们定义了一个新的组件Hello,并把它用在了App组件里。当然,一个组件可以多次使用:
const App = () => {
return (
<div>
<h1>Greetings</h1>
<Hello />
<Hello /> <Hello /> </div>
)
}
注意:在这些示例以及将来的示例中,底部的export部分被省略。但它仍然是代码正常运行所必须的
用React编写组件是很容易的,通过组合组件,即使是比较复杂的应用也可以保持相当的可维护性。事实上,React的一大核心理念就是将应用由许多专门的可重复使用的组件组成。
另一个强制的惯例是在应用的组件树的顶端有一个叫做App的根组件。然而,我们将在第6章节中讲到,有些情况下,App实际上并不是根组件,而是被包裹在一个适当的实用组件中。
props:向组件传递数据
可以使用所谓的props向组件传递数据。
让我们将Hello组件修改如下:
const Hello = (props) => { return (
<div>
<p>Hello {props.name}</p> </div>
)
}
现在定义组件的函数有一个参数props。作为一个参数,该参数接收一个对象,其字段对应于组件使用者定义的所有“props”。
props的定义如下:
const App = () => {
return (
<div>
<h1>Greetings</h1>
<Hello name="George" /> <Hello name="Daisy" /> </div>
)
}
props的数量可以是任意的,它们的值可以是“硬编码”的字符串或JavaScript表达式的结果。如果props的值是通过JavaScript得到的,props必须包裹在大括号中。
让我们修改代码,让Hello组件使用两个props:
const Hello = (props) => {
console.log(props) return (
<div>
<p>
Hello {props.name}, you are {props.age} years old </p>
</div>
)
}
const App = () => {
const name = 'Peter' const age = 10
return (
<div>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} /> <Hello name={name} age={age} /> </div>
)
}
App组件发送的props有变量的值、表达式的计算结果和普通的字符串。
Hello组件还会将对象props的值打印到控制台上。
我真心希望你的控制台是开着的。否则,记住之前保证过的:
我保证在课程中,以及在我接下来的人生中,在开发网页时始终打开控制台
软件开发并非易事。如果不利用所有可用的工具,比如网页控制台和console.log打印的调试信息,那就更难了。专业人士始终都用这两样工具。对初学者来说,没有任何理由不用这些美妙的工具来让生活更轻松。
可能遇见的错误信息
如果你的项目装的React版本是18或更早的,你可能会在此时遇到这样的报错信息:

这实际上并不是个错误,只是工具ESLint产生的警告。你可以在文件eslint.config.js中添加下面这一行来关闭react/prop-types警告
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react/prop-types': 0, },
},
]
我们将在第3章节详细介绍到ESLint。
一些注意事项
React已经能生成相当清晰的错误信息。尽管如此,你仍应该以非常小的步骤前进,来确保每一个改变都能如愿以偿,至少在初学的时候如此。
控制台应始终打开。如果浏览器报告错误,不建议继续写更多的代码,寄希望有奇迹出现。相反,你应该试着理解错误的原因,然后比如说回到之前的工作状态。

我们之前提到过,在编写React时,在代码中写console.log()命令(打印到控制台)是可行的,也是值得的。
还要记住,React组件名称的首字母必须大写。如果你尝试这样定义一个组件:
const footer = () => {
return (
<div>
greeting app created by <a href="https://github.com/mluukkai">mluukkai</a>
</div>
)
}
然后这样使用它
const App = () => {
return (
<div>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<footer /> </div>
)
}
页面不会显示在Footer组件中定义的内容,相反,React只会创建一个空的footer元素,即HTML内置的元素,而不是同名的自定义React元素。如果你把组件名称的第一个字母改为大写字母,那么React就会创建一个定义在Footer组件中的div-元素,并在页面上渲染。
注意,React组件的内容(通常)需要包含一个根元素。比如如果我们试图定义没有最外层的div元素的App组件:
const App = () => {
return (
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<Footer />
)
}
结果一条错误信息。

使用根元素并不是唯一可行的选择。一个组件的数组也是一个有效的解决方案:
const App = () => {
return [
<h1>Greetings</h1>,
<Hello name="Maya" age={26 + 10} />,
<Footer />
]
}
然而,在定义应用的根元素时,这种做法并不特别明智,并且这样做使代码看起来有点难看。
由于根元素是强制规定的,我们在DOM树中有“额外的”div元素。我们可以通过使用Fragment来避免“额外的”div元素,即用一个空元素来包装组件要返回的元素:
const App = () => {
const name = 'Peter'
const age = 10
return (
<>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<Hello name={name} age={age} />
<Footer />
</>
)
}
现在编译成功了,由React生成的DOM也不再包含额外的div元素。
不要渲染对象
考虑一个将我们朋友的姓名和年龄打印到屏幕上的应用:
const App = () => {
const friends = [
{ name: 'Peter', age: 4 },
{ name: 'Maya', age: 10 },
]
return (
<div>
<p>{friends[0]}</p>
<p>{friends[1]}</p>
</div>
)
}
export default App
可是,屏幕上什么都没有。我在代码里找问题找了15分钟,但是我还是不知道哪里出了问题。
我终于想起来我们之前保证过的
我保证在课程中,以及在我接下来的人生中,在开发网页时始终打开控制台
控制台飘着红色的文字:

问题的核心在于对象不是有效的React子组件,也就是说应用尝试渲染对象但失败了。
代码尝试这样渲染一个朋友的信息
<p>{friends[0]}</p>
但出了问题,因为大括号中要渲染的东西是一个对象。
{ name: 'Peter', age: 4 }
在React里,在大括号中渲染的每个东西都必须是原始值,比如数字或字符串。
修好的代码如下:
const App = () => {
const friends = [
{ name: 'Peter', age: 4 },
{ name: 'Maya', age: 10 },
]
return (
<div>
<p>{friends[0].name} {friends[0].age}</p>
<p>{friends[1].name} {friends[1].age}</p>
</div>
)
}
export default App
现在朋友的名字分别在大括号中渲染
{friends[0].name}
年龄亦然
{friends[0].age}
在改正错误后,你应该按🚫来清除控制台的错误,然后刷新页面并确保没有显示新的错误信息。
还有一个小小的注意事项。React是支持渲染数组的,只要数组中的每个元素都能渲染(比如数字或字符串)。所以下面这个程序是能运行的,虽然结果可能不是我们想要的:
const App = () => {
const friends = [ 'Peter', 'Maya']
return (
<div>
<p>{friends}</p>
</div>
)
}
在这一章节中,还不需要用到表格的直接渲染,我们会在下一章节中提起。