从现在开始学习React入门-React的基础知识

表格目录

今から始めるReact入門 〜 React の基本 ←★ここ
今から始めるReact入門 〜 React Router 編
今から始めるReact入門 〜 flux編
今から始めるReact入門 〜 Redux 編: immutability とは
今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する
今から始めるReact入門 〜 Redux 編: Redux アプリケーションを作成する
今から始めるReact入門 〜 Mobx 編

在开始使用React之前

作为一个日常接触后端的人,我想将个人React学习的记录保留下来,以便接触前端知识。虽然已经有很多关于React的文章流传,但我想在这里主要讨论最近的React,以及React Router、Redux、其他Redux中间件和MobX。此外,我还想结合webpack v4和babel,以便在React的工作场所中可以应用到。

这次学习React主要参考的是以下React JS教程视频。

    • React JS Tutorials

在这个过程中,有些部分因为Webpack版本过旧而无法运行,但在这里我们会对它们进行修正以适应。由于内容比较长,我想分成多个部分来进行解释。

本课所使用的文件

本次讲座所使用的文件可在以下存储库中找到。

    https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/1-basic-react/00_start_point/react-tutorial

React 是什么?

总的来说,View层是JavaScript前端框架,具有以下特点。

宣言的 de) – 聲明的 de) – 声明的

通过在应用程序中对每个状态进行简单的设计,React 可以在数据发生更改时,仅高效更新和渲染适当的组件。
开发者无需关注找到应该更新的合适组件的逻辑。
声明式视图增加了可预测性,并且使代码更易于调试。
此外,可以使用 JSX 语法(一种在 JavaScript 内嵌入 HTML 标签的语法),使渲染的形式更加直观。

组件化

通过构建管理自身状态的封装组件,可以通过调用这些组件来创建复杂的UI。这些组件不是模板,而是使用JavaScript编写,可以轻松传递丰富的数据到应用程序,并且可以在DOM之外维持状态。

循环使用

由于React不对开发者的其他技术知识作出假设,因此可以在不重新编写现有代码的情况下,使用React开发新功能。
例如,可以使用Node来支持SSR(服务器端渲染),也可以使用React Native开发强大的移动应用程序。

此外,React 有一个特点是将Facebook作为其后台,之前可能会因为其采用BSD + 特许许可证等许可形式而使某些人对企业应用程序的开发产生犹豫,但在2017年9月左右改为了MIT许可证,使其比以前更易于使用。

既然我们已经掌握了React的基本特点,那么接下来就立即开始使用React来进行编程吧。

环境准备

这次我们是在以下这样的环境中创建的。

    • 環境概要

演算子
意味

node version
v8 系

npm version
5 系

webpack
4 系

OS
Arch Linux

让我们以一个简单的结构作为例子来体验一下React,以便确认React的基本操作。

创建工作空间

创建一个名为react-tutorial的工作目录,以便于制作一个能够体验React基本操作的应用程序。

$ mkdir react-tutorial
$ cd react-tutorial
$ mkdir -p src/js

使用npm init命令来创建项目。

$ npm init
......
package name: (react-tutorial) 
version: (1.0.0) 
description: 
entry point: (index.js) webpack.config.js    # <- "webpack.config.js" 入力(先にwebpack.config.js 作っておけばデフォルトで選択される)
test command: 
git repository: 
keywords: 
author: Your Name
license: (ISC) 
......

補充:根據 @ksilverwall 用戶的評論,如果在創建項目時沒有輸入description和git repository,當執行後續的npm install等命令時會出現以下警告。

npm WARN react-tutorial@1.0.0 No description
npm WARN react-tutorial@1.0.0 No repository field.

这个警告本身没有问题,所以可以按原样进行,没关系。但是,如果有人在意的话,请在描述和Git仓库中填入适当的值。

接下来,我们将安装React、Webpack和Babel。
在实际项目中,我们将使用Babel来支持ES6语法,并使用Webpack进行模块打包和开发。

$ npm install --save-dev webpack webpack-cli webpack-dev-server
$ npm install -g webpack webpack-cli
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
$ npm install --save-dev react react-dom

