跳到内容

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

默认浏览器应该自动启动。立即打开浏览器的控制台。同时打开一个文本编辑器,这样你就可以在屏幕上同时查看代码和网页。

fullstack content

应用的代码位于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 = () => (
  <div>
    <p>Hello world</p>
  </div>
)

export default App

文件App.css, App.test.js, index.css, logo.svg, setupTests.jsreportWebVitals.js 可以删除,因为它们现在在我们的应用中并不需要。

如果你最后出现了以下错误

fullstack content

可能由于某种原因使用了比当前18版本更早的React版本。

修复方法是修改index.js,如下所示

import ReactDOM from "react-dom"
import App from "./App"

ReactDOM.render(<App />, document.getElementById("root"))

你很可能需要为你的其他项目做相同的事情。

关于版本差异的更多信息,请参见这里

Component

文件App.js现在定义了一个名为AppReact组件。在文件index.js的最后一行的命令:

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已经能生成相当清晰的错误信息。尽管如此,至少在开始的时候,你应该以非常小的步骤前进,并确保每一个改变都能如愿以偿。

控制台应始终打开。如果浏览器报告错误,不建议继续写更多的代码,寄希望有奇迹出现。相反,你应该试着理解错误的原因,比如说,回到之前的工作状态。

fullstack content

请记住,在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 />
  )
}

结果是返回一个错误信息。

fullstack content

使用根元素并不是唯一可行的选择。一个组件的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元素。