a
React 简介
我们现在将开始入门的可能是本课程最重要的主题,即 React -库。让我们从制作一个简单的React应用开始,同时了解React的核心概念。
到目前为止,最简单的方法是使用一个叫做 create-react-app 的工具来开始。如果随Node一起安装的 npm 工具的版本号是 5.3 以上,那么在你的机器上安装 create-react-app 是可行的(但不是必须的)。
让我们创建一个名为 part1 的应用,并进入其目录。
npx create-react-app part1
cd part1
该应用的运行方式如下
npm start
默认情况下,该应用在本地主机的3000端口运行,地址为 http://localhost:3000 。
默认浏览器应该自动启动。 立即 打开浏览器的控制台。同时打开一个文本编辑器,这样你就可以在屏幕上同时查看代码和网页。
应用的代码位于 src 文件夹中。让我们简化默认代码,使文件 index.js 的内容如下所示:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
而文件 App.js 看起来是这样的:
const App = () => (
<p>Hello world</p>
</div>
export default App
文件 App.css , App.test.js , index.css , logo.svg , setupTests.js 和 reportWebVitals.js 可以删除,因为它们现在在我们的应用中并不需要。
如果你最后出现了以下错误
可能由于某种原因使用了比当前18版本更早的React版本。
修复方法是修改 index.js ,如下所示
import ReactDOM from "react-dom"
import App from "./App"
ReactDOM.render(<App />, document.getElementById("root"))
你很可能需要为你的其他项目做相同的事情。
关于版本差异的更多信息,请参见 这里 。
Component
文件 App.js 现在定义了一个名为 App 的 React组件 。在文件 index.js 的最后一行的命令:
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
将其内容渲染到 div -元素中,该元素在文件 public/index.html 中定义,其 id 值为'root'。
默认情况下,文件 public/index.html 不包含任何我们在浏览器中可见的HTML标记。你可以尝试在该文件中添加一些HTML。当使用React时,所有需要渲染的内容通常被定义为React组件。
让我们仔细看一下定义组件的代码。
const App = () => (
<p>Hello world</p>
</div>
正如你可能猜到的,这个组件将被渲染成一个div-标签,它包裹着一个p-标签,其中包含了文本Hello world。
从技术角度来说,该组件被定义为一个JavaScript函数。下面是一个函数(它不接收任何参数):
() => (
<p>Hello world</p>
</div>
然后这个函数被分配给一个常量变量App。
const App = ...
有几种方法可以在JavaScript中定义函数。这里我们将使用箭头函数,它在较新的JavaScript版本中被描述为ECMAScript 6,也称为ES6。
因为函数只由一个表达式组成,所以我们使用了一个简写,表示这一段代码。
const App = () => {
return (
<p>Hello world</p>
</div>
换句话说,该函数返回表达式的值。
定义该组件的函数可以包含任何种类的JavaScript代码。把你的组件修改成如下样子,观察控制台中发生了什么。
const App = () => {
console.log('Hello from component')
return (
<p>Hello world</p>
</div>
也可以在一个组件内渲染动态内容。
修改组件如下。
const App = () => {
const now = new Date()
const a = 10
const b = 20
return (
<p>Hello world, it is {now.toString()}</p>
{a} plus {b} is {a + b}
</div>
大括号内的任何JavaScript代码都会被计算,计算的结果会被嵌入到组件产生的HTML中的定义位置。
看起来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中可以写成如下。
但在编写JSX时,标签需要被关闭。
<br />
Multiple components
让我们修改文件App.js如下(注:在这些示例中,底部的export 部分被省略,现在和将来都是如此。但它仍然是代码正常工作所必须的)。
const Hello = () => { return ( <div> <p>Hello world</p> </div> )}
const App = () => {
return (
<h1>Greetings</h1>
<Hello /> </div>
我们定义了一个新的组件Hello,并在组件App中使用它。当然,一个组件可以被多次使用。
const App = () => {
return (
<h1>Greetings</h1>
<Hello />
<Hello /> <Hello /> </div>
用React编写组件是很容易的,通过组合组件,即使是比较复杂的应用也可以保持相当的可维护性。事实上,React的一个核心理念是由许多专门的可重复使用的组件组成应用。
另一个强制的惯例是在应用的组件树的顶端有一个叫做App的根组件。然而,正如我们将在第6章中了解到的,有些情况下,组件App并不完全是根,而是被包裹在一个适当的实用组件中。
props: passing data to components
可以使用所谓的props向组件传递数据。
让我们对组件Hello做如下修改
const Hello = (props) => { return (
<p>Hello {props.name}</p> </div>
现在定义组件的函数有一个参数props。作为一个参数,该参数接收一个对象,该对象有对应于组件用户定义的所有 "props "的字段。
这些prop的定义如下。
const App = () => {
return (
<h1>Greetings</h1>
<Hello name="George" /> <Hello name="Daisy" /> </div>
可以有任意数量的prop,它们的值可以是 "硬编码 "的字符串或JavaScript表达式的结果。如果prop的值是用JavaScript实现的,它必须用大括号来包裹。
让我们修改代码,让组件Hello使用两个props。
const Hello = (props) => {
return (
Hello {props.name}, you are {props.age} years old </p>
</div>
const App = () => {
const name = 'Peter' const age = 10
return (
<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 (
greeting app created by <a href="https://github.com/mluukkai">mluukkai</a>
</div>
并像这样使用它
const App = () => {
return (
<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元素。
练习1.1.-1.2.
练习通过GitHub和在提交应用中标记完成的练习来提交。
你可以将本课程的所有练习提交到同一个仓库,或者使用多个仓库。如果你提交不同章节的练习到同一个仓库,请使用合理的目录命名方案。
一个非常实用的提交仓库的文件结构如下。
part0
part1
courseinfo
unicafe
anecdotes
part2
phonebook
countries
请看这个实例提交库!
对于课程的每一章节都有一个目录,它进一步分支为包含一系列练习的目录,如第一章节的 "unicafe"。
对于每个 Web 应用的系列练习,建议提交所有与该应用有关的文件,除了目录node/modules。
练习是一次提交一个章节的。当你提交了课程中某一章节的练习,你就不能再提交同一章节的未完成的练习。
请注意,在这一章节,除了下面的练习,还有更多的练习。在你完成该章节的所有练习之前,请不要提交你的作品。
1.1:课程信息,第1步
我们将在本练习中开始处理的应用程序将在以下几个练习中得到进一步开发。 在本课程的这个和其他即将到来的练习集中,仅提交应用程序的最终状态就足够了。 如果你想,你也可以为本系列的每个练习创建一个提交,但这不是必要的。
使用create-react-app来初始化一个新的应用。修改index.js如下:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
和App.js,如下所示
const App = () => {
const course = 'Half Stack application development'
const part1 = 'Fundamentals of React'
const exercises1 = 10
const part2 = 'Using props to pass data'
const exercises2 = 7
const part3 = 'State of a component'
const exercises3 = 14
return (
<h1>{course}</h1>
{part1} {exercises1}
{part2} {exercises2}
{part3} {exercises3}
<p>Number of exercises {exercises1 + exercises2 + exercises3}</p>
</div>
export default App
并删除多余的文件(App.css, App.test.js, index.css, logo.svg, setupTests.js, reportWebVitals.js)。
整个应用都在同一个组件中。重构代码,使其由三个新的组件组成。Header、Content和Total。所有数据仍驻留在App组件中,它使用props将必要的数据传递给每个组件。Header负责显示课程的名称,Content显示各部分及其练习的数量,Total显示练习的总数量。
在文件App.js中定义新组件。
App组件的主体将大致如下:
const App = () => {
// const-definitions
return (
<Header course={course} />
<Content ... />
<Total ... />
</div>
警告 create-react-app会自动使项目成为git仓库,除非应用是在一个已经存在的仓库中创建的。很可能你不希望项目成为一个仓库,所以在项目根部运行rm -rf .git命令。
1.2: course information, step2
重构Content组件,使其本身不渲染任何部件的名称或其练习次数。相反,它只渲染三个Part组件,每个组件渲染一个部分的名称和练习的次数。
const Content = ... {
return (
<Part .../>
<Part .../>
<Part .../>