Snofly
7762 words
39 minutes
React 入门引导:理论概述与示例演练

前言#

译者述#

1、原文来自 Tania Rascia 个人网站上的一篇文章:React Tutorial: An Overview and Walkthrough

2、这篇文章受到 React 官网 的推荐,是一篇较易的入门介绍文章,发布于 2018 年 8 月 20 日。

3、翻译中会尽量遵照原意,也会加入译者的技术注释,以及选择更符合汉语文法的译句。


作者述#

我刚开始学习 JavaScript 的时候就听说 React 了,但只看了一眼,就觉得惊讶无比。因为我看见它把 HTML 代码和 JavaScript 代码混合在一起,而这不正是我们一直以来需要尽量去避免的吗?所以说,React 到底有什么了不起的地方呢?

一直以来,我专注于学习原生 JS(vanilla JavaScript),并且在职业环境中使用 jQuery 。在经过数次失败挫折之后,我终于成功入门 React ,然后就明白了为什么我会更愿意使用 React ,而不是原生 JS 或者 jQuery 。

下面,我将尽量精简我所学会的知识,汇成一个优质简介,分享给大家。

预备知识#

在学习使用 React 之前,你应当知道下面列举的知识。如果你之前从未使用过 JavaScript 或者 DOM 操作,额,我个人建议提前熟悉它们。

需要的知识

  • 熟悉基本的 HTML 和 CSS
  • 知道基本的 JavaScript 和编程
  • 基本理解 DOM
  • 熟悉 ES6 语法及其特性
  • 了解 Node.js 和 npm 全局安装

本文目标#

  • 学习 React 的重要概念和相关术语,例如 Babel, Webpack, JSX, components, props, state, lifecycle 。
  • 创建一个简单地 React 应用,并演示上面的相关概念。

下面是实例应用的源代码和网页实际效果。

什么是 React ?#

  • React 是一个 JavaScript 库,也是最受欢迎的库之一,他在 GitHub 上拥有超过十万的收藏!
  • React 不是一个框架(与“固执己见”的 Angular 大为不同)。
  • React 是 Facebook 创建的一个开源项目。
  • React 被用于在前端构建用户界面(UI)。
  • React 是 MVC 应用程序中的视图层(MVC: Model View Controller)。

事实上,React 最重要的一点就是,你可以创建组件(components) —— 类似于自定义的、可复用的 HTML 元素,然后借此高效地实现 UI。另一方面,React 通过 state 和 props 大大地简化了数据的存储和处理过程。

我们将在下文讨论这些内容,以及更多其他相关知识。那么,让我们开始吧!

Setup and Installation#

有多种使用 React 的方法,这里我将展示其中的两种,以便于你理解到 React 的工作原理。

静态 HTML 文件#

这是第一种方法,不过它是一种不受欢迎的写法,而且之后的内容重点也不是它。但是,它可以让你很容易地熟悉和理解 React ——即使你从未使用过 JS 库,比如 jQuery。甚至可以说,如果你一点也不熟悉 Webpack、Babel、Node.js,那么这将是最简单的入门方法。

我们先要创建一个 index.html 文件,然后在 head 元素中加载三个 CDN :React、React DOM、Babel。之后要写个 div 元素,设置其 id 为 root,最后创建一个 script 标签用来写你自己的代码。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />

    <title>Hello React!</title>

    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  </head>

  <body>
    <div id="root"></div>

    <script type="text/babel">
      // React code will go here
    </script>
  </body>
</html>

在撰写本文时,我正在加载这些库的最新稳定版本。

(译者补充:作者写文时,2018 年 React 是 16 版本,现在 2022 年是 18 版本。对于版本差异,读者暂时不必关心,React 核心思想是不变的。)

React - React 顶层 API React DOM - 提供了一些 DOM 方法 Babel - 一种 JavaScript 编译器,它能让 ES6+ 代码在旧浏览器上运行。

我们的程序入口是 id 为 root 的 div 元素,这是按照规范约定的命名。同时,你也要注意到 test/babel 这种类型,这是使用 Babel 必须的写法。

