React 入门

本文会引导我们进入一段 React 学习之旅。我们将逐步了解有关它的背景和用例的一些细节,在自己的电脑上建起基本的 React 工具链,创建并使用一个简单的入门应用程序,以学习一些关于 React 在此过程中如何工作的知识。

先决条件:

熟悉核心 HTMLCSS 和 JavaScript 语言,了解终端/命令行

React 使用称为 JSX(JavaScript 和 XML)的 HTML-in-JavaScript 语法。熟悉 HTML 和 JavaScript 可以帮助您学习 JSX,并更好地确定应用程序中的错误是与 JavaScript 还是与 React 的更特定领域相关。

目的: 要设置本地 React 开发环境,创建一个启动应用程序,并了解其工作原理

你好 React

如其官方口号所示,React 是一个用于构建用户界面的库。React 不是一个框架 —— 它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,React Native 可用于构建移动应用程序;React 360 可用于构建虚拟现实应用程序……

为了构建 Web 应用,开发人员将 React 与 ReactDOM 结合使用React 和 ReactDOM 通常被与其他真正的 Web 开发框架相提并论,并用于解决相同的问题。当我们将 React 称为“框架”时,就是在进行口语化的理解

React 的主要目标是最大程度地减少开发人员构建 UI 时发生的错误。它通过使用组件——描述部分用户界面的、自包含的逻辑代码段——来实现此目的。这些组件可以组合在一起以创建完整的 UI,React 将许多渲染工作进行抽象化,使您可以专注于 UI 设计(译者注:显而易见,此设计不等于视觉稿的设计)。

用例

与本模块中涵盖的其他框架不同,React 不会对代码约定或文件组织实施严格的规则。这使团队可以设置最适合自己的约定,并以他们希望的任何方式采用 React。React 可以处理一个按钮,一个界面的几个部分或应用程序的整个用户界面。

尽管 React 可以用于界面的小片段中,但要和 jQuery 这样的库,甚至是像 Vue 这样的框架那样“引入”应用程序并不容易 —— 当你使用 React 构建整个应用程序时更容易上手

另外,许多开发人员的经验对于 React 应用程序也是有用处的,例如使用 JSX 编写界面是需要编译过程的。在网站上添加类似于 Babel 的编译器会让网站上代码的运行速度变慢,因此开发人员通常会在构建项目的时候设置这样的工具。React 对于工具的要求可以说是很高的,但这是能够学习解决的。

本文将重点介绍使用 React 通过 Facebook 的 create-react-app 内的工具渲染应用程序中整个用户界面的用例。

React 如何使用 JavaScript?

React 中的许多模式都使用了现代 JavaScript 的功能。React 与 JavaScript 的最大区别在于 JSX 语法的使用上。JSX 是在 JavaScript 语法上的拓展,因此类似于 HTML 的代码可以和 JSX 共存。例如:

const heading = <h1>Mozilla Developer Network</h1>;

该 heading 常量称为 JSX 表达式。React 可以使用它在我们的应用程序中渲染 <h1> 标签。

假设出于语义原因,我们想将 heading 包装 <header> 在标记中?JSX 方法允许我们将元素彼此嵌套,就像使用 HTML 一样:

const header = (
  <header>
    <h1>Mozilla Developer Network</h1>
  </header>
);

注意:上一个代码段中的括号并非 JSX 的一部分,它对您的应用程序没有任何影响,括号只是用来向您(和您的计算机)表明其中的多行代码属于同一个表达式(译者注:原文表述实在有点啰嗦)。因此上面的代码等同于:

const header = <header>
    <h1>Mozilla Developer Network</h1>
</header>

这看起来多少有点不适感,因为表达式前面的 <header> 标记没有缩进与其对应的结束标记相同的位置。

浏览器是无法读取直接解析 JSX 的。我们的 header 表达式经过( Babel 或 Parcel 之类的工具)编译之后是这样的:

const header = React.createElement("header", null,
  React.createElement("h1", null, "Mozilla Developer Network")
);