创建webpack.config.js文件并编写捆绑规则。
通过以下方式设置webpack.config.js,从./src/js/client.js作为起点读取文件内部的import语法,并将这些源代码进行模块捆绑。

var debug   = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path    = require('path');

module.exports = {
  context: path.join(__dirname, "src"),
  entry: "./js/client.js",
  module: {
    rules: [{
      test: /\.jsx?$/,
        exclude: /(node_modules|bower_components)/,
        use: [{
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react', '@babel/preset-env']
          }
        }]
      }]
    },
    output: {
      path: __dirname + "/src/",
      filename: "client.min.js"
    },
    plugins: debug ? [] : [
      new webpack.optimize.OccurrenceOrderPlugin(),
      new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
    ]
};

接下来,创建src/index.html文件。
在src/index.html文件中,指定加载由Webpack创建的client.min.js文件。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Tutorials</title>
    <!-- change this up! http://www.bootstrapcdn.com/bootswatch/ -->
    <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cosmo/bootstrap.min.css" type="text/css" rel="stylesheet"/>
  </head>

  <body>
    <div id="app"></div>
    <script src="client.min.js"></script>
  </body>
</html>

让我们打开client.js文件并使用React的JSX在屏幕上呈现”Welcome!”。

import React from "react";
import ReactDOM from "react-dom";

class Layout extends React.Component {
  render() {
    return (
      <h1>Welcome!</h1>
    );
  }
}

const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);

用webpack命令来创建client.min.js文件,然后在Chrome中打开index.html文件即可,准备工作已完成。

$ webpack --mode development
Hash: f97a4ff0cc28741ce49d
Version: webpack 4.6.0
Time: 995ms
Built at: 2018-04-29 11:18:20
        Asset      Size  Chunks             Chunk Names
client.min.js  1.66 MiB    main  [emitted]  main
Entrypoint main = client.min.js
[./js/client.js] 2.31 KiB {main} [built]
    + 21 hidden modules

$ google-chrome-stable ./src/index.html
React_CreateProject0001.png

欢迎!看到显示了。使用JSX进行首次元素渲染。
尝试通过更改client.js的内容来确认显示是否会发生变化。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>Welcome!</h1>
+      <h1>It works!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

购买一个全新的,则使用webpack命令进行转译,然后再次在Chrome中打开屏幕进行尝试。

$ webpack --mode development
......

$ google-chrome-stable ./src/index.html
React_CreateProject0002.png

这次显示为 “It works!”。
React 使用JSX语法可以渲染屏幕。

使用webpack-dev-server启动开发用的web服务器。

webpack提供了一个用于开发的web服务器。
通过使用开发用的web服务器(webpack-dev-server),您可以同时进行捆绑和开发中服务的发布,从而大大提高开发效率,这是一个不容错过的功能。

那么让我们来试着启动webpack-dev-server吧。

$ ./node_modules/.bin/webpack-dev-server --content-base src --mode development

如果使用webpack 4版本的话

$ webpack serve

请以另一种方式执行。

您可以尝试在Web浏览器中访问http://localhost:8080以查看当前状态。然后应该会显示先前显示的It works!。

React_CreateProject0003.png

让我们在这个状态下,修改并覆盖保存./src/js/client.js。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>It works!</h1>
+      <h1>Welcome!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);
React_CreateProject0004.png

使用webpack-dev-server可以有效地进行开发,这样您可以在Web页面上实时查看到变化。

在npm脚本中注册webpack-dev-server的启动命令。