现在,让我们编写第一个 React 代码块。它将使用 ES6 的类来创建 React 组件,命名为 APP。

class App extends React.Component {
  //...
}

现在,我们添加 render() 方法,这是类组件中惟一强制要求实现的方法,它将用于渲染 DOM 节点。

class App extends React.Component {
  render() {
      return (
          //...
      );
  }
}

在 return 中,我们将放入一个好像 HTML 元素的东西。注意,这里并不是返回的字符串,所以不用引号来包裹元素。这种写法就被称为 JSX,后续我们将学习更多的相关知识。

(译者补充:传统 JS 代码,是基于字符串拼接来生成 HTML 代码,相当于一直在做字符串处理。JSX 中,直接在 JS 代码中插入有意义的 HTML 元素,这样的写法理解起来更直观。)

class App extends React.Component {
  render() {
    return <h1>Hello world!</h1>;
  }
}

最后,我们使用 ReactDOM.render() 方法,将我们创建的 App 类渲染到 HTML 的 root div 中。

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

下面是 index.html 的完整代码。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />

    <title>Hello React!</title>

    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  </head>

  <body>
    <div id="root"></div>

    <script type="text/babel">
      class App extends React.Component {
        render() {
          return <h1>Hello world!</h1>;
        }
      }

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>

现在在浏览器中浏览 index.html,你将看到我们创建的 h1 标签被渲染到了 DOM 中。

hello world

妙啊!

现在你完成这个代码,就能发现 React 入门并不是一样难以做到的事。它只是加载了一些 JS 库到我们的 HTML 中嘛。

(译者补充:可能有人觉得上面的代码很奇怪:在 HTML 代码里面放 JS 代码,再在 JS 代码里面放 HTML 代码,那为什么不直接写个 HTML 代码呢?想必聪明的你已经体会到了,当然是为了兼顾灵活性和可读性。到最后,一般的程序开发,就能像搭积木一样简单。)

到此为止的内容,都是为了简单演示。从下面开始,我们将使用另一种方法:Create React App。

Create React APP#

上面使用的方法——将 JavaScript 库加载到静态 HTML 页面并动态渲染 React 和 Babel,效率不高且难以维护。

幸运的是, Facebook 做了一个好东西:Create React APP,它是一个预先配置完善的 React app 构建环境。它构建了一个热更新的开发环境服务器;通过 Webpack 来自动编译 React、JSX、ES6 代码;自动增加 CSS 文件的前缀;使用 ESLint 来测试代码和警告其中的问题。

(译者补充:这里可以简单理解为,有一个程序,自动帮你配置了各种依赖,减少了手动配置的麻烦。)

在你的终端运行以下命令,将会在当前目录下生成一个新的文件夹 react-tutorial,其中就是项目内容。

$ npx create-react-app react-tutorial

等待完成之后,进去到新的文件夹中,开始启动项目。

$ cd react-tutorial && npm start

一会儿之后,你的浏览器就会弹出新窗口,现实如下页面。

运行成功啦

Create React App is very good for getting started for beginners as well as large-scale enterprise applications, but it’s not perfect for every workflow. You can also create your own Webpack setup for React.

(译)Create React App 非常适合初学者的入门,以及大型企业应用程序的开始,但它并不适合每个工作情况。 所以,你还可以为 React 自定义 Webpack 设置。

观察项目结构,你可以看到 /public 目录和 /src 目录,还有常规的 node_modules、.gitignore、README.md、package.json。

在 /public 中,最重要的文件是 index.html,它和前文中提到的静态 index.html 文件很像,都是一个 root div。不过这次,没有在文件中加载任何的依赖库和 script 标签。

在 /src 中,将会包含全部的 React 代码。

为了观察 React 代码的自动化编译与更新,在 /src/App.js 文件中找到这句话:

Edit <code>src/App.js</code> and save to reload.

然后随便写点字替换它,再然后保存文件,你就能看到 localhost:3000 中刷新了。

