Skip to content

Latest commit

 

History

History

test-react

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

node-study

Study React

React.js\ECMAScript 2015\Webpack usage

Install

Install node module package

$ cd test-dialog
$ npm install

Compile and build

$ npm run build

You also can use this command

$ webpack

Build with press

$ webpack -p

Develop

$ npm run dev

React(ECMAScript 2015)以及Webpack工具基本用法

本项目是对ReactWebpack工具的基本用法的简单应用,并且整个项目均使用ECMAScript 2015规定的Javascript语法,欢迎指正错误,欢迎补充,欢迎fork :)

目录·

  1. 准备

  2. 关于ECMAScript 2015(Javascript)

    2.1 关于Babel

  3. 关于React

  4. 关于Webpack

    4.1 Webpack特点

  5. 初始化项目准备

    5.1 创建项目目录,并初始化

    5.2 初始化package.json

    5.3 完整package.json清单

    5.4 新建工程目录src

  6. webpack配置(项目构建)

    6.1 初始化

    6.2 模块和插件配置

    6.3 完整webpack清单

    6.4 配置webpack-dev-server入口html页面

    6.5 执行构建/编译

  7. 项目目录结构

    7.1 目录结构说明

  8. 具体实现

    8.1 Hello World

    8.2 React JSX模板用法

    8.3 组件属性:props用法

    8.4 组件类的PropTypes属性

    8.5 获取真实的DOM节点

    8.6 状态

    8.7 组件生命周期

准备

在使用本项目之前,请配置好如下开发环境

node.js: v6.x/v7.x

开发工具建议使用

Visual Studio Code/Atom

若使用如上开发工具,建议安装如下插件

eslint: ES6/ES7(ECMAScript)语法检查工具
babel:  ES6/ES7(ECMAScript)编译工具

实际页面程序调试工具建议使用如下版本的浏览器

browser: chrome v50+

返回目录

关于ECMAScript 2015(Javascript)

ECMAScript是一种由ECMA国际(前身为欧洲计算机制造商协会)通过ECMA-262标准化的脚本程序设计语言.

ECMAScript 的第六版修订,于 2015 年完成标准化.这个标准被部分实现于大部分现代浏览器.

它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言.

返回目录

关于Babel

Babel 是一个广泛使用的 ES6(ECMAScript 2015) 转码器,可以将ES6代码转为ES5代码,从而在现有环境中运行.

例如

// ES6
// 编译前
var test = (str) => { 
  console.log(str);
}

// ES5
// 编译后
"use strict";

var test = function(str) {
  console.log(str);
};

Babel使用

可以通过node.js全局安装Babel工具

$ npm install -g babel-cli

Babel使用命令如下

转码结果输出到标准输出

$ babel example.js

转码结果写入一个文件,--out-file-o参数指定输出文件

$ babel example.js --out-file compiled.js

或者

$ babel example.js -o compiled.js

整个目录转码,--out-dir-d参数指定输出目录

$ babel src --out-dir lib

或者

$ babel src -d lib

-s 参数生成source map文件

$ babel src -d lib -s

在项目中使用需要配置babel配置文件:.babelrc

.babelrc存放在项目的根目录下,并需要按照如下格式配置

{
  "presets": [],
  "plugins": []
}

presets字段设定转码规则,在项目中可以根据需要安装官方提供的规则

最新转码规则

$ npm install --save-dev babel-preset-latest

react 转码规则

$ npm install --save-dev babel-preset-react

Babel官方插件/规则(es2015)

$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3
https://babeljs.io/docs/plugins/preset-es2015/

Babel在线工具

https://babeljs.io/repl/

返回

关于Eslint

ESLint 是一个开源的 JavaScript 代码检查工具.代码检查是一种静态的分析,常用于寻找有问题的模式或者代码,并且不依赖于具体的编码风格.对大多数编程语言来说都会有代码检查,一般来说编译程序会内置检查工具.

JavaScript 是一个动态的弱类型语言,在开发中比较容易出错.且是实时编译,为了改变开发方式,提升开发效率,需要一种语法检查工具.

ESLint 为了让开发者可以创建自己的检测规则,所有规则都被设计成可插入的.ESLint 的默认规则与其他的插件无太大区别,规则本身和测试可以依赖于同样的模式.在项目中可以使用ESLint 内置规则,也可以在使用过程中自定义规则.

ESLint是基于Node.js,使用node.js安装部署.

所有规则都是可拔插的

  • 内置规则和自定义规则共用一套规则 API
  • 内置的格式化方法和自定义的格式化方法共用一套格式化 API
  • 额外的规则和格式化方法能够在运行时指定
  • 规则和对应的格式化方法并不强制捆绑使用

每条规则:

  • 各自独立
  • 可以开启或关闭
  • 可以将结果设置成警告或者错误
  • ESLint 规则可根据需要自由定制
  • 所有内置规则都是泛化的
  • 在本项目中使用Airbnb规则

关于eslint-config-airbnb规则

eslint-config-airbnb规则是Airbnb公司开源的基于ESLint的规则,使用起来较为方便,且可以更好的保持代码风格一致性,可读性,可维护性

Airbnb Javascript规则

https://github.com/airbnb/javascript

返回目录

eslint使用

安装eslint,在这里使用全局的方式,也可以仅在项目中安装

$ npm i -g eslint

安装Airbnb语法规则.

$ npm i -g eslint-config-airbnb

在项目的根目录下新建一个.eslintrc文件,配置ESLint,格式如下

{
  "extends": "eslint-config-airbnb",
  "rules": {}
}

rules可以配置需要过滤的规则

{
  "extends": "eslint-config-airbnb",
  "rules": {
    "no-console": 1
  }
}

返回

关于React