将webpack-dev-server命令注册为npm脚本,添加到package.json中,并配置让它通过npm start命令来启动。在npm脚本中,由于会自动将./node_modules目录添加到PATH中,所以只需要在package.json中如下配置即可:

 ......
   "scripts": {
+    "start": "webpack-dev-server --content-base src --mode development --inline",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
 ......

当附加内容编辑完成后,尝试按照以下方式启动webpack-dev-server。

$ npm start

我认为如果在这种状态下,像之前一样通过Web浏览器访问http://localhost:8080,就可以看到由React渲染的页面。

我们已经准备好了开发环境。接下来我们将开始开发一个基于实际React开发场景的Web应用程序。

JSX可以用中文翻译为“JavaScript XML”。

我用webpack和babel创建了一个简单的React应用项目。
接下来我将解释一下在React应用程序的程序中出现的JSX。

关于JSX

创建React应用程序时,出现了以下描述。

......
  render() {
    return (
      <h1>It works!</h1>
    );
  }
......

这个

的写法是HTML标签,在JavaScript的语法中并不常见。
然而,通过这种写法,实际上将HTML标签的内容渲染到了屏幕上。

这是在执行webpack命令时,内部会调用babel并将其转换为一种常见的Web浏览器可解释的JavaScript语法。

......
    return React.createElement(
      "h1",
      null,
      "Welcome!"
    );
......

调用React.createElement方法,并将h1作为HTML标签名,属性值为null,被标签包裹的文本Welcome!作为参数传递。
实际上,当单独运行babel时,它会被编译成稍微复杂一些的形式。

$ npm install -g --no-save @babel/cli
$ npm install -g --no-save @babel/core @babel/preset-react @babel/preset-env
$ babel --presets @babel/preset-react,@babel/preset-env ./src/js/client.js
  ......
  _createClass(Layout, [{
    key: "render",
    value: function render() {
      return _react2.default.createElement(
        "h1",
        null,
        "It works!"
      );
    }
  }]);
  ......

然而,无论如何,它将被转换为符合JavaScript语法的语法。
通过使用webpack和babel,React/JSX语法会被编译为典型的JavaScript,以便在任何Web浏览器上都可以执行。

在描述多个组件时需要注意的要点

当使用JSX时,您可以在JavaScript中直接嵌入标签,就像正常编写HTML一样。不过,需要注意一些要点。现在让我们将src/js/client.js按照以下方式进行修改。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
       <h1>Welcome!</h1>
+      <h1>It's works!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

按下Google Chrome的F12键打开控制台,您可能会在错误消息中看到以下类似的信息。

React_JSXcomponent0000.png
SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

只能将JSX组件排成两行,必须通过父级组件进行包裹才能完成。
通过以下方式进行描述,可以正确渲染错误。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>Welcome!</h1>
-      <h1>It's works!</h1>
+      <div>
+        <h1>Welcome!</h1>
+        <h1>It's works!</h1>
+      </div>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

关于变量的处理

在JSX中嵌入用JavaScript定义的变量,可以使用{变量名}进行嵌入。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     let name = "Tsutomu";
     return (
-      <div>
-        <h1>Welcome!</h1>
-        <h1>It's works!</h1>
-      </div>
+      <h1>It's {name}!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

还可以通过使用{数式}将数学公式的计算结果嵌入其中。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>It's {name}!</h1>
+      <h1>It's {1 + 2}!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

另外,您也可以直接将函数嵌入替代变量的用法。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>It's {1 + 2}!</h1>
+      <h1>It's {this.get_result(3)}!</h1>
     );
   }
+  get_result(num) {
+    return 1 + num;
+  }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

此外,您可以创建匿名函数并立即调用它。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
   render() {
     return (
-      <h1>It's {this.get_result(3)}!</h1>
+      <h1>It's { ((num) => { return 1 + num; })(3) }!</h1>
     );
   }
-  get_result(num) {
-    return 1 + num;
-  }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

此外,还可以使用构造函数来引用在其中初始化的成员变量。

 import React from "react";
 import ReactDOM from "react-dom";

 class Layout extends React.Component {
+  constructor() {
+    super();
+    this.name = "Tsutomu";
+  }
   render() {
     return (
-      <h1>It's { ((num) => { return 1 + num; })(3) }!</h1>
+      <h1>It's {this.name}!</h1>
     );
   }
 }

 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

React 组件化

通过将 React 组件化,可以将内容分散到多个文件中,而不是像上一篇文章那样把所有内容都放在一个文件中,这样可以提高再利用性。

import React from "react";
import ReactDOM from "react-dom";

class Layout extends React.Component {
  constructor() {
    super();
    this.name = "Tsutomu";
  }
  render() {
    return (
      <h1>It's {this.name}!</h1>
    );
  }
}

const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);

将此源代码中的Layout类移植到新的文件src/js/components/Layout.js中。