(译者补充,React 16 中的这句话是:To get started, edit src/App.js and save to reload. 而在 React 18 中,这句话是:Edit src/App.js and save to reload.)

现在,让我们删除 /src 目录下的全部文件,因为我们将要创建自定义的模板——非常简洁,只有 index.css 和 index.js 文件。

对于 index.css 文件,我就直接复制粘贴来自 Primitive CSS 的内容。当然,如果你想,也可以使用 Bootstrap 或者别的什么 CSS 框架,或者干脆什么都不写。我只是觉得这个更容易使用。

对于 index.js 文件,我们导入 React、ReactDOM,以及 CSS 文件。

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

让我们再来创建 App 组件。之前,我们只是写了一个 h1 标签,现在添加一个 div 元素包裹 h1 标签,并设置 div className=“App”。值得注意的是,我们使用 className 代替了 class。这是一个小提醒,说明这里的代码本质上是 JavaScript,而不是单纯的 HTML。

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>Hello, React!</h1>
      </div>
    );
  }
}

最后,像之前一样,我们渲染 App 组件到 root div 中。

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

这里是 index.js 的完整代码。这次我们直接导入了 Component,所以就不需要再写 React.Component 了。

import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./index.css";

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Hello, React!</h1>
      </div>
    );
  }
}

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

在 localhost:3000 网页上,你就能看到”Hello, React!” 了。就像之前一样,我们实现了一个简单的 React app。

React Developer Tools#

有一个浏览器扩展,叫做 React Developer Tools,它能让你更轻松地使用 React。下载 React DevTools for Chrome ,或者在其他你更喜欢的浏览器里面下载。

安装之后,打开浏览器开发工具 F12,你能在 tab 列中看到 React。点击一下,你就能够在编写组件时进行检查。你也可以在 Elements 中查看实际的 DOM 输出。现在看起来用处不大,但是当 app 变得越来越复杂的时候,它就越来越有必要去使用了。

原文原图 译者本地测试图片

(译者补充:React Developer Tools 版本可能出现了大的变化,安装插件后,在 F12 的 tab 列中,没有 React ,而是 Component 以及 Profiler。所以这里图片有两张,一张是原文原图,一张是译者测试的图片。 另外需要注意的是, React 18 中 render 用法发生改变,旧的写法会引发警告。)

此时此刻,我们已经拥有了真正开始使用 React 所需的全部工具和设置。

JSX: JavaScript + XML#

如你所见,我们在 React 代码中编写的好像是 HTML,但它并不是正经的 HTML,而是 JSX,即 JavaScript XML。

使用 JSX,我们可以编写类似 HTML 的内容,还可以创建和使用自定义的类 XML 便签。下面是 JSX 变量赋值语句。

const heading = <h1 className="site-heading">Hello, React</h1>;

编写 React 代码并不一定要使用 JSX。引擎底层的实现,是通过 createElement 方法来创建标签、属性、子组件的,之后渲染它们。下面的代码具有相同的输出结果。

const heading = React.createElement(
  "h1",
  { className: "site-heading" },
  "Hello, React!"
);

事实上,JSX 更像 JavaScript 而不是 HTML,这里有一些编写代码时候需要注意的关键性差异:

  • 在 CSS 类名指定时,className 被用来替换 class ,因为在 JavaScript 中 class 是关键字。
  • 在 JSX 中的属性和方法采用小驼峰命名法,比如 onclick 变为 onClick。
  • 自闭合标签必须使用斜杠结尾,例如 < img />

JavaScript 代码自然也能被嵌入 JSX 中,使用花括号{}就能嵌入变量、函数、属性。

const name = "Tania";
const heading = <h1>Hello, {name}</h1>;

JSX 创建和添加元素(大部分)比原生 JS 更容易理解和使用,这也是人们喜欢他的理由之一。

组件 (Components)#

目前为止,我们只创建了一个组件:APP。几乎所有 React 组件可以分为两种:类组件(class components)和简单组件(simple components)。

大多数 React app 都包含了很多小组件,并且他们都会在 App 组件中被加载。小组件们拥有它们自己的文件,下面让我们一起来升级改造我们的项目吧。