可以跳过编译步骤,并使用 React.createElement() 自己编写 UI。但是,这样做会失去 JSX 的声明性优势,并且代码变得更难以阅读。编译是开发过程中的一个额外步骤,但是 React 社区中的许多开发人员都认为 JSX 的可读性值得。另外,流行的工具使 JSX-to-JavaScript 编译成为其设置过程的一部分。除非您愿意,否则不必自己配置编译。

由于 JSX 是 HTML 和 JavaScript 的结合,因此一些开发人员认为它很直观。其他人则说它的混合特性使它变得混乱。但是,一旦熟悉了它,它将使您能够更快,更直观地构建用户界面,并使其他人一眼就能更好地理解您的代码库。

要阅读有关 JSX 的更多信息,请查看 React 团队的 JSX In Depth 文章。

设置您的第一个 React 应用

有很多使用 React 的方法,但是我们将使用命令行界面(CLI)工具 create-react-app,如前所述,该方法通过安装一些软件包并创建一些软件包来加快开发 React 应用程序的过程。文件供您处理上述工具。

通过将一些 <script> 元素复制到 HTML 文件中,可以在没有 create-react-app 的情况下将 React 添加到网站,但是 create-react-app CLI 是 React 应用程序的常见起点。使用它可以让您花费更多的时间来构建应用,而花更少的时间进行设置。

要求

为了使用 create-react-app,您需要安装 Node.js。建议您使用长期支持(LTS)版本。Node 包括 npm(Node 程序包管理器)和 npx(Node 程序包运行器)

您也可以使用 Yarn 软件包管理器作为替代方案,但是我们假设在这套教程中使用 npm。有关 npm 和 yarn 的更多信息,请参见程序包管理基础 (en-US)

如果您使用的是 Windows,则需要安装一些软件以与 Unix/macOS 终端保持同等地位,才能使用本教程中提到的终端命令。Gitbash(作为 git Windows 工具集的一部分提供)或适用于 Linux 的 Windows 子系统WSL)均适用。有关这些以及一般终端命令的更多信息,请参见命令行速成课程

还请记住,React 和 ReactDOM 生成的应用程序只能在相当现代的一组浏览器上运行 —— 通过某些 polyfill 可以使用 IE9+。在阅读这些教程时,建议您使用 Firefox,Safari 或 Chrome 等现代浏览器。

另外,有关更多信息,请参见以下内容:

初始化您的应用

create-react-app ,该命令接受一个参数:即你想给自己的应用所起的名字。create-react-app 将为此应用创建一个同名的文件夹,并在其中创建所需文件。在你打算放置你的应用程序的文件夹下打开你的命令终端,并键入命令:

npx create-react-app moz-todo-react

这句命令创建了一个名为 moz-todo-react 的文件夹, 并在此文件夹里做了如下工作:

  • 为你的应用程序安装了一些 npm 包;
  • 写入 react 应用启动所需要的脚本文件;
  • 创建一系列结构化的子文件夹和文件,奠定应用程序的基础架构;
  • 如果你的电脑上安装了 git 的话,顺便帮你把 git 仓库也建好。

注意:如果你的电脑上安装了 yarn 的话, create-react-app 会默认使用 yarn 而非 npm。如果你同时安装了 yarn 和 npm ,但你希望使用 npm 的话,在 create-react-app 的时候需要输入  --use-npm :

npx create-react-app moz-todo-react --use-npm

create-react-app 运行的时候会在终端上显示一些与其状态相关的信息,通常情况下无需为此担心。运行需要一点时间,在此期间你可以适度放松一下。

处理完成之后,你可以 cd 到 moz-todo-react 文件夹下,然后键入 npm start 命令并回车,先前由 create-react-app 创建的脚本会启动一个地服务 localhost:3000,并打开你的默认浏览器来访问这个服务。成功启动浏览器的话,你的浏览器上会显示如下画面:

Firefox MacOS的屏幕截图,打开到localhost:3000,显示了默认的create-react-app应用程序

应用结构

create-react-app 提供了开发React应用所需的工具。它的初始文件结构如下:

moz-todo-react
├── README.md
├── node_modules
├── package.json
├── package-lock.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js

目录 src 是我们花费时间最多的地方,因为它是我们React应用源码存放的目录。