$ mkdir -p ./src/js/components
$ touch src/js/components/Layout.js

请创建src/js/components/Layout.js文件并将以下内容从src/js/client.js移动到该文件中。
另外,我们将使用export语法,以便稍后可以从src/js/client.js进行引用并从外部访问。

import React from "react";

export default class Layout extends React.Component {
  constructor() {
    super();
    this.name = "Tsutomu";
  }
  render() {
    return (
      <h1>It's {this.name}!</h1>
    );
  }
}

我们将在 client.js 中导入这个 Layout 组件。

 import React from "react";
 import ReactDOM from "react-dom";
+import Layout from "./components/Layout";

-class Layout extends React.Component {
-  constructor() {
-    super();
-    this.name = "Tsutomu";
-  }
-  render() {
-    return (
-      <h1>It's {this.name}!</h1>
-    );
-  }
-}
- 
 const app = document.getElementById('app');
 ReactDOM.render(<Layout/>, app);

通过这种方式,JSX的Layout标签现在可以像以前一样在client.js中使用。
执行npm start命令并在http://localhost:8080上确认之前的It’s Tsutomu!仍然显示。

创建 Header、Footer 组件

创建了Layout组件之后,接下来我们要创建Header和Footer组件。

创建Header组件

请创建src/js/components/Header.js文件,并开始制作Header组件。

import React from "react";

export default class Header extends React.Component {
  render() {
    return (
      <header>header</header>
    );
  }
}

一旦创建了Header组件,就可以将其引入到Layout.js中。

 import React from "react";
+import Header from "./Header";

 export default class Layout extends React.Component {
-  constructor() {
-    super();
-    this.name = "Tsutomu";
-  }
   render() {
     return (
-      <h1>It's {this.name}!</h1>
+      <div>
+        <Header />
+      </div>
     );
   }
 }
React_CreateHeaderAndFooterComponents0000.png

请问header是否正常显示?
现在我们尝试将Layout.js内的Header组件增加到3个,以确保可重用性。

 import React from "react";
 import Header from "./Header";

 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <Header />
+        <Header />
+        <Header />
       </div>
     );
   }
 }
React_CreateHeaderAndFooterComponents0001.png

页脚组件

接下来我们将创建 Footer 组件。

import React from "react";

export default class Footer extends React.Component {
  render() {
    return (
      <footer>footer</footer>
    );
  }
}

我们将这个内容像之前一样引入到Layout.js中。

 import React from "react";
 import Header from "./Header";
+import Footer from "./Footer";

 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <Header />
-        <Header />
-        <Header />
+        <Footer />
       </div>
     );
   }
 }
React_CreateHeaderAndFooterComponents0002.png

补充:关于将多个组件排列在一起的注意事项

如果需要将多个组件并列排列,也可以创建一个数组并使用它。

import React from "react";
import Header from "./Header";
import Footer from "./Footer";

export default class Layout extends React.Component {
  render() {
    let components = [<Header />, <Footer />];
    return (
      <div>
        {components}
      </div>
    );
  }
}

调用组件中的组件。

我刚刚创建了Header组件,但是这个组件也可以调用其他组件。
通常情况下,如果要从一个组件中调用另一个组件,通常会创建一个用于存放其他组件的目录。

为了在header区域使用h1元素来显示标题,我们将创建一个Title组件。首先,创建src/js/components/Header目录,在其中创建一个Title.js文件。

$ mkdir -p src/js/components/Header
import React from "react";

export default class Title extends React.Component {
  render() {
    return (
      <h1>Welcome!</h1>
    );
  }
}

创建组件后,尝试从Header组件中引用Title组件。

 import React from "react";
+import Title from "./Header/Title";

 export default class Header extends React.Component {
   render() {
     return (
-      <header>header</header>
+      <header>
+        <Title />
+      </header>
     );
   }
 }

让我们打开Web浏览器并确认显示。标题应该显示在标题中。

以上是关于组件的说明。
通过将布局的各个部分组件化,可以增加重复使用性,并期望提高开发效率和设计统一性。
通过巧妙地创建可重复使用的组件,我们可以高效地开发大型Web项目的应用程序。