jiang index.js 中的 App,如下所示:

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

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

接着新建一个文件 App.js,将组件放到这里:

import React, { Component } from "react";

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Hello, React!</h1>
      </div>
    );
  }
}

export default App;

我们导出 App 组件,然后在 index.js 中加载。将组件分离到其他文件里面,这并不是强制性的,但是如果你不这样做,一个应用程序就会变得非常臃肿且难以维护。

类组件(class components)#

现在创建另一个组件,计划是做个表格组件。现新建一个 Table.js 文件,然后把下面的代码贴上去。

import React, { Component } from "react";

class Table extends Component {
  render() {
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Job</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Charlie</td>
            <td>Janitor</td>
          </tr>
          <tr>
            <td>Mac</td>
            <td>Bouncer</td>
          </tr>
          <tr>
            <td>Dee</td>
            <td>Aspiring actress</td>
          </tr>
          <tr>
            <td>Dennis</td>
            <td>Bartender</td>
          </tr>
        </tbody>
      </table>
    );
  }
}

export default Table;

这就是我们创建的自定义类组件,它用大写字母开始,以区别于一般的 html 元素。回到 App.js 中,我们加载 Table 到里面。第一步是引入 Table :

import Table from "./Table";

然后是在 App 的 render() 方法中加载它,就是之前写 “Hello, React!” 的地方。当然,也可以把 className 改为 container。

import React, { Component } from "react";
import Table from "./Table";

class App extends Component {
  render() {
    return (
      <div className="container">
        <Table />
      </div>
    );
  }
}

export default App;

最后回到运行环境中,在浏览器中就可以看到 Table 被加载了出来。

Table

现在,我们已经见识了自定义类组件是什么东西了。我们可以多次复用这个组件,但是,因为里面的数据是写死的,所以其实也没什么用。

简单组件(simple components)#

另一种 React 组件就是简单组件,其本质是一个函数,所以这种组件不需要使用关键字 class。现在让我们改造 Table,在里面做两个简单组件:一个表格标题、一个表格内容。

我们使用 ES6 的箭头函数来创建这些简单组件。首先,是表格标题。

const TableHeader = () => {
  return (
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
      </tr>
    </thead>
  );
};

然后是表格内容。

const TableBody = () => {
  return (
    <tbody>
      <tr>
        <td>Charlie</td>
        <td>Janitor</td>
      </tr>
      <tr>
        <td>Mac</td>
        <td>Bouncer</td>
      </tr>
      <tr>
        <td>Dee</td>
        <td>Aspiring actress</td>
      </tr>
      <tr>
        <td>Dennis</td>
        <td>Bartender</td>
      </tr>
    </tbody>
  );
};

现在我们的 Table 如下所示。注意,TableHeader 和 TableBody 组件在同一个文件中,都是被 Table 类组件使用的。

const TableHeader = () => { ... }
const TableBody = () => { ... }

class Table extends Component {
  render() {
    return (
      <table>
        <TableHeader />
        <TableBody />
      </table>
    )
  }
}

最后结果应该和之前一样。注意观察,组件是可以被嵌套在其他组件中的,并且简单组件和类组件可以混合使用。

A class component must include render(), and the return can only return one parent element.

(译) 一个类组件必须包含 render() 方法,并且 return 返回的最外层父元素只能有一个。

(译者补充:React 18 版本有较大变化,不用 render() 方法)

最后总结一下类组件和函数组件的区别。

/** Simple Component */
const SimpleComponent = () => {
  return <div>Example</div>;
};
/** Class Component */
class ClassComponent extends Component {
  render() {
    return <div>Example</div>;
  }
}

注意,如果 return 只有一行,就可以不用括号。

Props#

现在,我们有了一个漂亮的 Table 组件,但是它的数据是硬编码的。React 的一大优化就是数据的处理,通过属性 props 和状态 state 来实现。接下来,我们将重点关注 props 中的数据处理。

首先,将 TableBody 中的数据移除。