目录 public 包含了开发应用时浏览器会读取的文件,其中最重要的就是 index.html。React将目录 src 中的代码嵌入这个文件,从而浏览器才能运行此文件。 index.html中的有些内容关乎create-react-app的运作,因此除非你知道自己在做什么样的修改,否则不建议编辑这个文件。当然,你可以修改index.html中的 <title> 元素的内容来表现此应用程序通俗易懂的名称。

目录 public 会在建立并部署此应用的时候更新。此教程不涉及部署,你可以参考 Deploying our app 这一篇教程。

文件 package.json 包含了Node.js/npm为了建立该应用程序所管理着的文件信息。这个文件不是React应用独有的。你无需理解这个文件也能看懂这篇教程。 不过,如果你想了解更多,你可以阅读 What is the file `package.json`? on NodeJS.org 和 Package management basics

探索第一个 React 组件 — <App/>

在React中,组件是组成应用程序的可重复利用的模块。组件可大可小,但它们都只有单一的、明确的功能。

打开 src/App.js,之前打开的页面也提示我们对这个文件进行编辑。这个文件包含了我们第一个组件 App,内容如下:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}
export default App;

文件 App.js 主要由三部分组成: 顶部的 import 语句, 中间的 App 组件,以及底部的 export 语句。大多数React组件都遵循这个模式。

import 语句

脚本开头的 import 语句允许在此脚本中使用其他文件中的代码,让我们更进一步地了解这些语句。

import React from 'react';
import logo from './logo.svg';
import './App.css';

第一句代码引入了 React 库,这是为了将代码中的 JSX 语句转为React.createElement(),所有的 React 模块都应该引入 React 模块,否则会抛错。

第二句代码引入了 './logo.svg'。注意文件路径以 ./ 开头、由 .svg 尾——表明这是一个本地文件,并且它不是 JavaScript 文件。

我们没有指定 React 模块的路径——表明它并非来自本地文件,而是在 package.json 文件中列为依赖项。在整个学习过程中,请务必留心这两种引入方式的不同之处。

第三行引入了我们的组件所需的 CSS 文件。与上面两句不同,这里没有将引入的内容赋给任何变量、也没有用到 from 指令。请注意这种特殊的语法并非原生 JS 的语法 —— 它源自前端资源打包工具 webpack,而 create-react-app 正是基于 webpack 配置而来的。

译者补充:webpack 可用于打包 JS 和非 JS 的内容(当然,非 JS 的内容需要一些插件或加载器来处理),但是 JavaScript 标准只有关于 JS 的内容,所以 webpack 社区使用这种特殊的 `import` 语句来声明对非 JS 内容的引用。

详情参见 webpack 官方和社区,截止目前(2020年下旬),webpack 仍是现代前端工作中必不可少的技能之一。

App 组件

After the imports, we have a function named App. Whereas most of the JavaScript community prefers camel-case names like helloWorld, React components use pascal-case variable names, like HelloWorld, to make it clear that a given JSX element is a React component, and not a regular HTML tag. If you were to rename the App function to app, your browser would show you an error.

让我们进一步看下App方法。

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

App方法返回一个JSX表达式,这个表达式定义了浏览器最终要渲染的DOM。

表达式中的元素就像以前写的HTML一样,都拥有属性。

Some elements in the expression have attributes, which are written just like in HTML, following a pattern of attribute="value". On line 3, the opening <div> tag has a className attribute. This the same as the class attribute in HTML, but because JSX is JavaScript, we can't use the word class – it's reserved, meaning JavaScript already uses it for a specific purpose and it would cause problems here in our code. A few other HTML attributes are written differently in JSX than they are in HTML too, for the same kind of reason. We'll cover them as we encounter them.

Take a moment to change the <p> tag on line 6 so that it reads "Hello, world!", then save your file. You'll notice that this change is immediately rendered in the development server running at http://localhost:3000 in your browser. Now delete the <a> tag and save; the "Learn React" link will be gone.

Your App component should now look like this:

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Hello, World!
        </p>
      </header>
    </div>
  );
}

Export statements

At the very bottom of the App.js file, the statement export default App makes our App component available to other modules.

Interrogating the index