React的state和生命周期

React 有一个叫做state的状态数据。这个state是用来保存应用程序的状态的,它存放了关于如何渲染组件的信息。state可以通过setState方法进行修改,当state发生变化时,会自动触发重新渲染组件的命令,并将其排队。此外,setState方法是一种用于修改state状态并重新渲染组件的基本方法,在React中经常被使用。

让我们在这里学习React和state的特点,并学习如何创建具有响应性的应用程序。

关于国家

在React组件中,state位于拥有React.Component类作为父类的类中,可以通过this.state进行访问。

那么,让我们实际组装一下。
请按照以下方式定义构造函数并设置this.state的初始值,使用setTimeout函数使得在初始界面显示后1000毫秒后更新屏幕显示的程序。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
+  constructor() {
+    super();
+    this.state = {name: "Tsutomu"};
+  }
   render() {
+    setTimeout(
+      () => { this.setState({name: "Hello"}); }
+    , 1000
+    );
     return (
       <div>
+        {this.state.name}
         <Header />
         <Footer />
       </div>
     );
   }
 }

当这个this.state的数据通过setState方法设置或修改后,会自动检测更新差异,并且只重新呈现JSX内部必要的地方,使得dom重新渲染。

React_StateAndPropsAndApplicationData0000.gif

为了使其更加直观易懂,您可以通过按下F12键打开Chrome开发者模式并选择Rendering,然后勾选Paint flashing来动态转换dom,并确认屏幕变化。

React_CreateHeaderAndFooterComponents0002.png

如果这样做,可以实时显示状态变化的DOM,并以不同的颜色进行标示。
通过观察这种显示方式,可以了解到React在重新渲染时不会更新整个组件,而是只更新需要更新的部分。
如果查看相关的源代码位置…

  ......
  return (
    <div>
      {this.state.name}
      <Header />
      <Footer />
    </div>
  );
  ......

这段文本以及前面的显示已经确认,虽然有一些文字不是来自于`this.state.name`,但是它们一起更新的只是`Header`和`Footer`组件。React 在前端 JavaScript 中,即使涉及成本高的 DOM 渲染,也只会更新必要的部分,因此无需编写复杂的处理逻辑,就可以实现高效的渲染处理。

关于道具

HTML 标签中的属性可以通过指定参数来对标签元素进行设置,从而在相同的元素中实现细微的差异,例如改变大小、改变颜色或注册事件等等。

在 React 的 JSX 中,也可以通过传递参数来使用每个组件,并且通过这样做可以为每个组件传递单独的值作为参数。

在 React 的 JSX 中,我们称之为 Props。

那么,让我们立刻使用 Props 进行编程吧。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
-  constructor() {
-    super();
-    this.state = {name: "Tsutomu"};
-  }
   render() {
-     setTimeout(
-       () => { this.setState({name: "Nakamura"}); }
-     , 1000);
+    const title = "Welcome Tsutomu!";
     return (
       <div>
-        {this.state.name}
-        <Header />
+        <Header title={title} />
         <Footer />
       </div>
     );
   }
 }

在上述的文件中,

中的title={title} 是一个props。

接下来打开Header.js文件。
在Header.js中,您可以通过this.props访问从Layout.js传递过来的props。

 import React from "react";
 import Title from "./Header/Title";

 export default class Header extends React.Component {
   render() {
+    console.log(this.props);
     return (
       <header>
         <Title />
       </header>
     );
   }
 }

当检查Web浏览器的开发者窗口时,this.props 的显示如下。

React_StateAndPropsAndApplicationData0003.png

要传递一个字符串,可以使用{“string”}的形式来传递。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
   render() {
     const title = "Welcome Tsutomu!";
     return (
       <div>
-        <Header title={title} />
+        <Header name={"some string"} title={title} />
         <Footer />
       </div>
     );
   }
 }
React_StateAndPropsAndApplicationData0004.png

另外,如果创建了两个Header组件并传递不同的props,那么将调用具有不同props的每个Header组件。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
   render() {
     const title = "Welcome Tsutomu!";
     return (
       <div>
-        <Header name={"some string"} title={title} />
+        <Header title={title} />
+        <Header title={"Thank you"} />
         <Footer />
       </div>
     );
   }
 }