/** src/Table.js */
const TableBody = () => {
  return <tbody />;
};

然后把数据都放到一个数组对象里面,就像自定义了一个 JSON 格式参数的 API。这个数组对象需要放在 render() 方法里面。

/** src/App.js */
class App extends Component {
  render() {
    const characters = [
      {
        name: "Charlie",
        job: "Janitor",
      },
      {
        name: "Mac",
        job: "Bouncer",
      },
      {
        name: "Dee",
        job: "Aspring actress",
      },
      {
        name: "Dennis",
        job: "Bartender",
      },
    ];

    return (
      <div className="container">
        <Table />
      </div>
    );
  }
}

现在,我们通过 Table 组件的元素特性将数据传到 Table 组件里面,这种传数据的用法类似 HTML5 中的 data-* 属性。

这个元素特性的命名是任意的,只要不与关键字重复即可,所以这里使用自定义的 characterData。

传递的数据是一个变量 characters,所以需要用花括号包裹起来——其本质上是作为一个 JavaScript 表达式存在的。

/** src/App.js */
return (
  <div className="container">
    <Table characterData={characters} />
  </div>
);

现在数据已经从外面开始传入 Table,那么就需要从里面接收数据。

/** src/Table.js */
class Table extends Component {
  render() {
    const { characterData } = this.props;

    return (
      <table>
        <TableHeader />
        <TableBody characterData={characterData} />
      </table>
    );
  }
}

这时打开 React 开发工具检查 Table 组件,你就能在其 props 中看到数组数据。这些数据是被存储在虚拟 DOM(virtual DOM) 中,这时一种快速同步数据到实体 DOM 方法。

开发工具虚拟DOM数据

此时数据还没有放到实际的 DOM 中。在 Table 中,我们可以通过 this.props 访问全部的属性。当然,这里只用到一个属性:characterData,所以我们使用 this.props.characterData 来取得那个数据。

我将使用 ES6 的结构方式来创建一个变量,用于包含 this.props.characterData 。

const { characterData } = this.props;

因为 Table 组件包含两个更小的简单组件,所以需要再次通过 props 将数据传递到 TableBody 中。

/** src/Table.js */
class Table extends Component {
  render() {
    const { characterData } = this.props;

    return (
      <table>
        <TableHeader />
        <TableBody characterData={characterData} />
      </table>
    );
  }
}

现在,在 TableBody 中没有参数,并且它也只返回了一个标签。

/** src/Table.js */
const TableBody = () => {
  return <tbody />;
};

我们将通过函数参数来传递 props,然后通过数组映射来返回构造

我们将 props 作为函数参数传递,并通过数组映射以返回数组中每个对象的表行。该映射将包含在 rows 变量中,我们将其作为表达式返回。

(译者补充:如果不知道 map 语法,建议先点击“数组映射”链接看看。这里相当于简化了一个循环拼接 HTML 元素的过程。)

/** src/Table.js */
const TableBody = (props) => {
  const rows = props.characterData.map((row, index) => {
    return (
      <tr key={index}>
        <td>{row.name}</td>
        <td>{row.job}</td>
      </tr>
    );
  });

  return <tbody>{rows}</tbody>;
};

现在去查看 app 的前端运行效果,就能看到数据加载了。

值得注意的是,我在每个表行中都加了 key。你也应该在生成 React 的列表时使用 keys,以便于区分每个列表条目。当我们想要操作列表条目时,key 就是必要的了。

在 React 组件中传递存在的数据,props 是一种高效的方式,然而组件不能修改只可读的 props。在下个章节,我们将学习使用状态(state) 来进一步控制 React 中的数据处理。

State#

现在,我们已经将数据存储在一个数组变量中,并通过 props 传参。这样很容易使用,但是试想一种场景:如果我们想要删除数组中的条目呢?通过 props 我们得到了一个单向数据流;而通过 state 我们可以更新组件内部的私有数据。

你可以将 state 想象成这数据:能够被保存和修改,但是又不存入数据库。举个例子,在购物车中添加或删除物品,还没有进行结算提交。