Let’s open src/index.js, because that's where the App component is being used. This file is the entry point for our app, and it initially looks like this:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

As with App.js, the file starts by importing all the JS modules and other assets it needs to run. src/index.css holds global styles that are applied to our whole app. We can also see our App component imported here; it is made available for import thanks to the export statement at the bottom of App.js.

Line 7 calls React’s ReactDOM.render() function with two arguments:

  • The component we want to render, <App /> in this case.
  • The DOM element inside which we want the component to be rendered, in this case the element with an ID of root. If you look inside public/index.html, you'll see that this is a <div> element just inside the <body>.

All of this tells React that we want to render our React application with the App component as the root, or first component.

Note: In JSX, React components and HTML elements must have closing slashes. Writing just <App> or just <img> will cause an error.

Service workers are interesting pieces of code that help application performance and allow features of your web applications to work offline, but they’re not in scope for this article. You can delete line 5, as well as lines 9 through 12.

Your final index.js file should look like this:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

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

Variables and props

Next, we'll use a few of our JavaScript skills to get a bit more comfortable editing components and working with data in React. We'll talk about how variables are used inside JSX, and introduce props, which are a way of passing data into a component (which can then be accessed using variables).

Variables in JSX

Back in App.js, let’s focus on line 9:

<img src={logo} className="App-logo" alt="logo" />

Here, the <img /> tag's src attribute value is in curly braces. This is how JSX recognizes variables. React will see {logo}, know you are referring to the logo import on line 2 of our app, then retrieve the logo file and render it.

Let's try making a variable of our own. Before the return statement of App, add const subject = 'React';. Your App component should now look like this:

function App() {
  const subject = "React";
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Hello, World!
        </p>
      </header>
    </div>
  );
}

Change line 8 to use our subject variable instead of the word "world", like this:

function App() {
  const subject = "React";
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Hello, {subject}!
        </p>
      </header>
    </div>
  );
}

When you save, your browser should display "Hello, React!" instead of "Hello, world!"

Variables are convenient, but the one we've just set doesn’t make great use of React's features. That's where props come in.

Component props

A prop is any data passed into a React component. Props are written inside component calls, and use the same syntax as HTML attributes — prop="value". Let’s open index.js and give our <App/> call its first prop.

Add a prop of subject to the <App/> component call, with a value of Clarice. When you are done, your code should look something like this:

ReactDOM.render(<App subject="Clarice" />, document.getElementById('root'));

Back in App.js, let's revisit the App function itself, which reads like this (with the return statement shortened for brevity):

function App() {
  const subject = "React";
  return (
    // return statement
  );
}

Change the signature of the App function so that it accepts props as a parameter. Just like any other parameter, you can put props in a console.log() to read it out to your browser's console. Go ahead and do that after your subject constant but before the return statement, like so:

function App(props) {
  const subject = "React";
  console.log(props);
  return (
    // return statement
  );
}

Save your file and check your browser's JavaScript console. You should see something like this logged:

Object { subject: "Clarice" }

The object property subject corresponds to the subject prop we added to our <App /> component call, and the string Clarice corresponds to its value. Component props in React are always collected into objects in this fashion.

Now that subject is one of our props, let's utilize it in App.js. Change the subject constant so that, instead of defining it as the string React, you are reading the value of props.subject. You can also delete your console.log() if you want.

function App(props) {
  const subject = props.subject;
  return (
    // return statement
  );
}

When you save, the the app should now greet you with "Hello, Clarice!". If you return to index.js, edit the value of subject, and save, your text will change.

Summary

This brings us to the end of our initial look at React, including how to install it locally, creating a starter app, and how the basics work. In the next article we'll start building our first proper application — a todo list. Before we do that, however, let's recap some of the things we’ve learned.

In React:

  • Components can import modules they need, and must export themselves at the bottom of their files.
  • Component functions are named with PascalCase.
  • You can read JSX variables by putting them between curly braces, like {so}.
  • Some JSX attributes are different to HTML attributes, so that they don't conflict with JavaScript reserved words. For example, class in HTML translates to className in JSX. Note that multi-word attributes are camel-cased.
  • Props are written just like attributes inside component calls, and are passed into components.

In this module