React是一个为数据提供渲染,HTML的视图的开源 JavaScript 库.React视图通常采用包含以自定义HTML 标记规定的其他组件的组件渲染.React 为开发者提供了一种子组件不能直接影响外层组件 ("data flows down") 的模型,数据改变时对HTML文档的有效更新,和现代单页应用中组件之间干净的分离

React提出了虚拟DOM的概念(virtual DOM)即React组件并不是真实的DOM节点,而是存在于内存之中的一种数据结构.只有当它插入文档以后,才会变成真实的DOM.根据React的设计,所有的DOM变动,都先在虚拟DOM上发生,然后再将实际发生变动的部分,反映在真实DOM上,这种算法叫做DOM diff,它可以极大提高网页的性能表现

返回目录

关于Webpack

Webpack是一个模块打包器.它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源.

返回目录

Webpack特点

  • 代码拆分

Webpack有两种组织模块依赖的方式,同步和异步.异步依赖作为分割点,形成一个新的块.在优化了依赖树后,每一个异步区块都作为一个文件被打包.

  • Loader

Webpack本身只能处理原生的JavaScript模块,但是loader转换器可以将各种类型的资源转换成JavaScript模块.这样,任何资源都可以成为Webpack可以处理的模块.

  • 智能解析

Webpack有一个智能解析器,几乎可以处理任何第三方库,无论它们的模块形式是CommonJS/AMD还是普通的JS文件.甚至在加载依赖的时候,允许使用动态表达式`require("./templates/" + name + ".jade").

  • 插件系统

Webpack还有一个功能丰富的插件系统.大多数内容功能都是基于这个插件系统运行的,还可以开发和使用开源的Webpack插件,来满足各式各样的需求.

  • 快速运行

Webpack使用异步I/O和多级缓存提高运行效率,这使得Webpack能够快速增量编译.

接下来是在本项目中的应用

返回

返回目录

初始化项目

新建项目目录,并配置项目(package.json)

创建项目目录,并初始化

$ mkdir test-react
$ cd test-react
$ npm init 

初始化package.json

执行完npm init命令,需要填写项目配置package.json,请注意不可忽略的选项

name: (test-react) //项目名称,可忽略
version: (1.0.0) //项目版本,可忽略
description: Study react.js // 项目描述,可忽略
entry point: (index.js) // 默认入口文件,可忽略
test command: // 测试用命令,可忽略
git repository: //git仓库,可忽略
keywords: react// 可忽略
author: Jun // 作者
license: (ISC) // 开源协议,可忽略

至此完成package.json初始化配置

package.json初始化配置清单
{
  "name": "test-react",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "react"
  ],
  "author": "Jun",
  "license": "ISC"
}

返回

配置项目完整清单

加入React依赖包

"dependencies": {
  "lodash": "4.16.4",
  "react": "15.2.1",
  "react-dom": "15.2.1"
}

加入webpack工具

"devDependencies": {
  "extract-text-webpack-plugin": "~0.8.2",
  "webpack": "^1.7.3",
  "webpack-dev-server": "^1.16.2"
}

加入babel编译工具

"devDependencies": {
  "babel-core": "^6.5.2",
  "babel-eslint": "^4.1.8",
  "babel-loader": "^6.2.2",
  "babel-plugin-add-module-exports": "^0.1.2",
  "babel-plugin-transform-runtime": "^6.15.0",
  "babel-preset-es2015": "^6.18.0",
  "babel-preset-es2015-ie": "6.x",
  "babel-preset-react": "^6.3.13",
  "babel-preset-stage-1": "^6.16.0",
  "babel-register": "^6.18.0",
  "extract-text-webpack-plugin": "~0.8.2",
  "webpack": "^1.7.3",
  "webpack-dev-server": "^1.16.2"
}

加入代码校验工具ESLint

"devDependencies": {
  "babel-core": "^6.5.2",
  "babel-eslint": "^4.1.8",
  "babel-loader": "^6.2.2",
  "babel-plugin-add-module-exports": "^0.1.2",
  "babel-plugin-transform-runtime": "^6.15.0",
  "babel-preset-es2015": "^6.18.0",
  "babel-preset-es2015-ie": "6.x",
  "babel-preset-react": "^6.3.13",
  "babel-preset-stage-1": "^6.16.0",
  "babel-register": "^6.18.0",
  "eslint": "^1.10.3",
  "eslint-config-airbnb": "^5.0.1",
  "eslint-loader": "^1.6.3",
  "eslint-plugin-react": "^3.16.1",
  "extract-text-webpack-plugin": "~0.8.2",
  "webpack": "^1.7.3",
  "webpack-dev-server": "^1.16.2"
}

返回

配置Webpack开发环境(webpack-dev-server)

package.json中按下配置

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "webpack-dev-server --devtool eval --progress --colors --open --hot --content-base ./example",
  "build": "webpack -p --colors"
}

关于webpack/webpack-dev-server

在本项目中会用到的webpack/webpack-dev-server命令说明

webpack

Command Description
webpack 主命令:执行编译/混合/CSS样式,开发模式,代码未压缩处理,并包含webpack相关编译代码
-p 主命令:执行编译/压缩/混合/CSS样式,不包含webpack相关编译代码
-w 执行编译/混合/CSS样式,开发模式,实时监听代码变化,并进行编译/压缩/混合等一系列热部署操作
-p --color 主命令:执行编译/压缩/混合/CSS样式,不包含webpack相关编译代码,并高亮显示控制台输出结果
-h 查看更多webpack命令

webpack-dev-server

Command Description
webpack-dev-server 主命令:启动webpack开发调试服务
--devtool eval 启用开发者模式,编译后代码包含sourcemap等信息,可用于浏览器进行调试
--progress 显示webpack building进度
--colors 高亮显示控制台输出结果
--open 浏览器自动刷新
--hot webpack服务实时监听
--content-base ./example webpack服务启动入口html文件目录设置,例如example目录

至此package.json配置完成,在之后的开发中如果需要其他配置和依赖包,可按照如上步骤

返回

完整package.json清单

{
  "name": "test-react",
  "version": "0.0.1",
  "description": "study react",
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --devtool eval --progress --colors --open --hot --content-base ./example",
    "build": "webpack -p --colors"
  },
  "keywords": [
    "react"
  ],
  "author": "jun",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.5.2",
    "babel-eslint": "^4.1.8",
    "babel-loader": "^6.2.2",
    "babel-plugin-add-module-exports": "^0.1.2",
    "babel-plugin-transform-runtime": "^6.15.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-es2015-ie": "6.x",
    "babel-preset-react": "^6.3.13",
    "babel-preset-stage-1": "^6.16.0",
    "babel-register": "^6.18.0",
    "eslint": "^1.10.3",
    "eslint-config-airbnb": "^5.0.1",
    "eslint-loader": "^1.6.3",
    "eslint-plugin-react": "^3.16.1",
    "extract-text-webpack-plugin": "~0.8.2",
    "style-loader": "^0.13.1",
    "webpack": "^1.7.3",
    "webpack-dev-server": "^1.16.2"
  },
  "dependencies": {
    "lodash": "^4.16.4",
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  }
}

完成上面的步骤,执行如下命令安装依赖包

$ npm install

在项目根目录下生成node_modules文件夹,用于存放所有node依赖包

配置编译工具Babel

新建.babelrc文件,配置清单如下

{
    "presets": [
        "es2015",
        "react"
    ],
    "plugins": [
        "transform-runtime"
    ]
}

新建.eslintignore文件,配置清单如下

node_modules

返回

由于并不需要让babel编译依赖包目录node_modules,所以需要进行配置

配置语法检查工具eclint

{
    "env": {
    "node": true,
    "es6": true,
    "jquery": true,// 对jquery做处理
    "browser": true // 支持使用浏览器js全局变量window
  },
  "parser": "babel-eslint",
  "extends": "eslint-config-airbnb",
  "rules": {
    "no-console": 1
  }
}

返回

新建工程目录src

$ mkdir src

新建静态资源目录

src目录下新建组件目录components,静态资源图片目录img,静态html页面资源目录view

$ mkdir src
$ mkdir src/components
$ mkdir src/img
$ mkdir src/view

配置出口文件

src新建出口文件index.js和用于测试的出口文件index.test.js

$ touch index.js
$ touch index.test.js

windows用户(windows 10)请用bash命令,再使用touch

返回

返回目录

webpack配置(项目构建)

在这里使用Webpack对项目进行打包/压缩/混合等操作,所以需要配置Webpack清单,详细如下

初始化

引入node path模块,用于获取文件路径,引入ExtractTextPlugin外部加载文件插件,初始化node依赖包路径

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const nodeModulesPath = path.resolve(__dirname,'node_modules');

这里使用ECMAScript 2015不可随意修改的变量类型const,具有块级作用域的作用,可避免var声明的变量存在变量提升和随意修改的问题

使用module.export输出配置

module.exports = {
  //webpack配置清单
}

设置devtool属性为false,在webpack打包时不生成sourcemap信息

module.exports = {
  devtool: false
}

设置webpack入口文件,即编译入口文件

entry: {
  'TestReact.test': path.join(__dirname,'src','index.test.js'),// demo测试程序入口文件
  TestReact: path.join(__dirname,'src','index.js'),
}

设置输出文件目录以及chunk文件

output: {
  path: path.join(__dirname,'dist'),// 输出目录(编译生成文件目录)
  publicPath: '',
  filename: 'js/[name].js',// 编译生成的文件,文件名由前面入口文件配置确定
  chunkFilename: 'js/[id].chunk.js',
}

返回

模块和插件配置

配置ESLint预加载,用于语法检查
module: {
  preLoaders: [
    {
      // ESlint loader
      test: /\.(js|jsx)$/,
      loader: 'eslint-loader',
      include: [path.resolve(__dirname,'src')],
      exclude: [nodeModulesPath],
    },
  ]
}

返回

配置模块插件

配置加载模块插件,在本项目中仅编译js所以仅加载js的编译工具,同时排除node.js依赖包的编译

module: {
  preLoaders: [
    {
      // ESlint loader
      test: /\.(js|jsx)$/,
      loader: 'eslint-loader',
      include: [path.resolve(__dirname,'src')],// 处理src工程目录下所有js文件
      exclude: [nodeModulesPath],
    },
  ],
  loaders: [
    { 
      test: /\.js?$/,// 用于处理所有js文件
      exclude: /node_modules/,
      loader: 'babel',// 使用babel
    },
  ]
}

返回

外部引入react/react-dom

为了便于更好的扩展性,且同时编译react/react-dom,生成的文件会很大,比较消耗资源,在页面中加载数MB的js文件并不理想,所以在这里进行如下配置,可以将react/react-dom通过CDN依赖等外部引入的方式加载至页面

externals: {    // 指定采用外部 CDN 依赖的资源,不被webpack打包
  react: 'React',
  'react-dom': 'ReactDOM',
}

返回

配置webpack-dev-server服务端口

webpack-dev-server也可以在这里配置,包括服务监听端口号

devServer: {
  hot: true,
  inline: true,// webpack-dev-server有两种模式,默认是false,即在页面中加入frame标签构建调试页面;若为true则是在完整页面中构建调试页面
  progress: true,
  port: '3001',
}

返回

加载ESLint配置文件

在上面步骤中已预加载ESLint模块,在这里需要加入ESLint配置文件

eslint: {
  configFile: '.eslintrc',
}

返回

完整webpack清单

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const nodeModulesPath = path.resolve(__dirname,'node_modules');

module.exports = {
  devtool: false,
  entry: {
    'TestReact.test': path.join(__dirname,'src','index.test.js'),// demo测试程序入口文件
    TestReact: path.join(__dirname,'src','index.js'),
  },
  output: {
    path: path.join(__dirname,'dist'),
    filename: 'js/[name].js',
    chunkFilename: 'js/[id].chunk.js',
  },
  module: {
    preLoaders: [
      {
        // Eslint loader
        test: /\.(js|jsx)$/,
        loader: 'eslint-loader',
        include: [path.resolve(__dirname,'src')],
        exclude: [nodeModulesPath],
      },
    ],
    loaders: [
      {
        test: /\.js?$/,
        exclude: /node_modules/,
        loader: 'babel',
      },
    ],
  },
  externals: { // 指定采用外部 CDN 依赖的资源,不被webpack打包
    react: 'React',
    'react-dom': 'ReactDOM',
  },
  plugins: [],
  devServer: {
    hot: true,
    inline: true,
    progress: true,
    port: '3001',
  },
  eslint: {
    configFile: '.eslintrc',
  },
};

请注意,在之后的开发中可以灵活配置入口和出口文件,这样即可以配置多文件编译

返回

配置webpack-dev-server入口html页面

package.json中有如下配置

"scripts": {
    // ...
    "dev": "webpack-dev-server --devtool eval --progress --colors --open --hot --content-base ./example",
}

所以在example目录中新建index.html

$ touch index.html

index.html中用CDN链接引入react.js\ react-dom.js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Test react</title>
    <meta name="description" content="Test react" />
    <script src="https://npmcdn.com/react@15.3.1/dist/react.js"></script>
  <script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
</head>

<body>
    
</body>

</html>

定义存放react组件的容器

<div id="example" ></div>

接下来引入编译好的TestReact.test.js,请注意根据前面配置的webpack清单,index.test.js编译后生成TestReact.test.js,且运行在webpack-dev-server服务缓存中,所以在这里需要使用服务url地址

<script src="http://localhost:3001/js/TestReact.test.js"></script>

请注意react.js/react-dom.js加载顺序

组件js代码一定要在容器之后引入,否则会报错

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Test react</title>
    <meta name="description" content="Test react" />
    <script src="https://npmcdn.com/react@15.3.1/dist/react.js"></script>
  <script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
</head>

<body>
    <div id="example" ></div>
    <script src="http://localhost:3001/js/TestReact.test.js"></script>
</body>

</html>

返回

执行构建/编译

启动webpack-dev-server开发环境
$ npm run dev

自动打开浏览器并跳转至如下地址

localhost:3001 // 在上面的package。json webpack-dev-server配置中设置端口

至此webpack-dev-server服务启动成功,浏览器当前页面就是example目录下的index.html 当工程目录下的相关js文件发生改变时webpack实时编译,webpack-dev-server通知浏览器自动刷新页面

webpack-dev-server与浏览器建立websocket实时通信

返回

构建/编译
$ npm run build

可以直接使用webpack命令编译

$ webpack

也可以如下命令进行压缩

$ webpack -p

最终编译生成的文件目录如下

dist/js/

返回

项目目录结构

├─dist
│  └─js
│          TestReact.js
│          TestReact.test.js
├─doc
│  └─img
├─example
|          index.html
├─node_modules
├─src
|   │  index.js
|   │  index.test.js
|   ├─components
|   │  └─test
|   │        Example.js
|   ├─img
|   └─view
│  .babelrc
│  .eslintignore
│  .eslintrc
│  package.json
│  README.md
│  webpack.config.js

返回目录

目录结构说明

  • dist: 存放编译好的js文件,由webpack配置生成
  • doc/img: 存放文档资源文件
  • example: 存放demo/实例文件
  • node_modules: node依赖包,包含react/lodash/webpack等
  • src: 项目开发目录
  • src/components: 存放组件
  • src/view: 静态页面资源目录
  • src/index.js: 分页组件导出
  • src/index.test.js: 分页组件demo实例
  • .babelrc: babel编译工具配置文件
  • .eslintignore: ESLint工具过滤器配置文件
  • .eslintrc: ESLint工具配置文件
  • webpack.config.js: webpack配置文件
  • README.md: 项目说明文档
  • package.json: 项目配置文件

返回

具体实现

接下来是react基本用法和简单组件的实现

返回目录

Hello World

在页面上打印Hello World,即第一个组件

新建HelloWorld.js文件

components目录新建test目录,并在Test目录下新建HelloWorld.js文件

$ cd components
$ mkdir test
$ touch HelloWorld.js

返回

具体实现

HelloWorld.js中引入react.js

import React,{ Component } from 'react';

这里使用ECMAScript 2015标准规定的模块导入import,即引入外部js模块. import命令接受一对大括号,里面指定要从其他模块导入的变量名.大括号里面的变量名,必须与被导入模块对外接口的名称相同

定义HelloWorld类,并继承React.Component,请注意在react/ECMAScript 2015中定义组件(声明组件)时,组件名称(类名)首字母要大写,遵循驼峰命名

class HelloWorld extends Component {

}

请注意这里使用ECMAScript 2015标准规定的类(class)的用法,在ES5中声明类即是原型

function Test(){}

类的属性即是对原型的操作

Test.prototype.method = {}

继承父类就是对原型赋值

function fatherClass(){
  this.flag = true; // 父类属性
}

Test.prototype = new fatherClass();

Test.prototype.method = function(){
  console.log(this.flag);
}

var t = new Test();

t.method();

// true

子类继承父类属性,且子类不能向父类传值;ES6(ECMAScript 2015)是同样的,但是需要写构造方法继承父类的this对象

class HelloWorld extends Component {
  constructor(props){
    super(props)
  }
}

接下来渲染组件,使用reactJSX模板语言,在render方法中return html DOM标签

请注意react单一组件包含多个子组件(标签)时需要设置key作为标识符

class HelloWorld extends Component {
  render() {
    return (
      <div>
        <h3 key={`title0`}>Example 1. Hello world</h3>
        <h4 key={`title1`}>Hello World!</h4>
      </div>
    );
  }
}

最后导出HelloWorld组件类

export default HelloWorld;

ECMAScript 2015标准中模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入他模块提供的功能 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取.如果希望外部能够读取模块内部的某个变量/方法/类等,就必须使用export关键字输出 使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块

导出多个变量

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName,lastName,year};

在本例中,使用默认指定模块导出

export default ...

接下来使用ReactDOMHelloWorld组件渲染至页面

index.test.js中引入react/react-dom以及HelloWorld组件

import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/test/HelloWorld';

渲染组件,将组件绑定到容器中

ReactDOM.render(
  <HelloWorld />,
  document.getElementById('example1')
);

如果按照上面的步骤配置了webpack-dev-server入口页面那么此时已经能够看到自动刷新后的页面了,如下图所示

webpack-dev-server运行状态以及react最终渲染到页面的dom如下图所示

webpack后台热编译如图所示

返回

React JSX模板用法

JSX模板是react.js的基本工具,可以与js混写,即在html标签中嵌入js代码代码块,允许直接插入js变量

在具体实现

component/jsx/新建JSX.js文件

$ touch Example.js

同上引入react并新建JSX

import React,{ Component } from 'react';

class JSX extends Component {

}

render方法里面写如下html标签

class JSX extends Component {
  render() {
    return (
      <div>
        <h3 key={`title0`}>Example 2. JSX</h3>
        <h2 key={`title3`}>Hello World!</h2>
        <p key={`p`}>This is about react JSX </p>
      </div>
    );
  }
}

请注意render方法不可以返回多个html标签,必须由父节点包裹子节点,如下代码是错误的

render() {
    return (
      <div title={1}></div>
      <div title={1}></div>
      <div title={1}></div>
    );
  }

JSX模板加入js变量value

class JSX extends Component {
  render() {
    const value = 'This is a js value';
    return (
      <div>
        <span>Example 2</span>
        <h2>Hello World!</h2>
        <p>This is about react JSX </p>
        value:{value}
      </div>
    );
  }
}

如果变量是数组,则会遍历数组然后均显示出来,加入number数组array变量和DOM数组arrDOM变量

class JSX extends Component {
  render() {
    const value = 'This is a js value';
    const array = [1, 2, 3, 4];
    const arrDOM = [
      <h4 key={`title1`}>Hello JSX!</h4>,
      <h5 key={`title2`}>React is awesome</h5>,
    ];
    return (
      <div>
      <h3 key={`title0`}>Example 2. JSX</h3>
        <h2 key={`title3`}>Hello World!</h2>
        <p key={`p`}>This is about react JSX </p>
        value:{value}
        <p key={`content`}>This is a number array: {`[${array.join(',')}]`}</p>
        <br key={`br`}/>
        This is DOM array:
        {arrDOM}
      </div>
    );
  }
}

在这里使用了ES6语法中的模板字符串,用法如下 上面的字符串拼接代码在ES5中可以这样写

'[' + array.join(',') + ']';

ES6中使用两个反引号` `将字符串内容包裹起来,同时使用${ }包裹js表达式,例如

var a = 17.5;
var b = 9;
console.log(`a*b=${a*b} a+b=${a+b} a/b=${a/b}`);

// a*b=157.5 a+b=26.5 a/b=1.9444444444444444

导出组件

export default JSX;

渲染至页面

// src/index.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import JSX from './components/jsx/JSX';

// ...

ReactDOM.render(
  <JSX />,
  document.getElementById('example2')
);
<!--  example/index.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Test react</title>
    <meta name="description" content="Test react" />
    <script src="https://npmcdn.com/react@15.3.1/dist/react.js"></script>
  <script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
</head>

<body>
    <div id="example1" ></div>
    <br />
    <div id="example2" ></div>
    <script src="http://localhost:3001/js/TestReact.test.js"></script>
    
</body>
</html>

效果如下

返回

组件属性:props用法

component/props目录中新建Props.js文件,并定义组件,添加构造方法来继承React.Component父类this对象包括this.props属性

import React,{ Component } from 'react';

class Props extends Component {
  constructor(props) {
    super(props);
  }

  render() {
  }
}

export default Props;

render方法中对props按如下方式处理,请注意react中封装了html标签的基本属性,例如onClick,type,title等,像标签的class属性即为className,即class作为关键字不可以作为组件属性名使用

render() {
    const props = this.props;
    return (
      <div>
        <div>
        <h3 key={`title0`}>Example 3. Props</h3>
        <h4 key={`title1`} className = {props.className}>The className is:{props.className}</h4>
      </div>
    );
  }

在使用ReactDOM.render渲染组件的时候可以自行配置props,即组件对外提供API实现一系列的操作,在这里定义className props属性为test

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

// ...

import Props from './components/props/Props';

// ...

ReactDOM.render(
  <Props className = {'test'}/>,
  document.getElementById('example3')
);
<!--  example/index.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Test react</title>
    <meta name="description" content="Test react" />
    <script src="https://npmcdn.com/react@15.3.1/dist/react.js"></script>
  <script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
</head>

<body>
    <!-- ...components -->
    <br />
    <div id="example3" ></div>
    <script src="http://localhost:3001/js/TestReact.test.js"></script>
    
</body>
</html>

页面最终效果

返回

this.props.children属性

this.props.children它表示组件的所有子节点,可以使用React.Children.map遍历子节点,在回调函数中返回结果集

import React,{ Component } from 'react';

class Props extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const props = this.props;
    return (
      <div>
        <h3 key={`title0`}>Example 3. Props</h3>
        <h4 key={`title1`} className = {props.className}>The className is:{props.className}</h4>
        <ul key={`content`}>
          {
            React.Children.map(props.children, (child, i) => (<li key={i}>{child}</li>))
          }
        </ul>
      </div>
    );
  }
}

export default Props;

这里需要注意,this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined;如果有一个子节点,数据类型是 object;如果有多个子节点,数据类型就是 array.所以,处理 this.props.children 的时候要小心

ReactDOM.render渲染组件,即是将子组件嵌入Props组件中

// index.test.js

ReactDOM.render(
  <Props className = {'test'}>
    <li key={0}>test1</li>
    <li key={1}>test2</li>
    <li key={2}>test3</li>
    <li key={3}>test4</li>
  </Props>,
  document.getElementById('example3')
);

页面最终效果

返回

组件类的PropTypes属性

在组件的属性(props)可以接受任意类型值,字符串、对象、函数等,所以需要做约束并且可以赋默认值,如果类型不符合PropTypes已设定好的类型则报错

components/props目录下新建PropTypes.js,并定义PropTypes组件类,继承React.Component父类,定义构造方法,继承this对象,实现上面Props示例组件中的render方法

import React, { Component } from 'react';

class PropTypes extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const props = this.props;
    return (
      <div>
        <span>Example 3.1</span>
        <h3 className = {props.className}>The className is:{props.className}</h3>
        The test props is {props.test}
        <br />
        The num props is {props.num}
        <ul>
          {
            React.Children.map(props.children, (child) => (<li>{child}</li>))
          }
        </ul>
      </div>
    );
  }
}

export default PropTypes;

定义PropTypes属性

PropTypes.PropTypes = {
  test: React.PropTypes.string, // 规定test属性类型为string
  num: React.PropTypes.number, // 规定num属性类型为number
};

定义PropTypes 默认值

PropTypes.defaultProps = {
  test: 'test',
  num: 1,
};

渲染组件

ReactDOM.render(
  <PropTypes className = {'test'} num={2} />,
  document.getElementById('example3_1')
);

页面最终效果

请注意num默认值被重新赋值为2

返回

获取真实的DOM节点

上面所有组件均不是真实的DOM,是存在于内存中的虚拟DOM,只有真正插入至文档中才是真实的DOM.react的核心原理是所有的DOM结构变化均是内存中虚拟DOM结构变化,然后将实际变动结构渲染至文档真实DOM结构中,此时如果需要获取真实的DOM结构,需要使用ref属性

定义RealDOM react组件类,并定义ref属性为test如下所示

import React, { Component } from 'react';

class RealDOM extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <span>Example 4</span>
        <h3 ref={`test`}>This is real DOM </h3>
      </div>
    );
  }
}

export default RealDOM;

在这里,定义点击事件钩子函数获取ref属性为的test的真实DOM结构的innerHTML值(This is real DOM ),如下所示

// 定义钩子函数_handleClick,并绑定
class RealDOM extends Component {
  constructor(props) {
    super(props);

    [
      'render',
      '_handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  _handleClick() {
    alert(`The content of the real DOM is: \n${this.refs.test.innerHTML}`);
  }

  render() {
    // ...
  }
}
// 定义button按钮设置onClick事件

render() {
    return (
      <div>
        <h3 key={`title0`}>Example 4. Real DOM</h3>
        <h4 key={`title1`} ref={`test`}>This is real DOM </h4>
        <button key={`title2`} onClick={this._handleClick}>button</button>
      </div>
    );
  }

在这里h3ref值是test,用this.refs就能取得真实的DOM结构,但是请注意根据以上逻辑,只有当虚拟DOM真正插入文档中时才能获取,即可通过事件获取

页面最终效果

返回

状态

react核心思想之一就是监听状态变化(即状态机)引发组件重新渲染,且根据需要设置初始状态

即使用this.state,this.state初始值是一个空对象,即可以给this.state赋任意属性,并设置初始状态,使用this.setState()方法改变状态

// 设置初始状态
this.state.isDone = false 

// ...

// 更改状态
this.setState({
  isDone:false,
});

下面用一个组件例子说明,定义State组件类

class State extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const _state = this.state;
    return (
      
    );
  }
}

State.propTypes = {
};

State.defaultProps = {
};

export default State;

加入button标签,通过点击事件改变组件状态

return (
      <div>
        <h3 key={`title0`}>Example 5. State</h3>
        <br key={`title1`}/>
        The state is: {_state.text}
        <br key={`title2`} />
        <button key={`title3`} onClick = {this._handleClick}>button</button>
      </div>
    );

定义props属性,text,并设置默认值为start,同时在构造函数中初始化状态

class State extends Component {
  constructor(props) {
    super(props);

    this.state = {
      text: props.text,
    };
  }

  render() {
    const _state = this.state;
    return (
      <div>
        <h3 key={`title0`}>Example 5. State</h3>
        <br key={`title1`}/>
        The state is: {_state.text}
        <br key={`title2`} />
        <button>button</button>
      </div>
    );
  }
}

State.propTypes = {
  text: React.PropTypes.string,
};

State.defaultProps = {
  text: 'start',
};

export default State;

定义点击事件钩子函数,用于改变状态

_handleClick() {
    let state = '';
    if (this.state.text === 'start') {
      state = 'end';
    } else {
      state = 'start';
    }

    this.setState({
      text: state,
    }, () => {
      console.log(this.state.text);
      document.getElementById('contentId').append(`${this.state.text}\n`);
    });
  }

在构造函数中为钩子函数绑定this对象

[
  'render',
  '_handleClick',
].forEach((method) => this[method] = this[method].bind(this));

给按钮onClick绑定钩子函数

<button key={`title3`} onClick = {this._handleClick}>button</button>

将组件渲染至页面

ReactDOM.render(
  <State />,
  document.getElementById('example5')
);

最终效果如下

返回

组件生命周期

根据react原理,每个组件均有生命周期.组件生命周期包括如下三种状态

* Mounting:已插入真实 DOM
* Updating:正在被重新渲染
* Unmounting:已移出真实 DOM

同时对应这几个状态分别有如下几种方法

componentWillMount() // 即将插入真实`DOM`之前
componentDidMount()  // 完成插入真实`DOM`
componentWillUpdate(object nextProps, object nextState) // 组件即将被渲染之前
componentDidUpdate(object prevProps, object prevState)  // 组件完成渲染
componentWillUnmount()  // 移除真实`DOM`

还有如下两种方法

componentWillReceiveProps(object nextProps)  // 已加载组件收到新的参数时调用
shouldComponentUpdate(object nextProps, object nextState) // 组件判断是否重新渲染时调用

componentWillMount用法

在插入真实DOM之前执行,并且允许使用setState方法改变状态,允许最后一次修改状态,且不会发生重新渲染

示例如下

定义ComponentWillMount组件类,定义构造方法,声明componentWillMount方法,设置初始状态teststart

class ComponentWillMount extends Component {
  constructor(props) {
    super(props);
  }

  render() {
  
  }
}

export default ComponentWillMount;

实现componentWillMount方法,打印初始状态,然后再改变状态,将状态test改为end

componentWillMount() {
   console.log(`[Method] componentWillMount\n[State test] ${this.state.test}`);
   document.getElementById('contentId_2')
           .append(`[Method] componentWillMount\n[State test] ${this.state.test}\n`);
   this.setState({
     test: 'end',
   });
 }

实现render方法,打印状态

render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    document.getElementById('contentId_2')
            .append(`[Method] render\n[State test] ${this.state.test}`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.1 componentWillMount</h3>
        <br />
        Render this component,state:{this.state.test}
      </div>
    );
  }

将组件渲染至页面

// index.test.html 

ReactDOM.render(
  <ComponentWillMount />,
  document.getElementById('example6_1')
);

效果如下

返回

componentDidMount用法

当组件完成插入真实DOM后执行,请注意此处不能使用'this.setState'方法,否则会陷入死循环,组件会被不停地改变状态刷新组件

示例如下

定义ComponentDidMount组件类,初始化状态this.state.test为'start',实现ComponentDidMount方法和render方法,并打印前后状态

class ComponentDidMount extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };
  }

  componentDidMount() {
    console.log(`[Method] componentDidMount\n[State test] ${this.state.test}`);
    document.getElementById('contentId_3')
           .append(`[Method] componentDidMount\n[State test] ${this.state.test}\n`);
  }

  render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    document.getElementById('contentId_3')
           .append(`[Method] render\n[State test] ${this.state.test}\n`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.2 componentDidMount</h3>
        <br />
        The state is: {this.state.test}
      </div>
    );
  }

}

将组件渲染至页面

ReactDOM.render(
  <ComponentDidMount />,
  document.getElementById('example6_2')
);

效果如下

返回

componentWillUpdate

组件即将被渲染之前执行,可以通过事件触发状态改变,刷新组件

示例如下

定义ComponentWillUpdate组件类,定义构造方法,声明ComponentWillUpdate方法,设置初始状态teststart,实现ComponentDidMount方法和render方法,并打印前后状态

class ComponentWillUpdate extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };
  }

  componentWillUpdate() {
    console.log(`[Method] componentWillUpdate\n[State test] ${this.state.test}`);
  }

  render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    return (
      <div>
        <span key={`title0`}>Example 6.4 componentWillUpdate</span>
        <br />
        Render this component,state:{this.state.test}
        <button key={`title1`}>updateComponent</button>
      </div>
    );
  }
}

实现点击事件钩子函数,并给按钮绑定钩子函数

class ComponentWillUpdate extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };

    [
      'render',
      '_handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  componentWillUpdate() {
    console.log(`[Method] componentWillUpdate\n[State test] ${this.state.test}`);
    document.getElementById('contentId_4')
           .append(`[Method] componentWillUpdate\n[State test] ${this.state.test}\n`);
  }

  _handleClick() {
    console.log(`[Method] _handleClick \n[State test] ${this.state.test}`);
    document.getElementById('contentId_4')
           .append(`[Method] _handleClick\n[State test] ${this.state.test}\n`);
    this.setState({
      test: 'end',
    }, () => {
      console.log(`[Method] _handleClick callback \n[State test] ${this.state.test}`);
      document.getElementById('contentId_4')
           .append(`[Method] _handleClick callback\n[State test] ${this.state.test}\n`);
    });
  }

  render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    document.getElementById('contentId_4')
           .append(`[Method] render\n[State test] ${this.state.test}\n`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.3 componentWillUpdate</h3>
        <br />
        Render this component,state:{this.state.test}
        <br />
        <button key={`title1`} onClick={this._handleClick}>updateComponent</button>
      </div>
    );
  }
}

渲染组件

ReactDOM.render(
  <ComponentWillUpdate />,
  document.getElementById('example6_3')
);

效果如下

返回

componentDidUpdate用法

组件完成渲染后执行

定义ComponentDidUpdate组件类,按照上面的ComponentWillUpdate组件实现类似的方法和配置

class ComponentDidUpdate extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };

    [
      'render',
      '_handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  componentDidUpdate() {
    console.log(`[Method] componentDidUpdate\n[State test] ${this.state.test}`);
    document.getElementById('contentId_5')
           .append(`[Method] componentDidUpdate\n[State test] ${this.state.test}\n`);
  }

  _handleClick() {
    console.log(`[Method] _handleClick \n[State test] ${this.state.test}`);
    document.getElementById('contentId_5')
           .append(`[Method] _handleClick\n[State test] ${this.state.test}\n`);
    this.setState({
      test: 'end',
    }, () => {
      console.log(`[Method] _handleClick callback \n[State test] ${this.state.test}`);
      document.getElementById('contentId_5')
           .append(`[Method] _handleClick callback\n[State test] ${this.state.test}\n`);
    });
  }

  render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    document.getElementById('contentId_5')
           .append(`[Method] render\n[State test] ${this.state.test}\n`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.4 componentDidUpdate</h3>
        <br />
        Render this component,state:{this.state.test}
        <br />
        <button key={`title1`} onClick={this._handleClick}>updateComponent</button>
      </div>
    );
  }
}

效果如下

返回

componentWillUnmount用法

移除真实DOM时执行

定义ComponentWillUnmount组件类,用ReactDOM.unmountComponentAtNode移除组件

class ComponentWillUnmount extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };
  }

  componentWillUnmount() {
    console.log(`[Method] componentWillUnmount\n The component is removed!`);
    document.getElementById('contentId_6')
           .append(`[Method] componentWillUnmount\n The component is removed!`);
  }

  render() {
    console.log(`[Method] render\n The component is rendered!`);
    document.getElementById('contentId_6')
           .append(`[Method] render\n The component is rendered!\n`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.5 componentWillUnmount</h3>
        <br />
        Render this component,state:{this.state.test}
        <br />
      </div>
    );
  }
}
ReactDOM.render(
  <ComponentWillUnmount />,
  document.getElementById('example6_5')
);

ReactDOM.unmountComponentAtNode(document.getElementById('example6_5'));

效果如下

返回

componentWillReceiveProps用法

已加载组件收到新的props参数时执行

示例如下

定义ComponentWillReceiveProps组件类,并定义props.test属性,初始化state,并将props.test传递给this.state.test,通过点击事件触发状态改变,同时需要外部调用ComponentWillReceiveProps组件类,并且将点击事件传递至ComponentWillReceiveProps组件类,且使该组件接收到新的props

class ComponentWillReceiveProps extends Component {
  constructor(props) {
    super(props);

    // 初始化test
    let test = 'start';

    // 通过props初始化test
    if ('test'in props) {
      test = props.test;
    }

    // 初始化state
    this.state = {
      test,
    };

    // 绑定this对象
    [
      'render',
      '_handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  componentWillReceiveProps(nextProps) {
    if ('test' in nextProps) {
      document.getElementById('contentId_7')
           .append(`[Method] componentWillReceiveProps\n[State test] ${this.state.test}\n`);
      this.setState({
        test: nextProps.test,
      });
    }
  }

  // 定义钩子函数,由外部传递事件函数
  _handleClick() {
    document.getElementById('contentId_7')
           .append(`[Method] _handleClick\n[State test] ${this.state.test}\n`);
    this.props._handleClick();
  }

  render() {
    return (
      <div>
        <span key={`title0`}>Example 6.6 componentWillReceiveProps</span>
        <br />
        <button onClick = {this._handleClick}>new props</button>
        <br />
        The state is: {this.state.test}
      </div>
    );
  }
}
// 初始化propTypes
ComponentWillReceiveProps.propTypes = {
  test: React.PropTypes.string,
  _handleClick: React.PropTypes.func,
};

// 函数类型的propTypes必须有默认值,这里赋空函数
ComponentWillReceiveProps.defaultProps = {
  _handleClick: () => (null),
};
// index.test.js

class Test extends Component {

  constructor(props) {
    super(props);

    // 初始化状态
    this.state = {
      test: 'start',
    };

    [
      'render',
      'handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  // 改变状态钩子事件函数
  handleClick() {
    this.setState({
      test: 'end',
    });
  }

  // 调用ComponentWillReceiveProps组件并设置test/_handleClick属性
  render() {
    return (
      <ComponentWillReceiveProps test = {this.state.test} _handleClick = {this.handleClick} /> 
    );
  }
}

// 渲染组件
ReactDOM.render(
  <Test />,
  document.getElementById('example6_6')
);

请注意此处使用了react组件复用性的特点

效果如下

返回

ShouldComponentUpdate用法

组件判断是否重新渲染时调用,即接收到新的propsstate时执行此方法

示例如下

定义ShouldComponentUpdate,初始化状态,并用事件触发改变状态,在shouldComponentUpdate方法和render中打印状态

class ShouldComponentUpdate extends Component {
  constructor(props) {
    super(props);

    this.state = {
      test: 'start',
    };

    [
      'render',
      '_handleClick',
    ].forEach((method) => this[method] = this[method].bind(this));
  }

  shouldComponentUpdate(nextprops, nextstate) {
    console.log(`[Method] shouldComponentUpdate\n[State test] ${nextstate.test}`);
    document.getElementById('contentId_8')
           .append(`[Method] shouldComponentUpdate\n[State test] ${this.state.test}\n`);
    return false;
  }

  _handleClick() {
    console.log(`[Method] _handleClick \n[State test] ${this.state.test}`);
    document.getElementById('contentId_8')
           .append(`[Method] _handleClick\n[State test] ${this.state.test}\n`);
    this.setState({
      test: 'end',
    }, () => {
      console.log(`[Method] _handleClick callback \n[State test] ${this.state.test}`);
      document.getElementById('contentId_8')
           .append(`[Method] _handleClick callback \n[State test] ${this.state.test}\n`);
    });
  }

  render() {
    console.log(`[Method] render\n[State test] ${this.state.test}`);
    document.getElementById('contentId_8')
           .append(`[Method] render \n[State test] ${this.state.test}\n`);
    return (
      <div>
        <h3 key={`title0`}>Example 6.7 shouldComponentUpdate</h3>
        <br />
        Render this component,state:{this.state.test}
        <br />
        <button key={`title1`} onClick={this._handleClick}>updateComponent</button>
      </div>
    );
  }
}

效果如下

请注意此方法要设置返回值,否则会提示如下警告

Warning: ShouldComponentUpdate.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.

如果返回值是false,那么下一个render不会执行

返回

返回目录

参考