(译者补充:简单理解为一种临时变量。)

为了说明入门,我们现创建一个 state 对象。

class App extends Component {
  state = {};
}

这个对象中要包含所有你想要存储的变量,这里就是 characters。

class App extends Component {
  state = {
    characters: [],
  };
}

移动实际的数据对象到 state.characters 中。

class App extends Component {
  state = {
    characters: [
      {
        name: "Charlie",
        // the rest of the data
      },
    ],
  };
}

这样数据就被包含在 state 中。因为我们想能够移除表格中的数据,所以在 App 中创建一个 removeCharacter 方法。

在 removeCharacter 中,为了取得相关数据,我们依然使用结构的方式,获得 this.state.characters。为了更新 state,我们使用 this.setState(),它是一种操作 state 的内置方法。最后基于传入的索引参数过滤数组,获取并返回一个新的数组。

You must use this.setState() to modify an array. Simply applying a new value to this.state.property will not work.

(译)你必须使用 this.setState() 来修改数组。简单地为 this.state.property 赋新值是无效的。

(译者补充:简单来说:通过内置方法来改值,不能直接写等号赋值。)

/** src/App.js */
removeCharacter = (index) => {
  const { characters } = this.state;

  this.setState({
    characters: characters.filter((character, i) => {
      return i !== index;
    }),
  });
};

filter 方法不会改变原来的数组,而是返回一个新的数组,它是 JavaScript 中修改数组的首选方法。上面这个特定的方法中,检测比较 index 和数组中的所有索引,然后返回了所有符合条件的索引。

现在,我们需要通过组件传递函数,然后在每行的后面渲染一个按钮,点击它可以调用这个函数。这里将 removeCharacter 作为 prop 传递到 Table 中。

/** src/App.js */
render() {
  const { characters } = this.state

  return (
    <div className="container">
      <Table characterData={characters} removeCharacter={this.removeCharacter} />
    </div>
  )
}

因为还需要将参数从 Table 传递到 TableBody 中,所以再来一次通过 prop 传值,和前文一样。

另外,在我们的项目中,只有 App 和 Form 两个组件拥有自己的 state,所以 Table 类组件最好转为简单组件。

/** src/Table.js */
const Table = (props) => {
  const { characterData, removeCharacter } = props;

  return (
    <table>
      <TableHeader />
      <TableBody
        characterData={characterData}
        removeCharacter={removeCharacter}
      />
    </table>
  );
};

下面就是 removeCharacter() 方法中我们定义的索引的来源。在 TableBody 组件中,我们传递 key/index 参数,所以 filter 函数知道那个条目是需要移除的。我们创建一个按钮 onClick 来传递这个参数。

/** src/Table.js */
<tr key={index}>
  <td>{row.name}</td>
  <td>{row.job}</td>
  <td>
    <button onClick={() => props.removeCharacter(index)}>Delete</button>
  </td>
</tr>

The onClick function must pass through a function that returns the removeCharacter() method, otherwise it will try to run automatically.

(意译)onClick 函数必须传入一个函数作为参数,且这个函数是返回的 removeCharacter() 方法,否则,如果直接传入 removeCharacter() 方法给 onClick,则会自动执行一次。 观察下面两行代码的区别

<button onClick={() => props.removeCharacter(index)}>Delete</button>
<button onClick={props.removeCharacter(index)}>Delete</button>

后者在项目运行之后,会自动执行一次;前者只有在点击按钮的时候才会执行。

妙极了,现在我们有了 delete 按钮,然后就可以修改 state 来删除数据了。

Remove Mac.

删除 Mac. 试试。

现在你应该理解 state 的初始化和修改了吧。

表单数据提交#

现在我们将数据存储在了 state 中,并且可以移除其中的任意条目。然而,如果我们想要添加新的条目呢?在一个实际的项目中,大概率是从一个空的 state 开始,然后往里面添加数据,例如任务手账或者购物车。

首先,移除 state.characters 中的硬编码数据,然后通过表单来更新数据。

