a
React 简介
我们现在将开始入门的可能是本课程最重要的主题,即React-库。让我们从制作一个简单的React应用开始,同时了解React的核心概念。
到目前为止,最简单的方法是使用一个叫做Vite的工具来开始。
让我们创建一个名为part1的应用,进入其目录并安装库。
# npm 6.x (outdated, but still used by some):
npm create vite@latest part1 --template react
# npm 7+, extra double-dash is needed:
npm create vite@latest part1 -- --template react
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 可以删除,因为它们现在在我们的应用中并不需要。
create-react-app
你也可以在课程中使用更老版本的工具 create-react-app 而不用Vite去设置应用。与Vite肉眼最大区别就是启动应用的文件的名字,是 index.js。
启动应用的方式也与CRA(create-react-app)不同,它启动命令是
npm start
区别与Vite的
npm run dev
Component
文件App.jsx现在定义了一个名为App的React组件。在文件main.jsx的最后一行的命令:
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
将其内容渲染到div-元素中,该元素在文件public/index.html中定义,其id值为'root'。
默认情况下,文件public/index.html不包含任何我们在浏览器中可见的HTML标记。你可以尝试在该文件中添加一些HTML。当使用React时,所有需要渲染的内容通常被定义为React组件。
让我们仔细看一下定义组件的代码。
const App = () => (
<div>
<p>Hello world</p>
</div>
)
正如你可能猜到的,这个组件将被渲染成一个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>
)
}
也可以在一个组件内渲染动态内容。
修改组件如下。
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中的定义位置。
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处理的。用create-react-app创建的项目被配置为自动编译。我们将在本课程的第7章节中学习更多关于这个主题的内容。
也可以把React写成 "纯JavaScript "而不使用JSX。不过,理智的人不会这么做的。
实际上,JSX很像HTML,区别在于使用JSX,你可以通过在大括号内编写适当的JavaScript来轻松嵌入动态内容。JSX的理念与许多模板语言非常相似,例如与Java Spring一起使用的Thymeleaf,它被用在服务器上。
JSX是"XML-like"语言,这意味着每个标签都需要被关闭。例如,换行是一个空元素,在HTML中可以写成如下。
<br>
但在编写JSX时,标签需要被关闭。
<br />
Multiple components
让我们修改文件App.js如下(注:在这些示例中,底部的export 部分被省略,现在和将来都是如此。但它仍然是代码正常工作所必须的)。
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>
)
}
用React编写组件是很容易的,通过组合组件,即使是比较复杂的应用也可以保持相当的可维护性。事实上,React的一个核心理念是由许多专门的可重复使用的组件组成应用。
另一个强制的惯例是在应用的组件树的顶端有一个叫做App的根组件。然而,正如我们将在第6章中了解到的,有些情况下,组件App并不完全是根,而是被包裹在一个适当的实用组件中。
props: passing data to components
可以使用所谓的props向组件传递数据。
让我们对组件Hello做如下修改
const Hello = (props) => { return (
<div>
<p>Hello {props.name}</p> </div>
)
}
现在定义组件的函数有一个参数props。作为一个参数,该参数接收一个对象,该对象有对应于组件用户定义的所有 "props "的字段。
这些prop的定义如下。
const App = () => {
return (
<div>
<h1>Greetings</h1>
<Hello name="George" /> <Hello name="Daisy" /> </div>
)
}
可以有任意数量的prop,它们的值可以是 "硬编码 "的字符串或JavaScript表达式的结果。如果prop的值是用JavaScript实现的,它必须用大括号来包裹。
让我们修改代码,让组件Hello使用两个props。
const Hello = (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是变量的值、表达式的计算结果和一个常规字符串。
Some notes
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组件的内容(通常)需要包含一个根元素。例如,如果我们试图定义组件App而不使用最外层的div元素。
const App = () => {
return (
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<Footer />
)
}
结果是返回一个错误信息。
使用根元素并不是唯一可行的选择。一个组件的array也是一个有效的解决方案。
const App = () => {
return [
<h1>Greetings</h1>,
<Hello name="Maya" age={26 + 10} />,
<Footer />
]
}
然而,定义应用的根元素时,不是一个特别明智的做法,它使代码看起来有点难看。
由于根元素被强制规定了,我们在DOM树中有 "额外的 "div-elements。这可以通过使用fragments来避免,即用一个空元素来包装组件要返回的元素。
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元素。