我们已经理解了Props的外部要求,下一步是从Header组件传递props到Title组件。

import React from "react";
import Title from "./Header/Title";

export default class Header extends React.Component {
  render() {
-   console.log(this.props);
    return (
      <header>
-       <Title />
+       <Title title={this.props.title} />
      <header>
    );
  }
}

在组件中,我们可以这样写,并使用title props将传递的值在

标签中显示出来。

import React from "react";

export default class Title extends React.Component {
  render() {
    return (
-     <h1>Welcome!</h1>
+     <h1>{this.props.title}</h1>
    );
  }
}

保存并在Web浏览器上通过http://localhost:8080进行屏幕确认。

React_StateAndPropsAndApplicationData0005.png

会显示两个不同标题的组件。
通过使用props,可以大大提高一个组件的可重用性。

state 和 props 的组合

我们也可以将state和props组合在一起使用。
让我们将Layout.js的代码修改如下。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
+  constructor() {
+    super();
+    this.state = {title: "Welcome"};
+  }
   render() {
-    const title = "Welcome Tsutomu!";
+    setTimeout(
+      () => { this.setState({title: "Welcome Tsutomu!"}); },
+      2000
+    );
     return (
       <div>
-        <Header title={title} />
+        <Header title={this.state.title} />
         <Header title={"Thank you"} />
         <Footer />
       </div>
     );
   }
 }

通过将props应用于state的值,可以实现只有第一个Header组件在显示后2秒更改标题。

关于活动和数据变更

打开Header.js文件,添加一个输入框,通过用户在表单中输入的数据实时触发事件。

 import React from "react";
 import Title from "./Header/Title";

 export default class Header extends React.Component {
   render() {
     return (
       <header>
         <Title title={this.props.title} />
+        <input />
       </header>
     );
   }
 }

在之前的编辑中,Layout.js中有两个Header组件的记录,所以我们需要删除一个。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
   constructor() {
     super();
     this.state = {title: "Welcome"};
   }
   render() {
     setTimeout(
       () => { console.log("called"); this.setState({title: "Welcome Tsutomu!"}); },
       2000
     );
     return (
       <div>
         <Header title={this.state.title} />
-        <Header title={"Thank you"} />
         <Footer />
       </div>
     );
   }
 }

让我们来确认一下 http://localhost:8080 的情况。
然后,页面会出现如下的情况,Title 中会添加一个 input。

React_EventAndDataChanges0000.png

然而,如果保持当前状态,即使在表单中输入文字也不会触发任何事件。
接下来,让我们添加一个处理程序,以获取在此输入表单中输入的值,并实时渲染标题。

添加用于获取输入内容的处理程序

让我们在Layout.js中创建一个changeTitle方法,并将它传递给Header组件。

 import React from "react";
 import Header from "./Header";
 import Footer from "./Footer";

 export default class Layout extends React.Component {
   constructor() {
     super();
     this.state = {title: "Welcome"};
   }
+  changeTitle(title) {
+    this.setState({title});
+  }
   render() {
-    setTimeout(
-      () => { console.log("called"); this.setState({title: "Welcome Tsutomu!"}); },
-      2000
-    );
     return (
       <div>
-        <Header title={this.state.title} />
+        <Header changeTitle={this.changeTitle.bind(this)} title={this.state.title} />
         <Footer />
       </div>
     );
   }
 }

上述的changeTitle函数的this.setState({title}); 这种写法是ES6的写法,与this.setState({title: title}); 有相同的意思。
此外,通过将方法通过props传递给Header组件作为

,在Header.js中就可以调用Layout.js的changeTitle(title)方法。接下来,我们将编辑Header.js。
通过以下方式,Header组件和Title组件只需要直接显示Layout组件传递的值,而无需管理Layout组件何时传递什么值。

 import React from "react";
 import Title from "./Header/Title";

 export default class Header extends React.Component {
+  handleChange(e) {
+    const title = e.target.value;
+    this.props.changeTitle(title);
+  }
   render() {
     console.log(this.props);
     return (
       <header>
         <Title title={this.props.title} />
-        <input />
+        <input value={this.props.title} onChange={this.handleChange.bind(this)} />
       </header>
     );
   }
 }