class App extends Component {
  state = {
    characters: [],
  };
}

然后在新文件 From.js 中创建一个 Form 组件。

我们将 Form 的初始值设置为空属性对象 initialState,并将其配给 this.state。

import React, { Component } from "react";

class Form extends Component {
  initialState = {
    name: "",
    job: "",
  };

  state = this.initialState;
}

Previously, it was necessary to include a constructor() on React class components, but it’s not required anymore.

(译)以前在 React 类组件中,必须包含 constructor() 方法,但是现在不用了。

(译者补充:这个可能是版本不一样,但是构造函数还是挺有用的)

我们这个表单的目标是,每当一个表单中的字段发生变化时,能够更新 Form 的 state,然后在提交时,所有数据都要传到 App state 中并更新 Table。

首先,创建一个函数,一旦发生了输入改变,它就会执行。这个事件被传递,然后通过设置 Form state 来获得输入的 name(key) 和 value。

/** src/Form.js */
handleChange = (event) => {
  const { name, value } = event.target;

  this.setState({
    [name]: value,
  });
};

在提交表单之前,需要完成下面的事情。在 render 中,获取 state 中的两个属性,并将他们作为 value 分配给对应的表单 key。将 handleChange() 作为 input 的 onChange,最后导出 Form 组件。

render() {
  const { name, job } = this.state;

  return (
    <form>
      <label htmlFor="name">Name</label>
      <input
        type="text"
        name="name"
        id="name"
        value={name}
        onChange={this.handleChange} />
      <label htmlFor="job">Job</label>
      <input
        type="text"
        name="job"
        id="job"
        value={job}
        onChange={this.handleChange} />
    </form>
  );
}

export default Form;

在 App.js 中,我们可以在 表格的下方渲染表单。

import Form from "./Form";
return (
  <div className="container">
    <Table characterData={characters} removeCharacter={this.removeCharacter} />
    <Form />
  </div>
);

观察前端运行效果,会发现此时还没有提交按钮。更新表单字段,你会发现本地的 From 也被更新。

Form change

好极了!最后一步就是提交表单,然后更新父组件状态。我们在 App 组件中创建一个 handleSubmit() 方法,用来更新 App 的 state,数据来自原有的 this.state.characters 以及新添加 character 参数,这里用到了 ES6 扩展运算符

handleSubmit = (character) => {
  this.setState({ characters: [...this.state.characters, character] });
};

然后确保把这个函数参数传递到了 Form 中。

<Form handleSubmit={this.handleSubmit} />

在 Form 组件中,创建一个 submitForm() 方法,由它来调用 handleSubmit() 并传参,实参就是 Form 中的 state ,形参就是前面定义的 character。 之后,它会重置 state 为初始值,相当于表单重置。

submitForm = () => {
  this.props.handleSubmit(this.state);
  this.setState(this.initialState);
};

最后,我们添加一个表单提交按钮。注意,这里使用 onClick 而不是 onSubmit,因为我们并不使用标准的提交函数。这个点击事件会调用自定义的 submitForm 函数。

<input type="button" value="Submit" onClick={this.submitForm} />

大功告成!这样 app 就已经完成啦,我们可以创建和删除表格中的数据。因为 Table 和 TableBody 已经获取到了数据,所以会直接展示出来。

Last

如果你不知所措,没有跟上,可以查看Github 中的源代码

(译者补充:强烈建议初学者,一步一步修改到最终版,而不是直接使用最后的代码运行。)

Pulling in API Data#

(译者补充:这一节看不懂,不用纠结,可以略过。)

React 有一个很常见的用法,就是从 API 中提取数据。如果你不熟悉 API 的概念或者不知道怎么链接 API,我推荐你阅读这篇文章:How to Connect to an API with JavaScript,它会让你知道什么是 API 以及怎样用原生 JavaScript 调用它。

(译者补充:关于 API,网上随便找几篇文章对照着看,简单了解下就行了。)

