Python: WEB开发-react
- TAGS: Python
React项目构建
项目依赖安装
将项目开发基础文件 react-mobx-starter-master.zip 解压缩,并用这个目录作为项目根目录。
在项目根目录中,执行下面的命令,就会自动按照package.json的配置安装依赖模块。
npm install #或者 $ npm i
安装完成后,会生成一个目录 node_modules ,里面是安装的所有依赖的模块
项目整体说明
项目目录结构
.
├── .babelrc
├── .gitignore
├── index.html #测试时用的
├── jsconfig.json
├── LICENSE
├── .npmrc
├── package.json
├── README.md
├── src
│ ├── App.js
│ ├── AppState.js
│ ├── index.html #打包时用的
│ └── index.js
├── node_modules
│ ├── ...
├── webpack.config.dev.js
└── webpack.config.prod.js
配置文件详解
package.json
$ npm init 产生的文件,里面记录项目信息,所有项目依赖
版本管理
可指定到对应的git仓库
"repository": { "type": "git", "url": "https://192.168.124.135/react-mobx/react-mobx-starter.git" }
项目脚本管理
"scripts": { "test": "jest", "start": "webpack-dev-server --config webpack.config.dev.js --hot --inline", "build": "rimraf dist && webpack -p --config webpack.config.prod.js" }
scripts属性是一个字典描述,key是名字,value是能够让npm运行的脚本内容。
运行时只要输入 npm run key_name
即可。
start 指定启动webpack的dev server开发用WEB Server,
主要提供2个功能:静态文件支持、自动刷新和 热替换HMR(Hot Module replacement) 。对应 npm run start
- –hot 启动HMR
- HRM可以在应用程序运行中替换、添加、删除模块,而无需重载页面,只把变化部分替换掉。不使用HMR则自动刷新会导致这个页面刷新
- –inline 默认模式, 使用HMR的时候建议开启inline模式。热替换时会有消息显示在控制台
build 使用webpack构建打包。对应 npm run build
项目依赖
devDependencies 开发时依赖,不会打包到目标文件中。对应 npm install xxx --save-dev
例如babel的一些依赖,只是为了帮我们转译代码,没有必要发布到生产环境中.
dependencies 运行时依赖,会打包到项目中。对应 npm install xxx --save
开发是依赖
"devDependencies": { "babel-core": "^6.24.1", "babel-jest": "^19.0.0", "babel-loader": "^6.4.1", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.4.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "css-loader": "^0.28.0", "html-webpack-plugin": "^2.28.0", "jest": "^19.0.2", "less": "^2.7.2", "less-loader": "^4.0.3", "react-hot-loader": "^4.3.12", "rimraf": "^2.6.2", "source-map": "^0.5.6", "source-map-loader": "^0.2.1", "style-loader": "^0.16.1", "uglify-js": "^2.8.22", "webpack": "^2.4.1", "webpack-dev-server": "^2.4.2" }
版本号指定
- 版本号 ,只安装指定版本号的
- ~版本号 ,例如 ~1.2.3 表示安装1.2.x中最新版本,不低于1.2.3,但不能安装1.3.x
- ^版本号 ,例如 ^2.4.1 表示安装2.x.x最新版本不低于2.4.1
- latest ,安装最新版本
- 1.2.x或1.x
- * 任意版本
- 1.2.7 || >=1.2.9 <2.0.0, 可以安装1.2.7,或者1.2.9本身或者之上的1.2.x版本,但不能是2.x版本。
babel 转译
- 因为开发用了很多ES6语法。从6.x开始babel拆分成很多插件,需要什么引入什么。
- babel-core 核心
- babel-loader webpack的loader,webpack是基于loader装载器的。把HTML,CSS,JS,图片,字体等资源看做模块,就可以像 JS一样加载这些模块。文件类型不同,就需要各种各样的loader。babel-loader加载ES6+代码转换为ES5.
- babel-preset-xxx 预设的转换插件
- babel-plugin-transform-decorators-legacy 下面的课程用到了装饰器,这个插件就是转换装饰器用的
css样式相关
- css-loader、style-loader样式表加载器
- less、less-loader是对less文件的支持
- react-hot-loader 是react热加载插件,希望改动保存后,直接在页面上直接反馈出来,不需要手动刷新
source-map文件打包,js会合并或者压缩,没法调试,用它来看js原文件是什么。source-map-loader也需webpack的 loader
webpack 打包工具,2.4.1 发布于2017年4月,当前2.7.0发布于2017年7月。
webpack-dev-server 启动一个开发时用的server
运行时依赖
"dependencies": { "antd": "^3.10.9", "axios": "^0.16.1", "babel-polyfill": "^6.23.0", "babel-runtime": "^6.23.0", "mobx": "^4.6.0", "mobx-react": "^5.4.2", "react": "^16.6.3", "react-dom": "^16.6.3", "react-router": "^4.3.1", "react-router-dom": "^4.3.1" }
- antd 即 ant design,基于react实现,蚂蚁金服开源的react的UI库。做中后台管理非常方便.
- axios 异步请求支持。
- polyfill 解决浏览器api不支持的问题。写好polyfill就让你的浏览器支持,帮你抹平差异化
- react开发的主框架
- react-dom 支持DOM
- react-router 支持路由
- react-router-dom DOM绑定路由
- mobx 状态管理库,透明化。
- mobx-react mobx和react结合的模块。react和mobx是一个强强联合
babel配置
.babelrc babel转译的配置文件
{ "presets": [ "react", "env", "stage-0" ], "plugins": [ "transform-decorators-legacy", "transform-runtime", "react-hot-loader/babel" ] }
webpack配置
webpack-react: https://webpack.js.org/awesome-webpack/#react
webpack.config.dev.js
这是一个符合Commonjs的模块
module.exports 导出
devtool:'source-map'
生成及如何生成source-map文件。
source-map适合生成环境使用,会生成完成Sourcemap独立文件。
由于在Bundle中添加了引用注释,所以开发工具知道如何找到Sourcemap。
entry入口
描述入口。webpack会从入口开始,找出直接或间接的模块和库构建依赖树,最后输出为bundles文件中。
entry如果是一个字符串,定义就是入口文件
如果是一个数组,数组中最后一项是入口文件,其他项先加载,可以配置的热加载插件来自己刷新页面。
如果是一个对象,可以支持多入口。也可是函数,返回以上的字符串或数组或对象,好处是动态生成。
output输出
告诉webpack输出bundles到哪里去,如何命名。
filename定义输出的bundle的名称
path 输出目录是 __dirname+'dist' ,名字叫做 bundle.js 。 __dirname 是nodejs中获取当前js文件所在的目录名
publicPath 可以是绝对路径或者相对路径,一般会以/结尾。/assets/表示网站根目录下assets目录下
resolve解析
设置模块如何被解析。
extensions 自动解析扩展名。'.js'的意思是,如果用户导入模块的时候不带扩展名,它尝试补全
module 模块
如何处理不同的模块
rules 匹配请求的规则,以应用不同的加载器、解析器等
module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'babel-loader' } ] }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, ] }, { test: /\.less$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "less-loader" } ] } ] },
test 匹配条件的
exclude 排除的, /node_modules/打包排除目录。这一句一定要有,否则,编 译就把这个目录下所有文件也拿进来了,巨大无比。 use 使用模块的UseEntries列表中的loader rules中对.js结尾的但不在node_modules目录的文件使用转译babel-loader
加载器:倒着写,先应用数组中的最后一个
style-loader通过 <style> 标签把css添加到DOM中 css-loader 加载css less-loader 对less的支持
LESS
CSS好处简单易学,但是坏处是没有模块化、复用的概念,因为它不是语言。
LESS是一门CSS的预处理语言,扩展了CSS,增加了变量、Mixin、函数等开发语言的特性,从而简化了CSS的编写。
LESS本身是一套语法规则及解析器,将写好的LESS解析成CSS。LESS可以使用在浏览器端和服务器端。
范例:
/*test.less*/ @color: #4D926F; #header { color: @color; } h2 { color: @color; }
可以使用解析成如下的CSS
PS D:\project\pyproj\trae\react-mobx-starter-master> ./node_modules/.bin/lessc test.less #header { color: #4D926F; } h2 { color: #4D926F; }
LESS在服务器端使用,需要使用LESS编译器, $ npm install less ,本项目目录已经安装过了
#编译输出到控制台 node_modules/.bin/lessc test.less npx lessc test.less #编译输出到文件 node_modules/.bin/lessc test.less test.css npx lessc test.less test.css
plugins:webpack的插件
plugins: [ new webpack.optimize.OccurrenceOrderPlugin(true), new webpack.HotModuleReplacementPlugin(), new webpack.DefinePlugin({'process.env': {NODE_ENV: JSON.stringify('development')}})
- HotModuleReplacementPlugin 开启HMR
- DefinePlugin 全局常量配置
devServer,开发用server
devServer: { compress: true, port: 3000, publicPath: '/assets/', hot: true, inline: true, historyApiFallback: true, stats: { chunks: false }, proxy: { '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true, pathRewrite: {'^/api':''} } } }
compress 启动gzip port 启动端口3000 hot 启用HMR proxy指定访问/api 开头URL都代理到 http://127.0.0.1:8000 去
vscode配置
jsconfig.json是vscode的配置文件,覆盖当前配置
以上是所有配置文件的解释。拿到这个文件夹后,需要修改name项目名称、 version版本、description描述,需要修改repository仓库地址,需要修改 author作者、license许可证信息。这些信息修改好之后,就可以开始开发了。
启动项目
在项目根目录,使用
$ npm run start 或 npm start #package.json start热加载选项--hot --open 自动打开浏览器 "scripts": { "test": "jest", "start": "webpack-dev-server --config webpack.config.dev.js --hot --inline --open", "build": "webpack -p --config webpack.config.prod.js" },
启动成功应该就可以访问了.
webpack使用babel转译、打包,较为耗时,需要等一会儿
React技术
简介
React是Facebook开发并开源的前端框架。
当时他们的团队在市面上没有找到合适的MVC框架,就自己写了一个Js框架,用来架设Instagram(图片分享社交网络)。2013年React开源。
React解决的是前端MVC框架中的View视图层的问题。
Virtual DOM
DOM(文档对象模型Document Object Model)

将网页内所有内容映射到一棵树型结构的层级对象模型上,浏览器提供对DOM的 支持,用户可以是用脚本调用DOM API来动态的修改DOM结点,从而达到修改网页 的目的,这种修改是浏览器中完成,浏览器会根据DOM的改变重绘改变的DOM结点 部分。
修改DOM重新渲染代价太高,前端框架为了提高效率,尽量减少DOM的重绘,提出 了Virtual DOM。Virtual DOM是一个JavaScript对象,性能更高。所有的修改都 是先生成一个新的Virtual DOM,通过比较算法比较新旧Virtual DOM,得到差异 化VirtualDOM,将这部分差异更新到浏览器DOM,浏览器只需要渲染这部分变化 就行了。
React实现了DOM Diff算法可以高效比对Virtual DOM
支持JSX语法
JSX是一种JavaScript和XML混写的语法,是JavaScript的扩展
React.render(
<div>
<div>
<div>content</div>
</div>
</div>,
document.getElementById('example')
);
测试程序
替换 /src/index.js 为下面的代码
import React from 'react'; import ReactDom from 'react-dom'; class Root extends React.Component { render() { return <div>Hello World</div>; } } ReactDom.render(<Root/>, document.getElementById('root'));
保存文件后,会自动编译,并重新装载刷新浏览器端页面
程序解释
import React from 'react'; 导入react模块。
import ReactDOM from 'react-dom'; 导入react的DOM模块。
class Root extends React.Component 组件类定义,从React.Component类上继承。这个类生成JSXElement对象即React元素 render() 渲染函数。返回组件中渲染的内容。注意,只能返回唯一 一个顶级元素回去。
ReactDom.render(<Root/>, document.getElementById('root')); 第一个参数是JSXElement对象,第二个是DOM的Element元素。将React元素添加到DOM的Element元素中并渲染。
还可以使用React.createElement创建react元素,第一参数是React组件或者一个HTML的标签名称(例如div、span)。
改写后代码为
import React from 'react'; import ReactDom from 'react-dom'; class Root extends React.Component { render() { //return <div>Hello World</div>; return React.createElement('div', null, 'Hello World'); } } //ReactDom.render(<Root/>, document.getElementById('root')); ReactDom.render(React.createElement(Root), document.getElementById('root'));
很明显JSX更简洁易懂,推荐使用JSX语法
增加一个子元素
import React from 'react'; import ReactDom from 'react-dom'; class SubEle extends React.Component { render() { return <div>Sub content</div>; } } class Root extends React.Component { render() { return ( <div> <h2>Hello World</h2> <br /> <SubEle /> </div >); } } ReactDom.render(<Root />, document.getElementById('root'));
注意:
- React组件的render函数return,只能是一个顶级元素
- JSX语法是XML,要求所有元素必须闭合,注意 <br /> 不能写成 <br>
JSX规范
React快速入门:https://zh-hans.react.dev/learn
- 约定标签中首字母小写就是html标记,首字母大写就是组件
- 要求严格的HTML标记,要求所有标签都必须闭合
- br也应该写成 <br /> ,/前留一个空格
- 单行省略小括号,多行请使用小括号
- 元素有嵌套,建议多行,注意缩进
- JSX表达式:表达式使用{}括起来,如果大括号内使用了引号,会当做字符串处理,例如 <div>{'2>1?true:false'}</div> 里面的表达式成了字符串
组件状态state **
每一个React组件都有一个状态属性state,它是一个JavaScript对象,可以为它定义属性来保存值。
如果状态变化了,会触发UI的重新渲染。使用setState()方法可以修改state值。
注意:state是每个组件自己内部使用的,是组件自己的属性。必须是state,不能是其它名字。
依然修改/src/index.js
import React from 'react'; import ReactDom from 'react-dom'; class Root extends React.Component { // 定义一个state对象 state = {domain: 'cici.com', host: 'www'} // 构造函数中定义state render() { // render 用来呈现state或props值 // this.state.host = 'python'; // 可以修改属性值 // this.setState({host:'python'}); //不可对还在更新中的state使用setSate. state改变会继续引起render, 递归了 //Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). setTimeout(()=> this.setState({host:this.state.host + '*'}), 3000) console.log('Root render ~~~~') return <div> Welcome to {this.state.host}.{this.state.domain} <hr /> </div> } } ReactDom.render(<Root />, document.getElementById('root'));
如果将 ·this.state.host = ‘python’· 改为 ·this.setState({host:'python'});· 就会出警告。
可以使用延时函数 setTimeout(() => this.setState({ host: 'python' }), 5000); 即可。
使用延时函数好吗? 不好,一般不推荐这么做,但是它确实不递归。
复杂的状态例子
先看一个网页
<html> <head> <script type="text/javascript"> function getEventTrigger(e) { x = e.target; // 从事件中获取元素 alert("触发的元素的id是:" + x.id); } </script> </head> <body> <div id="t1" onmousedown="getEventTrigger(event)"> 点击这句话,会触发一个事件,并弹出一个警示框 </div> </body> </html>
div的id是t1,鼠标按下事件捆绑了一个函数,只要鼠标在对象上按下就会触发调用getEventTrigger函数,浏览器会送给它一个参数event。event是事件对象,当事件触发时,event包含触发这个事件的对象
HTML DOM的JavaScript事件
属性 | 此事件发生在何时 |
---|---|
onabort | 图像的加载被中断 |
onblur | 元素失去焦点 |
onchange | 域的内容被改变 |
onclick(常用) | 当用户点击某个对象时调用的事件句柄 |
ondblclick | 当用户双击某个对象时调用的事件句柄 |
onerror | 在加载文档或图像时发生错误 |
onfocus | 元素获得焦点. 例如文本框中光标在里面时聚焦了 |
onkeydown | 某个键盘按键被按下 |
onkeypress | 某个键盘按键被按下并松开 |
onkeyup | 某个键盘按键被松开 |
onload | 一张页面或一幅图像完成加载 |
onmousedown | 鼠标按钮被按下 |
onmousemove | 鼠标被移动 |
onmouseout | 鼠标从某元素移开 |
onmouseover | 鼠标移到某元素之上 |
onmouseup | 鼠标按键被松开 |
onreset | 重置按钮被点击 |
onresize | 窗口或框架被重新调整大小 |
onselect | 文本被选中 |
onsubmit | 确认按钮被点击 |
onunload | 用户退出页面 |
使用React实现上面的传统的HTML
import React from 'react'; import ReactDom from 'react-dom'; class Toggle extends React.Component { state = { flag: true }; // 类中定义state handleClick(event) { console.log(event.target.id); console.log(event.target === this); console.log(this); console.log(this.state); this.setState({ flag: !this.state.flag }); } render() {/* 注意一定要绑定this onClick写成小驼峰 */ return <div id="t1" onClick={this.handleClick.bind(this)}> 点击这句话,会触发一个事件。{this.state.flag.toString()} </div>; } } class Root extends React.Component { // 定义一个对象 state = { p1: 'www.python', p2: '.org' }; // 构造函数中定义state render() { return ( <div> <div>Welcome to {this.state.p1}{this.state.p2}</div> <br /> <Toggle /> </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
Toggle类
- 它有自己的state属性,就给组件自身增加属性的。
- 当render完成后,网页上有一个div标签,div标签对象捆绑了一个click事件的处理函数
- 如果通过点击左键,就触发了click方法关联的handleClick函数,在这个函数里将状态值改变
- 使用this.state.flag = !this.state.flag 方式改变,不会触发render函数执行
- 使用this.setState({flag: !this.state.flag}) 方式,会触发该组件和其子组件render函数执行。
注意
- {this.handleClick.bind(this)},不能外加引号
- this.handleClick.bind(this) 一定要绑定this,否则当触发捆绑的函数时,this是函数执行的上下文决定的,this已经不是触发事件的对象。 或将handleClick定义为箭头函数也能解决this问题。
- console.log(event.target.id),取回的产生事件的对象的id,但是这不是我 们封装的组件对象。所以,console.log(event.target===this)是false。所 以这里一定要用this,而这个this是通过绑定来的
- this写在类中,始终指的是React组件实例本身
React中的事件
- 使用小驼峰命名
- 使用JSX表达式,表达式中指定事件处理函数
- 不能使用return false,如果要阻止事件默认行为,使用event.preventDefault()
属性props
props就是组件的属性properties。
把React组件当做标签使用,可以为其增加属性,如下
<Toggle name="school" parent={this} />
为上面的Toggle元素增加属性:
- name = "school" ,这个属性会作为一个单一的对象传递给组件,加入到组件的props属性中
- parent = {this} ,注意这个this是在Root元素中,指的是Root组件本身。parent属性是我们自己添加的。
- 使用JSX语法为Toggle增加子元素,这些子元素也会被加入Toggle组件的props.children中。 如果要显示这些内容,就必须在Toggle组件中从props中提取出来。
import React from 'react'; import ReactDom from 'react-dom'; class Toggle extends React.Component { state = { flag: true }; // 类中定义state handleClick(event) { console.log(event.target.id); console.log(event.target === this); console.log(this); console.log(this.state); this.setState({ flag: !this.state.flag }); } render() {/* 注意一定要绑定this onClick写成小驼峰 */ return <div id="t1" onClick={this.handleClick.bind(this)} style={{backgroundColor:'white', margin: '10' + 'px'}} > 点击这句话,会触发一个事件。{this.state.flag.toString()}<br /> 显示props<br /> {this.props.name} : {this.props.parent.state.p1}{this.props.parent.state.p2} {this.props.children} </div>; } } class Root extends React.Component { // 定义一个对象 state = { p1: 'www.python', p2: '.org' }; // 构造函数中定义state render() { return ( <div style={{backgroundColor: '#f0f0f0'}}> <div>Welcome to {this.state.p1}{this.state.p2}</div> <br /> <Toggle name="school" parent={this}>{/*自定义2个属性通过props传给Toggle组件对象*/} <hr />{/*子元素通过props.children访问*/} <span>我是Toggle元素的子元素</span>{/*子元素通过props.children访问*/} </Toggle> </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
尝试修改props中的属性值,会抛出 TypeError: Cannot assign to read only property 'name' of object '#<Object>'异常。也就是说props在组件内部不能修改,只读。
范例:复杂点的
import React from 'react'; import ReactDom from 'react-dom'; class Sub extends React.Component { render() {// render 用来呈现state或props值 return <div>sub content ++++</div> } } class Toggle extends React.Component { state = { flag: true }; // 类中定义state handleClick(e) { console.log('toggle handle click ~~~~') // e.preventDefault(); // 阻止默认事件 // console.log(e.target); // console.log(e.target.id); // console.log(this) //this 的坑 this.setState({ flag: !this.state.flag }); this.props.parent.setState({host: this.props.parent.state.host + '@'}) } render() {// render 用来呈现state或props值 console.log('Toggle render ~~~~') console.log(this.state, this.props) return <div id="toggle" onClick={this.handleClick.bind(this)} style={{backgroundColor:'white', margin: '10' + 'px'}} > 我是子组件Toggle. Flag = {this.state.flag.toString()} <hr /> 子组件区域显示props = {this.props.school} - {this.props.parent.state.host} <div style={{width:'80%', border:'1px solid red'}}> Toggle的子元素显示区域: {this.props.children} </div> </div> } } class Root extends React.Component { state = { domain: 'cici.com', host: 'www' }; // 构造函数中定义state handleClick(e) { console.log('root click') this.setState({host:this.state.host + '*'}) } render() { console.log('Root render ++++') return ( <div style={{backgroundColor: '#f0f0f0'}} onDoubleClick={this.handleClick.bind(this)}> Welcome to {this.state.host}.{this.state.domain} <hr /> <Toggle school='python' parent={this}> <div>toggle的子元素 <div>cell</div> </div> <div>第二个子元素 <Sub /> </div> </Toggle> </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
单向数据流
- state是私有private的属于组件自己的属性,组件外无法直接访问。可以修改state,但是建议使用setState方法。
- props是公有public属性,组件外也可以访问,但组件内只读。
- props是一种组件外部传入向组件内部传入数据的一种方式,只不过采用标签属性的方式。
- 在React中,数据只能由外层通过props传递给内层。这种称为单向数据流Single Data Flow。
构造器constructor
使用ES6的构造器,要提供一个参数props,并把这个参数使用super传递给父类
import React from 'react'; import ReactDom from 'react-dom'; class Toggle extends React.Component { //state = { flag: true }; // 类中定义state constructor(props) { super(props); // 一定要调用super父类构造器,否则报错 this.state = { flag: true }; // 类中定义state } handleClick(event) { console.log(event.target.id); console.log(event.target === this); console.log(this); console.log(this.state); this.setState({ flag: !this.state.flag }); } render() {/* 注意一定要绑定this onClick写成小驼峰 */ return <div id="t1" onClick={this.handleClick.bind(this)}> 点击这句话,会触发一个事件。{this.state.flag.toString()}<br /> 显示props<br /> {this.props.name} : {this.props.parent.state.p1}{this.props.parent.state.p2} {this.props.children} </div>; } } class Root extends React.Component { // 定义一个对象 state = { p1: 'www.python', p2: '.org' }; // 构造函数中定义state render() { return ( <div> <div>Welcome to {this.state.p1}{this.state.p2}</div> <br /> <Toggle name="school" parent={this}>{/*自定义2个属性通过props传给Toggle组件对象*/} <hr />{/*子元素通过props.children访问*/} <span>我是Toggle元素的子元素</span>{/*子元素通过props.children访问*/} </Toggle> </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
组件的生命周期
组件的生命周期可分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
组件的生命周期状态,说明在不同时机访问组件,组件正处在生命周期的不同状态上。
在不同的生命周期状态访问,就产生不同的方法。
生命周期的方法如下:
- 装载组件触发
- componentWillMount 在渲染前调用,只会在装载之前调用一次。此方法在V17即将过期。建议在constructor中初始化state。
- componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生 成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想 和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送A JAX请求等操作(防止异部操作阻塞UI)。只在装载完 成后调用一次,在render之后。
- 更新组件触发。这些方法不会在首次render组件的周期调用
- componentWillReceiveProps(nextProps) 在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。在V17中也即将过期。
- shouldComponentUpdate(nextProps,nextState)返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
- 可以在你确认不需要更新组件时使用
- 如果设置为false,就是不允许更新组件,那么componentWillUpdate、componentDidUpdate不会执行
- componentWillUpdate(nextProps, nextState) 在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。在V17中也即将过期。
- componentDidUpdate(prevProps, prevState) 在组件完成更新后立即调用。在初始化时不会被调用。
- 卸载组件触发
- componentWillUnmount在组件从 DOM 中移除的时候立刻被调用

由图可知 constructor构造器是最早执行的函数
组件构建好之后,如果更新组件的state或props(注意在组件内props是只读的),就会在render渲染前触发一系列的 更新生命周期函数
因此,重新编写/src/index.js 构造两个组件,在子组件Sub中,加入所有生命周期函数
下面的例子添加是装载、卸载组件的生命周期函数
import React from 'react'; import ReactDom from 'react-dom'; class Sub extends React.Component { constructor(props) { console.log('Sub constructor') super(props); // 调用父类构造器 this.state = { count: 0 }; } handleClick(event) { this.setState({ count: this.state.count + 1 }); } render() { console.log('Sub render'); return (<div id="sub" onClick={this.handleClick.bind(this)}> Sub's count = {this.state.count} </div>); } componentWillMount() { // constructor之后,第一次render之前 console.log('Sub componentWillMount'); } componentDidMount() { // 第一次render之后 console.log('Sub componentDidMount'); } componentWillUnmount() { // 清理工作 console.log('Sub componentWillUnmount'); } } class Root extends React.Component { constructor(props) { console.log('Root Constructor') super(props); // 调用父类构造器 // 定义一个对象 this.state = {}; } render() { return ( <div> <Sub /> </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
上面可以看到顺序是
constructor -> componentWillMount -> render -> componentDidMount -—state或props改变-—> render
增加 更新组件函数
为了演示props的改变,为Root元素增加一个click事件处理函数
import React from 'react'; import ReactDom from 'react-dom'; class Sub extends React.Component { constructor(props) { console.log('Sub constructor') super(props); // 调用父类构造器 this.state = { count: 0 }; } handleClick(event) { this.setState({ count: this.state.count + 1 });// 不要用++ } render() { console.log('Sub render'); return (<div style={{ height: 200 + 'px', color: 'red', backgroundColor: '#f0f0f0', padding: '20px' }}> <a id="sub" onClick={this.handleClick.bind(this)} style={{ backgroundColor: 'white' }}> Sub's count = {this.state.count} </a> </div>); } componentWillMount() { // constructor之后,第一次render之前 console.log('Sub componentWillMount'); } componentDidMount() { // 第一次render之后 console.log('Sub componentDidMount'); } componentWillUnmount() { // 清理工作 console.log('Sub componentWillUnmount'); } componentWillReceiveProps(nextProps) { // props变更时,接到新props了,交给shouldComponentUpdate。 // props组件内只读,只能从外部改变 console.log(this.props); console.log(nextProps); console.log('Sub componentWillReceiveProps', this.state.count); } shouldComponentUpdate(nextProps, nextState) { // 是否组件更新,props或state方式改变时,返回布尔值,true才会更新 console.log('Sub shouldComponentUpdate', this.state.count, nextState); return true; // return false将拦截更新 } componentWillUpdate(nextProps, nextState) { // 同意更新后,真正更新前,之后调用render console.log('Sub componentWillUpdate', this.state.count, nextState); } componentDidUpdate(prevProps, prevState) { // 同意更新后,真正更新后,在render之后调用 console.log('Sub componentDidUpdate', this.state.count, prevState); } } class Root extends React.Component { constructor(props) { console.log('Root Constructor') super(props); // 调用父类构造器 // 定义一个对象 this.state = { flag: true, name: 'root' }; } handleClick(event) { this.setState({ flag: !this.state.flag, name: this.state.flag ? this.state.name.toLowerCase() : this.state.name.toUpperCase() }); } render() { return ( <div id="app" onClick={this.handleClick.bind(this)}> My Name is {this.state.name} <hr /> <Sub /> {/*父组件的render,会引起下一级组件的更新流程,导致props重新发送,即使子组件props没有 改变过*/} </div>); } } ReactDom.render(<Root />, document.getElementById('root'));
componentWillMount 第一次装载,在首次render之前。例如控制state、props。
componentDidMount 第一次装载结束,在首次render之后。例如控制state、props。
componentWillReceiveProps 在组件内部,props是只读不可变的,但是这个函 数可以接收到新的props,可以对props做一些处理,this.props = {name:‘roooooot’};这就是偷梁换柱。componentWillReceiveProps触发,也 会走shouldComponentUpdate。
shouldComponentUpdate 判断是否需要组件更新,就是是否render,精确的控制渲染,提高性能。
componentWillUpdate 在除了首次render外,每次render前执行,componentDidUpdate在render之后调用。
不过,大多数时候,用不上这些函数,这些钩子函数是为了精确的控制。
修改Root组件render中的这一句为 <div id="root" onDoubleClick={this.handleClick.bind(this)}> ,可以看到点击Sub中红色的文字,Root不会重绘。
如果子组件和父组件使用了相同的事件,可以认为点击子组件也是点击了父组件, 父组件重绘,就会把子组件props更新,引起子组件组件更新流程,就会从 componentWillReceiveProps开始执行。如果子组件自己修改自己的state,不会 执行componentWillReceiveProps。
范例:
import React from 'react'; import ReactDOM, {render} from 'react-dom'; // bundle.js <script src="bundle.js"></script> // 未来bundler.js下载并相当于运行在index.html中 class Sub extends React.Component{ // 只要出现组件,就必须导入react constructor(props) { // 构造函数 super(props); // 调用父类的构造函数,必须写 console.log('组件实例化了') this.state = {count: 0}// 定义state状态 } componentWillMount() { console.log('组件将要挂载到DOM上') } // 第一次调用render componentDidMount() { console.log('组件已经挂载到DOM上') } componentWillUnmount() { console.log('组件已经从DOM上卸载了') } ///////////////////////// componentWillReceiveProps(nextProps) { // props变更时,接到新props了,交给shouldComponentUpdate。 // props组件内只读,只能从外部改变 console.log('当前值', this.props); console.log('未来要更新值', nextProps); // 待更新的props console.log('componentWillReceiveProps,props发生了变化,注意pops的改变来自外部,传入') } shouldComponentUpdate(nextProps, nextState) { // 是否组件更新,props或state方式改变时,返回布尔值,true才会更新 console.log('当前值', this.state, this.props); console.log('未来要更新值', nextState, nextProps); console.log('shouldComponentUpdate, 由props或state发生变化引起执行') // return false; // 就算state和props有更新,到底结束,不更新组件 return true; } componentWillUpdate(nextProps, nextState) { // 同意更新后,真正更新前,之后调用render console.log('当前值', this.state, this.props); console.log('未来要更新值', nextState, nextProps); // nextProps = {'name': 'componentWillUpdate nextProps'} // nextProps.name = 'python.org' // 改了没有用. 因为props只读 nextState.count = nextState.count + 100; // 改了有效果 console.log('未来要更新值', nextState, nextProps); console.log('componentWillUpdate, 由props或state发生变化引起执行') } render() { console.log('render, 组件渲染') return <div id='sub' onClick={e => { this.setState({count: this.state.count + 10}) }} style={{backgroundColor:'#FFF', width:'80%', margin: '5px' }}> Sub content. props = {this.props.name} <br /> state = {this.state.count} </div> } componentDidUpdate(prevProps, prevState) { // 同意更新后,真正更新后,在render之后调用 console.log('当前值', this.state, this.props); console.log('上次值', prevState, prevProps); console.log('componentDidUpdate') } } class App extends React.Component{ constructor(props) { super(props); this.state = {name: 'APP'} } handleClick(e) { this.setState({ name: this.state.name + '*' }) } render() { return <div onDoubleClick={this.handleClick.bind(this)} style={{backgroundColor: '#f0f0f0'}}> Root state = {this.state.name} <br /> <Sub name={this.state.name} /> </div> } } render(<App />, document.getElementById('root'))
函数式组件
React从15.0开始支持函数式组件,定义如下
import React from 'react'; import ReactDom from 'react-dom'; function Root(props) { return <div>{props.schoolName}</div>; } ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
开发中,很多情况下,组件其实很简单,不需要state状态,也不需要使用生命周期函数。
函数式组件的函数应该提供一个参数props,返回一个React元素。
函数式组件的函数本身就是render函数。
改写上面代码
import React from 'react'; import ReactDom from 'react-dom'; let Root = props => <div>{props.schoolName}</div>; ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
以前函数式组件还有个名字叫stateless components无状态组件。当前React发布了16.8,已经可以在函数式组件中使用state了,所以官方建议叫函数式组件。
高阶组件
let Root = props => <div>{props.schoolName}</div>;
如果要在上例的Root组件进行增强怎么办?例如将Root组件的div外部再加入其它div。
柯里化这个Wrapper函数
let Wrapper = function (Component, props) { return ( <div> {props.schoolName} <hr /> <Component /> </div> ); }; let Root = props => <div>{props.schoolName}</div>;
柯里化这个Wrapper函数
import React from 'react'; import ReactDom from 'react-dom'; let Wrapper = function (Component) { function _wrapper(props) { return ( <div> {props.schoolName} <hr /> <Component /> </div> ); } return _wrapper; }; let Root = props => <div>Root</div>; let NewComp = Wrapper(Root) // 返回一个包装后的元素 ReactDom.render(<NewComp schoolName="Hello World"/>, document.getElementById('root'));
下面代码本身就是一个无状态组件,内部包裹这一个传入的组件,可以看做增强了传入的组件。
传入的组件作为返回的新组件的子组件
function _wrapper(props) { return ( <div> {props.schoolName} <hr /> <Component /> </div> ); }
那么上面代码的Wrapper函数可以看做参数是一个组件,返回一个新组件,即高阶组件。
简化Wrapper,箭头函数
let Wrapper = function (Component) { return (props) => { return ( <div> {props.schoolName} <hr /> <Component /> </div> ); }; };
再次变化
let Wrapper = function (Component) { return props => ( <div> {props.schoolName} <hr /> <Component /> </div> ); };
再次变化
import React from 'react'; import ReactDom from 'react-dom'; let Wrapper = Component => props => (<div> {props.schoolName} <hr /> <Component /> </div>); let Root = props => <div>Root</div>; let NewComp = Wrapper(Root) ReactDom.render(<NewComp schoolName="Hello World" />, document.getElementById('root'));
装饰器
新版ES 2016中增加了装饰器的支持,因此可以使用装饰器来改造上面的代码
@Wrapper // 这是不对的,装饰器装饰函数或类 let Root = props => <div>Root</div>;
ES 2016的装饰器只能装饰类,所以,将Root改写成类
import React from 'react'; import ReactDom from 'react-dom'; let Wrapper = Component => props => (<div> {props.schoolName} <hr /> <Component /> </div>); @Wrapper class Root extends React.Component { render() { return <div>Root</div>; } } ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
如何让Root也显示出schoolName?
import React from 'react'; import ReactDom from 'react-dom'; let Wrapper = Component => props => (<div> {props.schoolName} <hr /> {/* <Component schoolName={props.schoolName} /> */} <Component {...props} /> </div>); @Wrapper class Root extends React.Component { render() { return <div>{this.props.schoolName}</div>; } } ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
使用 <Component {…props} /> 相当于给组件增加了属性。
带参装饰器
想给装饰器函数增加一个id参数
import React from 'react'; import ReactDom from 'react-dom'; // 带参装饰器函数 let Wrapper = id => Component => props => (<div id={id}> {props.schoolName} <hr /> <Component {...props} /> </div>); @Wrapper('wrapper') // 带参 class Root extends React.Component { render() { return <div>{this.props.schoolName}</div>; } } ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
import React from 'react'; import ReactDom from 'react-dom'; // 带参装饰器函数 let Wrapper = (id, id2) => Component => props => (<div id={id}> {props.schoolName} <hr /> <Component {...props} id={id2}/> </div>); @Wrapper('wrapper', 'wrapp_app') // 带参 class Root extends React.Component { render() { return <div id={this.props.id}>{this.props.schoolName}</div>; } } ReactDom.render(<Root schoolName="Hello World" />, document.getElementById('root'));
通过上面的改写,就得到带参装饰器。
如果觉得不好接受,可以先写成原始的形式,熟悉了箭头函数语法后,再改成精简的形式。