在这个状态下,打开http://localhost:8080 并尝试在输入框中输入文本。
输入框中输入的值将实时反映在标题上,并显示在屏幕上。

React_EventAndDataChanges0001.gif

在这里,我们学习了使用React进行基本状态管理和渲染。当React的状态发生改变时,状态会立即重新渲染,你有没有对此有所了解呢?目前,你已经可以无问题地使用React创建简单的应用程序了。

接下来我们将学习使用React Router 实现内容列表显示和通过不同的路径在SPA中实现网页的方法。

附注:关于Layout组件中的bind(this)这一描述

在将方法从Layout组件传递给Header组件时,我们使用了

来调用bind(this)方法进行传递。然而,如果我们不这样做,而是使用

,虽然函数本身会传递到Header组件,但当从Header组件调用该函数时,它将变成一个与Layout作用域中调用的函数不同的函数。因此,如果我们将changeTitle函数传递给Header组件并调用它…

  changeTitle(title) {
    this.setState({title});
  }

在上述的代码中,this.setState({title}); 中的this 不再是Layout 实例。
因此,setState函数也不再是Layout类内的函数,可能会产生意想不到的行为。
为了确保能够确切调用Layout实例的setState函数,我们需要将bind函数的参数this(Layout实例)与

进行绑定,这样无论该函数是否在Header组件内或者其他任何地方调用,都能调用Layout实例的changeTitle函数。另外,当然可以这样做:

,这样可以将changeTitle函数以someInstance作用域的方式传递进去。

補充:可以通过public class fields的语法来省略bind的描述。

通过使用公共类字段语法,可以省略对绑定的声明。

  ...
  changeTitle = (title) => {    /* <- 関数の宣言をこのように変える */
    this.setState({title});
  }
  render() {
    return (
      <div>
        <Header changeTitle={this.changeTitle} title={this.state.title} />    /* <- bind の記載が省略できる */
        <Footer />
      </div>
    );
  }
    2019-04-30 @6in さんのコメントより追記:

需要安装@babel/plugin-proposal-class-properties插件,并将其作为.babelrc或webpack.config.js中的插件指定,以使用public class fields语法。

$ npm install --save-dev @babel/plugin-proposal-class-properties
{
  "plugins": [
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
  ]
}
       ......
       use: [{
         loader: 'babel-loader',
         options: {
-          presets: ['@babel/preset-react', '@babel/preset-env']
+          presets: ['@babel/preset-react', '@babel/preset-env'],
+          plugins: [
+            ['@babel/plugin-proposal-class-properties', { 'loose': true }]
+          ]
         }
       ......

请在完成之后重新运行npm start。

補充:使用箭头函数可以省略bind的声明。

使用箭头函数也可以省略bind的声明。

  ...
  changeTitle(title) {
    this.setState({title});
  }
  render() {
    return (
      <div>
        <Header changeTitle={(e) => this.handleClick(e)} title={this.state.title} />
        <Footer />
      </div>
    );
  }

这种写法的缺点通常不会成为问题,但有一些注意点。

使用这种方法时,每次调用render方法都会生成changeTitle函数。
而且,如果子组件通过prop并且以这种方式传递函数,则子组件将始终重新渲染,除非单独编写shouldComponentUpdate()方法。
由于这些问题,通常建议使用bind或public class fields语法的方法。

请提供以下选项的中文本地化:

React A JavaScript library for building user interfaces

https://reactjs.org/

REACT JS TUTORIAL #1 – Reactjs Javascript Introduction & Workspace Setup

REACT JS TUTORIAL #2 – Reactjs Components & Rendering

REACT JS TUTORIAL #3 – Composing Multiple React.js Components

Best way to build/compile/deploy ReactJS to production

https://stackoverflow.com/questions/37152773/best-way-to-build-compile-deploy-reactjs-to-production

Handling Events

https://reactjs.org/docs/handling-events.html

Function.prototype.bind()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Public Class Fields saves sooo many keystrokes in React code

https://www.peterbe.com/plog/public-class-fields

bannerAds