做个小测试,我们新建一个 Api.js 文件,然后创建一个新的 App。Wikipedia API 是一个公共接口,我们可以用来测试。这里有个 URL 可以用来做测试,直接点击这个链接,看看效果,如果用 JSONView 插件可以好看一点。

(译者补充:维基百科是不能直接打开的,嗯。)

我们计划使用 JavaScript 内置的 Fetch 方法从 URL 终端获取数据并展示。通过改变 index.js 中的 App 引入路径 (例如:import App from ‘./Api’),你可以在我们之前创建的 app 和这个测试的 app 之间切换。

我不会逐行解释这段代码,因为我们前面学过了通过 state 数组创建组件、渲染和映射。这里新的知识点是 componentDidMount(),它是 React 生命周期里面的方法,而生命周期是指 React 中的某些特定方法的执行顺序。挂载(Mount)是指某个条目被插入到 DOM。

当我们获取 API 的数据时,我们会选择使用 componentDidMount,因为我们想要确保组件在获取数据之前就被渲染出来。下文中,你将看到如何从 Wikipedia API 中引入数据,并将其显示在页面上。

/** src/Api.js */
import React, { Component } from "react";

class App extends Component {
  state = {
    data: [],
  };

  // Code is invoked after the component is mounted/inserted into the DOM tree.
  componentDidMount() {
    const url =
      "https://en.wikipedia.org/w/api.php?action=opensearch&search=Seona+Dancing&format=json&origin=*";

    fetch(url)
      .then((result) => result.json())
      .then((result) => {
        this.setState({
          data: result,
        });
      });
  }

  render() {
    const { data } = this.state;

    const result = data.map((entry, index) => {
      return <li key={index}>{entry}</li>;
    });

    return <ul>{result}</ul>;
  }
}

export default App;

在本地环境中运行,你就能看到 API 的数据被展示在了 DOM 中。

API

还有其他生命周期方法,但对它们的介绍超出了本文的范围。 您可以在此处阅读有关 React 组件的更多信息

构建和部署 React 应用#

(译者补充:如果这一节看不懂,不必纠结,略过即可。这些内容,重要但不急迫,需要各种基础知识和经验,才能更好地理解这一部分内容。)

到目前为止,我们所做的一切都是在开发环境中进行的:编译、热加载、实时更新。 在实际生产中,我们将希望加载的是最后的静态文件,也就是不需要源代码来编译。 这个可以通过构建和部署来实现。

现在,如果你想要编译所有的 React 代码,然后把它放到根目录的某个地方,你需要运行这个命令:

npm run build

这会创建一个 build 文件夹来存放你的 app,其中的内容可以放到任何地方,然后就完成了!

我们还可以更进一步,用 npm 将它部署到某处。我们计划创建一个 GitHub 网页,所以你需要熟悉 Git ,然后将你的代码上传到 GitHub 中。

确保你已经退出本地运行环境,也就是代码没在运行。首先,在 package.json 中添加 homepage 字段,这个 URL 是我们想让这个 app 运行的地址。

/** package.json */
"homepage": "https://taniarascia.github.io/react-tutorial",

然后添加这两行到 package.json 的属性 scripts 中。

"scripts": {
  // ...
  "predeploy": "npm run build",
  "deploy": "gh-pages -d build"
}

在项目中,添加 gh-pages 包来到开发依赖中。

npm install --save-dev gh-pages

我们创建 build 静态文件,它能完成全部的编译。

npm run build

最后部署到 gh-pages 。

npm run deploy

完成!在 https://taniarascia.github.io/react-tutoria 中就能查看 app 了。

总结#

本文应该很好地介绍了 React、简单组件、类组件、state、props、使用表单数据、从 API 中提取数据、部署应用程序。关于 React 的学习和使用,还有很多很多内容,但我希望你现在已经有一定信心,继续研究和运用 React 。

如果有任何不清楚的地方,或者想在这篇文章或后续文章中看到什么,请告诉我。

(译者补充:翻译不合理之处,欢迎指正。)

React 入门引导:理论概述与示例演练
http://www.snofly.cn/posts/react/react-to/
Author
snofly
Published at
2022-05-31