React

React项目

目录

1、简介
2、项目根目录结构配置文件解析
3、react使用

  1. 简介
  2. 组件状态
  3. 属性
  4. 组件生命周期
  5. 无状态组件
  6. 高阶组件
简介

一些工具的组合。
一个纯粹的index.js 在网页上是可以直接运行的,webserver的本质。
因为react多用Mixin等,我们需要搭建开发环境,将这些代码转译。只要你做代码修改,保存后会自动重新转译,转译完之后立即动态刷新。
项目的根目录很重要,很多都是从项目的根目录开始的。

项目根目录结构

.(这里有个点)
|----.babelrc babel的预设值
|----.gitignore 忽略文件
|----index.html 项目的根目录
|----jsconfig.json VScode相关文件,一些项目相关的配置可以放在这里
|----LICENSE
|----.npmrc
|----package.json
|----package-lock.json
|----README.md
|----src/ 项目里的js文件
| |----app.js
| |----AppState.js
| |----index.html
| |----index.js
|----node_modules/ 所依赖的模块
| |----…
|----webpack.config.dev.js
|----webpack.config.prod.js

配置文件

首先我们先来了解一下每个配置文件
pacjage.json
npm init产生的文件,里面记录项目信息,所有项目依赖。

版本管理
“repository”:{“type”:“git”,
“url”:“https://192.168.124.135/react-mobx/react-mobx-starter.git”}

项目管理
“scripts”:{
“test”:“jest”,
“start”:“webpack-dev-server”,
“build”:“webpack -p --config webpack.config.prod.js”
}
start指定启动webpack的dev server开发用WEB Server,主要提供两个功能:静态文件支持、自动刷新和热替换HMR。HMR可以在应用程序运行中替换、添加、删除模块,无需重新加载页面,只需要把变化的部分替换掉。不使用HMR则会自动刷新。 --hot启动HMR --inline 默认模式,使用HMR的时候建议开启,热替换时会有消息显示在控制台。
build 使用webpack构建打包,对应$ npm run build,将会按照 webpack -p --config webpack.config.prod.js 对路径里的文件进行打包,然后根目录下会多出一个分发目录,目录含有的文件如下:
在这里插入图片描述
我们看看index文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="root"></div> // id最好不要重复,作为唯一id,JS通过document.getElementByID('root')来找到这个,再进行操作
<script type="text/javascript" src="./assets/app-d2e554f9.js"></script></body> // 直接在浏览器打开是没有任何显示,因为 <script type="text/javascript" src="/assets/app-d2e554f9.js">这一句中是从web上加载/assets/app-d2e554f9.js,但是我们在本地加载的时候,这个根就变成本地的逻辑驱动器的根了,如果在webserver中运行,则是webserver的根,因此找不到这个文件,文件并没有错,将app-d2e554f9.js加入到项目根下的assets下。在src前加"."就是在index同一个目录下即可。
</html>

如果想修改样式,
有几种方式;1、html内部修改,例如:修改html样式

项目依赖
devDependencies 开发时依赖,不会打包到目标文件中。对应npm install xxx --save-dev。例如babel的一些依赖,只是为了转译,不需要放在生产环境中。
dependencies 运行时依赖,会打包到项目中,对应npm install xx --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”: “^3.0.0-beta.6”,
“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”}
^版本号,例如^2.4.3,表示安装2.x.x中不低于2.4.3的版本;~版本号表示安装 2.4.x的最新版本,不安装2.5.x;latest表示安装最新版本。一般来说做项目时,大版本号不做改变。

babel转译,因为开发用了很多ES6语法,从6.x开始babel拆分成很多插件,需要什么引入什么。
“babel-core” 核心。
“babel-loader” webpack的loader,webpack基于loader。
“babel-preset-xxx” babel预设的转换插件
“babel-plugin-transform-decorators-legacy” 转换装饰器用的。

css样式相关包括
“css-loader”、“less”、“less-loader”、“style-loader”。

“react-hot-loader” react热加载插件。

“source-map” 文件打包,会压缩合并JS,成一行,下断点没用,没有办法调试,可以将JS源码映射到一个source-map文件中,可以用来看JS的源文件是什么,可以定位到源文件的位置;也需要webpack的loader。

“webpack” 打包工具

“webpack-dev-server” 启动一个开发的server

运行时依赖
“dependencies”: {
“antd”: “^2.9.1”,
“axios”: “^0.16.1”,
“babel-polyfill”: “^6.23.0”,
“babel-runtime”: “^6.23.0”,
“mobx”: “^3.1.9”,
“mobx-react”: “^4.1.8”,
“mobx-react-devtools”: “^4.2.11”,
“prop-types”: “^15.5.8”,
“react”: “^15.5.4”,
“react-dom”: “^15.5.4”,
“react-router”: “^4.1.1”,
“react-router-dom”: “^4.1.1”}

“antd” 基于react的前端的库,蚂蚁金服开源的react的UI库,做中后台管理很方便。

“axios” 异步库,异步请求支持。

“babel-polyfill” 提供抹去JS差异的工具,解决浏览器api不支持的问题。。

“react” 开发框架
“react-dom”:支持dom
“react-router”: 支持路由
“react-router-dom”: dom绑定路由

“mobx” react状态相关,状态管理库,透明化。还可以用react,redx
“mobx-react” , “mobx-react-devtools” mobx和react结合的模块
mobx和react结合是很棒的组合,同时还可以react和redx

前端路由,前端的url进行变化。

.babelrc babel转译的配置文件。
{
“presets”: [
“react”,
“env”,
“stage-0”
],
“plugins”: [“transform-decorators-legacy”, “transform-runtime”]
}

webpack配置
webpack.config.dev.js是一个符合Commonjs的模块。

const path = require(‘path’);
const webpack = require(‘webpack’);

module.exports = { // 导出
devtool: ‘source-map’, // 生成及如何生成source-map文件,source-map 适合生产环境使用,会生成完成Sourcemap独立文件(运行后会生成一个大的压缩过的JS文件,会告诉你如何映射到你现在的源码)。由于在build中添加了引用注释,所以开发工具知道如何找到Sourcemap。
entry: { //入口,webpack会从入口(从src下的index.js开始)开始,找出直接或间接的模块和库,最后输出稳bundles文件,entry如果是一个字符串,定义是一个入口文件,如果是一个数组,数组中每一项都会执行,里面包含入口文件(./src/index),另一个参数可以用来配置一个服务器( ‘react-hot-loader/patch’),我们这里配置的是热加载插件,可以自动刷新。
‘app’: [
‘react-hot-loader/patch’,
‘./src/index’ //从src下的index.js开始,
]
},
output: { // 输出,告诉webpack输出bundles到哪里去,如何命名。filename定义输出到的bundle的名称,
path: path.join(__dirname, ‘dist’), //输出目录是__dirname, ‘dist’
filename: ‘bundle.js’, //输出目录的名字
publicPath: ‘/assets/’ //可以是绝对路径,一般会以/结尾,/assets/表示网站根目录下的assets目录下
},
resolve: { // 解析,设置模块如何被解析
extensions: [’.js’] // 自动解析扩展名,"js"的意思是,如果用户导入模块时不用带扩展名,它会尝试补全。
},
module: { //模块,如何处理不同的模块,
rules: [// 匹配请求的规则,以应用不同的加载器,解析器等。
{
test: /.jsKaTeX parse error: Expected 'EOF', got '}' at position 292: … ] }̲, {…/, //遇到less文件结尾的,则用less-loader加载,然后css-loader加载css,最后用style标签把css文件加载到DOM里。
use: [
{ loader: “style-loader” },
{ loader: “css-loader” }, // css没有模块化、复用的概念,不是语言
{ loader: “less-loader” } //是一个css的预处理语言,扩展了css,增加了变量,Mixin,函数等开发语言特性,是一套语法规则和解析器,将写好的LESS解析成css。less可以使用在浏览器端和服务器端。我们在node_modules下可以看到/.bin/lessc,需要安装npm install less试着将一个test.less文件转化,在shell中输入node__modules/.bin/lessc test.less。会输出转化后的css
]
}
]
},
plugins: [ // webpack插件
new webpack.optimize.OccurrenceOrderPlugin(true), //并发插件
new webpack.HotModuleReplacementPlugin(),//热加载
new webpack.DefinePlugin({‘process.env’: {NODE_ENV: //全局常量配置 JSON.stringify(‘development’)}})
],
devServer: { //开发server
compress: true, //开启压缩
port: 3000,
publicPath: ‘/assets/’, // 公共路径
hot: true, //启用热加载
inline: true,
historyApiFallback: true,
stats: {
chunks: false
},
proxy: { //http代理,如果你访问的是http://ip/api路径就会把这个请求转发到 target:‘http://127.0.0.1:8080’,可以更改。
‘/api’: {
target: ‘http://127.0.0.1:8080’,
changeOrigin: true
}
}
}
};

vscode配置
jsconfig.json是vscode的配置文件,覆盖当前目录。

以上是所有配置文件,需要修改name,version,description,需要修改repository仓库地址,需要修改author,license信息,修改好之后就可以开发了。

一个纯粹的index.js 在网页上是可以直接运行的,webserver的本质。

启动项目
在项目根目录下启动
$ npm start
需要等一会儿如图
npm start
启动成功就可以访问,使用http://127.0.0.1:3000就可以看到index了

  • 文件路径为啥要用.,视频中没有。
  • less可以使用在浏览器端和服务器端。我们在node_modules下可以看到/.bin/lessc,需要安装npm install less试着将一个test.less文件转化,在shell中输入node_modules/.bin/lessc test.less。会输出转化后的css。这个没有实现,在shell输入命令没有用,应该是文件路径的问题。
  • 以上是所有配置文件,需要修改name,version,description,需要修改repository仓库地址,需要修改author,license信息,修改好之后就可以开发了。修改要做一下。

React

简介

React是Facebook开发并开源的前端框架。解决的是前端MVC框架中的View视图层的问题。跑在浏览器端的。写起来就像在写面向对象的语言,很方便,所以这是必须学会的。

Virtual DOM

DOM(文档对象模型Document Object Model),看图:
HTML 文档
这是一个HTML文档,是要被浏览器加载的,浏览器的渲染引擎要解析这些标记,要知道这些标记对应的格式(之前动态的改格式是通过样式表来解决的),实际上浏览器在一开始要加载html css文件,将内容解析之后,把这个html绘制,绘制的同时要知道这是什么样式大小等做出来。dom将这些标记解析成树状结构,在内存中,节点具有类型。如图:
DOM tree
为什么呢?因为html本身就是树形结构。
我们可以对dom树操作,然后修改,将网页所有内容映射到一颗树型结构的层级对象模型上,浏览器提供对DOM的支持,用户可以是用脚本调用DOM API来动态的修改DOM结点,从而达到修改网页的目的,这种修改是浏览器中完成,浏览器会根据DOM的修改重绘改变的DOM结点(带有类型)部分。
但是修改DOM重新渲染的代价很高,前端框架为了提高效率,尽量减少DOM的重绘,提出来Virtual DOM,所有的修改都是在Virtual DOM上完成的,通过比较算法,找出浏览器DOM之间的差异,使用这个差异操作DOM,浏览器只需要渲染这部分变化就行了。
React实现了DOM Diff 算法可以高效对比Virtual DOM和DOM的差异。

支持JSX语法
是一种JavaScript和XML混写的语法,是JavaScript的扩展。
将src下的index.js文件修改,然后react会热加载保存后的index.js。代码如下:

import React from 'react';
import { render } from 'react-dom'; // render要修改成 ReactDOM即可成功,控制dom修改的。需要加载上。


class Root extends React.Component{ //把组件加载到DOM中
    render(){
        return <div>good work</div>//必须return一个jsx的文件
    }
}

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

成功运行:
成功
因为是热加载,所以调试起来很方便。
其中import React from ‘react’;导入React模块,
import { render } from ‘react-dom’; 导入react的DOM模块
class Root extends React.Component组件类定义,从React.Component类上继承。这个类生成JSXElement对象,即React元素,其中包含渲染函数render(),返回组件中渲染的内容,只能返回一个顶级元素。
ReactDOM.render(,document.getElementById(“root”)),第一个参数是JSXElement对象,即React元素;第二个是DOM的Element元素,将React元素添加到DOM的Element元素中并渲染。

还可以使用React.createElement创建react元素,第一参数是React组件或者一个HTML的标签名称(如div,span等)。如下

import React from 'react'; // 一定要导入react
import ReactDOM from 'react-dom';

class Root extends React.Component{ //把组件加载到DOM中 Root是组件,继承自React.Component,其中的render必须是return一个JSX文件
    render(){
        // return <div>good <br /> work</div>//必须return一个jsx的文件,必须是一个元素<div>good <br /> work</div>,如div,如果这样<div> work</div><br />,就是两个元素
      return React.createElement('div',null, 'good work');
      }
}

ReactDOM.render(React.createElement(Root),document.getElementById('root'))

// ReactDOM.render(<Root />,document.getElementById("root")); //对于<Root />这种单标记,必须有/结尾,不然会报错
//例如换行符<br />这样才有效果

显然JSX更好用,无论是嵌套还是易读性。

增加一个子元素如下:

import React from 'react'; // 一定要导入react
import ReactDOM from 'react-dom';

class Sub extends React.Component {
  render(){
    return <span> I am Sub element</span>;
  }
}

class Root extends React.Component { //把组件加载到DOM中 Root是组件,继承自React.Component,其中的render必须是return一个JSX文件
    render(){
        return <div>good work
          <hr />
          <Sub /> </div>;//必须return一个jsx的文件,必须是一个元素<div>good <br /> work</div>,如div,如果这样<div> work</div><br />,就是两个元素
      // return React.createElement('div',null, 'good work');
      }
}

// ReactDOM.render(React.createElement(Root),document.getElementById('root'))

ReactDOM.render(<Root />,document.getElementById("root")); //对于<Root />这种单标记,必须有/结尾,不然会报错
//例如换行符<br />这样才有效果

再次注意!!
React组件要有只能一个return顶级元素的render函数
所有元素必须必和,如<br / >。

JSX规范,可以到网上找!
标签中首字母小写就是html标记,首字母大写就是组件。
要求严格的HTML标记,要求所有标签都必须闭合。如<br / >。/前要有一个空格;
单行省略小括号,多行请使用小括号;
元素可以嵌套,要注意缩进;
JSX表达式:使用{}括起来,如果大括号内使用了引号,会当做字符串处理,例如{2>1?‘true’:‘false’},比较2和1的大小后输出ture,true变成字符串了。

组件状态state**

每一个React组件都有一个状态变量state,它是一个JS对象,可以为它定义属性来保存值。
如果状态变化了,会触发UI的重新渲染。使用setState()方法可以修改state值。
state是组件内部使用,组件私有属性。

每一个标签控制的范围内,只要用鼠标做动作就归该标签管。参考W3C中的onclick事件,只要你点击了,就调用回调函数,一般是用动态增加就是,如< button οnclick=‘my function()’> Click me</ button>(JSX中 button onClick)标签小写,事件响应用小驼峰来写。

看栗子:
修改index.js

class Sub extends React.Component {
  state = {count:0};
  clickHandler(event){console.log(event.target);
  console.log(event.target.id);
  this.setState({count:this.state.count + 1})}
  // this.state.count++;};  // this 的坑,定义时绑定,调用时绑定,此时为undifined。
  render(){
    return <button id="sub" onClick={this.clickHandler.bind(this)}> I am Sub element . {this.state.count}. {2>1?'true':'false'}</button>;
  }
}
class Root extends React.Component {
      state = {domain:'I am domain',p1:' I am p1'}
      render(){
        // this.setState({p1:' spy '}) //不可以对还在更新中的state使用setstate 
        // this.state.p1 = 'spy 1'
        setTimeout(()=>this.setState({p1:' spy '}),5000) 
        return <div>good work {this.state.domain} {this.state.p1}
          <hr />
          <Sub /> </div>;
      // return React.createElement('div',null, 'good work');
      }
}
ReactDOM.render(<Root />,document.getElementById("root"));

保存后页面会自动部分重载,如图
button实现
如果我们在Root中这样修改state,this.setState({p1:’ spy '}) 会报错,因为不可以对还在更新中的state使用setstate,因为render和这个修改是同步的,因此我们用一个延时的异步调用来修改,即可得到想要的结果。而Sub中完全不同,sub是事件驱动调用,因此也是异步调用,不会出现在Root中的问题。
clickHandler把state的值变化了,引起了render函数的重绘。而传统的做法是将button的文本里的值等于了一下。id是留给JS调用的。render函数一旦调用,浏览器中已经绘制好了。组件中的state变化了,组件会调用render函数重回,这是由框架决定的。
state的变化引起render的调用,因为render里含有state的参数,而重绘是靠虚拟dom,虚拟dom一比较两次节点的差异,如果没有差异,就不会重新渲染,即使你的state变化了;如果有就会重新变化。
如果state的变化引起了render函数返回值的变化,在调用render后,虚拟dom比较两个节点的差异,发现有差异,然后浏览器会重新渲染,将改变的一小块重新渲染;如果state的变化没有引起render函数返回值的变化,在调用render后,虚拟dom比较两个节点的差异,发现没有差异,则浏览器不会调用重新渲染。

看看一个状态例子。

<html>
<head>
    <script type='text/javascript'>
        function getEventTrigger(event){
            x = event.target; //从事件中获取元素
            alert('触发的元素id是', +x.id);
        }
        </script>
</head>
<body>
    <div id = 't1' onmousedown = "getEventTrigger(event)">
    点击这句话,会触发一个时间,并弹出一个警示框
    </div>
</body>
</html>>

在这个例子中,div的id是t1,鼠标按下事件绑定了一个函数,只要鼠标按下就会触发调用getEventTrigger函数,浏览器会给它一个参数event,当事件触发时,event包含触发这个事件的对象,可以找到这个对象的元素并获取属性,然后将属性输出(在这里属性时id = ‘t1’)。这里是传统写法,如何变成前端框架来实现呢?

HTML DOM 的JavaScript事件

名称此事件发生在何时
onabort图像的加载被中断
onblur元素失去焦点
onchange域的内容被改变
onclick当用户点击某个对象时调用的事件句柄
ondblclick当用户双击某个对象时调用的事件句柄
onerror在加载文档或图像时发生错误
onfocus元素获得焦点
onkeydown某个键盘按键被按下
onkeypress某个键盘按键被按下并松开
onkeyup某个键盘按键被松开
onload一张页面或图像完成加载
onmousedown鼠标按钮被按下
onmousemove鼠标被移动
onmouseout鼠标从某元素移开
onmouseover鼠标移动到某元素上
onmouseup鼠标被松开
onreset重置按钮被点击
onresize窗口或框架被重新调整大小
onselect文本被选中
onsubmit确认按钮被点击
onunload用户退出页面

onload一般用在body上,dom树一点一点形成,其中AMD的异步模块加载要有一个回调,因此会用到onload。
onchange域,注册的时候,写入用户名时会有该用户名已经使用(异步调用模式,并返回调用结果一个json)。在JSX中要注意小驼峰。on后面首字母要大写。

阻止默认行为,event.preventDefault()调用一下即可阻止例如,点击链接弹窗,试验一下即可。
使用react实现上面的传统的HTML

import React from 'react';
import ReactDOM from 'react-dom';
class Toggle extends React.Component{
  state = {flag:true};
  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:'p1 is here',p2:'p2 is here'};
  render(){
    setTimeout(()=> this.setState({p1:'exchange p1'}),3000);
  return (
    <div>
      <div>your happy for {this.state.p1}, {this.state.p2}</div>
      <br />
      <Toggle />
    </div>
  );
  }
}
ReactDOM.render(<Root />,document.getElementById("root"));

网页输出:
在这里插入图片描述
分析
Toggle类
有属性state,当render完成后,网页上有一个div标签,div标签对象捆绑了一个click事件的处理函数,div标签内有文本内容。如果通过点击左键,就触发了click方法关联的handleClick函数,在这个函数里将状态值改变。状态值state的改变将引发render重绘。如果组件自己的state改变了,只会触发自己的render方法重绘。

注意{this.handleClick.bind(this)}不能外加引号,一定要绑定this,否则触发捆绑的函数时,this是函数执行的上下文决定的,this已经不是触发事件的对象了。console.log(event.target.id);取回的产生事件的对象的id,但是这不是我们封装的组件对象。 console.log(event.target === this);是false。所以这里一定要用this,而这个this是通过绑定来的。

react中的事件
使用小驼峰命名
使用JSX表达式,表达式中指定事件处理函数
不能使用return false,如果要阻止事件默认行为,使用event.preventDefault()

属性props

我们将React组件(例如Toggle)当做标签使用,为其增加属性,如:
,尝试增加属性:
test = ‘think god’ 这个属性作为一个单一的对象传递给组件,加入到组件的props属性中。
parent = {this} 可以传对象在其中,这个this是在Root元素中,是Root组件本身。
在Root中使用JSX语法为Toggle增加子元素,这些子元素也会被加入到Toggle组件的props.children中。看栗子:

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


class Toggle extends React.Component{
  state = {flag:true};
  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()}
      <hr /> {this.props.test}
      <br />
      {this.props.children} // 不能修改props的属性值,类似于变成只读
      {this.props.parent.state.p1} //在组件内调用props所含有的属性
    </div>;
  }
}

class Root extends React.Component {
  state = {p1:'p1 is here',p2:'p2 is here'};
  render(){
    setTimeout(()=> this.setState({p1:'exchange p1'}),3000);
  return (
    <div>
      <div style = {{color:'red'}} >your happy for {this.state.p1}, {this.state.p2}</div>
      <br />
      <Toggle test = 'think god' parent = {this}>  // 将属性通过props添加到Toggle组件中,其中parent为类
        <a href='http://baidu.com'>baidu</a>
        <span>I am the Toggle children <br /></span>
      </Toggle>
    </div>
  );
  }
}

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

输出为
props
但是不能修改props的属性,因为是公有属性,组件外可访问,只读。和state不同,state是组件私有属性,组件外无法访问,可以修改。

构造器constructor

使用ES6的构造器,要提供一个参数props,并把这个参数使用super传递给父类。就是this 的props。
例如:

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


class Toggle extends React.Component{
  constructor(props){
    super(props)
    this.state = {flag:true};
  }
  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()}
      <hr /> {this.props.test}
      <br />
      {this.props.children}
      {this.props.parent.state.p1}
    </div>;
  }
}

class Root extends React.Component {
  constructor(props){
    super(props);
    this.state = {p1:'p1 is here',p2:'p2 is here'};
  }
  // state = {p1:'p1 is here',p2:'p2 is here'};
  render(){
    setTimeout(()=> this.setState({p1:'exchange p1'}),3000);
  return (
    <div>
      <div style = {{color:'red'}} >your happy for {this.state.p1}, {this.state.p2}</div>
      <br />
      <Toggle test = 'think god' parent = {this}>
        <a href='http://baidu.com'>baidu</a>
        <span>I am the Toggle children <br /></span>
      </Toggle>
    </div>
  );
  }
}

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

输出同上。

组件的生命周期

组件的生命周期分成三个状态:
Mounting:已插入真实DOM
Updating:正在被重新渲染
Unmounting:已移出真实DOM
组件的生命周期状态,说明在不同时机访问,组件处于生命周期的不同状态上。在不同的生命周期访问组件,就有不同的方法
如下:

  • 装载组件触发

    • componentWillMount:在渲染前调用,在客户端也在服务器。只会在装载之前调用一次。

    • 有一次render的调用

    • componentDidMount:在第一次渲染后掉用,只在客户端,之后组件已经生成了对应的DOM结构,可以通过this.getDOMNoder()来进行访问。如果想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout,setInterval或者法师AJAX请求等操作(防止异步阻塞UI)。只在装载完成后调用一次,在render之后。

  • 更新组件触发,这些方法不会再首次render组件的周期调用

    • componentWillReceiveProps(nextProps)在组件接受到一个新的prop时被调用。这个方法在初始化render时不会被调用.

    • shouldComponentUpdate(nextProps,nextState)返回一个bool值(如果返回false,则更新流程到此为止,不会到达render,每次渲染都会经过它)。在组件接受到新的props或者state时被调用,在初始化时或者使用forceUpdate时不被调用。

      • 可以在你确认不需要更新组件时使用。
      • 如果设置为false,就是不允许组件更新,那么componentWillUpdate,ComponentDidUpdate不会执行。
    • componentWillUpdate(nextProps,nextState)在组件接受到新的props或者state但还没有render时被调用,在初始化时不会被调用。

    • ComponentDidUpdate(prevProps,prevState)在组件完成更新后立即调用。在初始化时不会被调用。

  • 卸载组件触发

    • componentWillUnmount在组件从DOM中移除的时候立刻被调用。

流程图如下:
组件生命周期
constructor构造器是最早执行的函数。
触发 更新生命周期函数,需要新state或者props。
因此重新写src/index.js
构造两个组件,在子组件Sub中,加入所有生命周期。
看栗子:

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

class Sub extends React.Component{
  constructor(props){
    // console.log(...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(){
    console.log('Sub componentWillMount');
  }
  componentDidMount(){
    console.log('Sub  componentDidMount');
  }
  componentWillUnmount(){
    console.log('componentWillUnmount')
  }

}

class Root extends React.Component{
  constructor(props){
    // console.log(...props);
    console.log('Root Constructor');
    super(props);
    this.state = {};
  }
  render(){
    return (
      <div>
        <Sub />
      </div>
    )
  }
}

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

运行输出
在这里插入图片描述
从这里可见这些周期函数的调用顺序。每次点击,造成的state,或者props改变,都会调用一次render。其中props是空的{}。
这里再增加一些元素,以更加清晰的方式看这个栗子;

class Sub extends React.Component{
  constructor(props){
    console.log(1 ,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:100+'px',color:'red',backgroundColor:'#f0f0f0'}}>
      <a id='sub' onClick={this.handleClick.bind(this)}>
        Sub's count ={this.state.count}
      </a>
    </div>);
  }
  componentWillMount(){
    console.log('Sub componentWillMount');
  }
  componentDidMount(){
    console.log('Sub  componentDidMount');
  }
  componentWillUnmount(){
    console.log('componentWillUnmount')
  }
  componentWillReceiveProps(nextprops){
    console.log(this.props);
    console.log(nextprops);
    console.log('Sub componentWillReceiveProps',this.state.count);
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('Sub shouldComponentUpdate', this.state.count, nextState);
    return true;
  }
  
  componentWillUpdate(nextProps,nextState){
    console.log('Sub componentWillUpdate', this.state.count, nextState)
  }

  componentDidUpdate(prevProps,prevState){
    console.log('Sub componentDidUpdate', this.state.count, prevState)
  }
}

class Root extends React.Component{
  constructor(props){
    console.log(2,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 ='root' onClick={this.handleClick.bind(this)}>
        name is {this.state.name}
        <hr />
        <Sub />
      </div>
    )
  }
}

运行结果如下
在这里插入图片描述
运行结果
componentWillMount第一次装载,再首次render之前;
componentDidMount 第一次装载结束,再首次render之后;

componentWillReceiveProps 在组件内部,props是只读不可变的,但是这个函数可以接收新的props,可以对props进行处理,如果加入this.props={name:‘customer’},就可以偷偷更换props,触发后也要进入shouldComponentUpdate.

shouldComponentUpdate判断是否需要组件更新,就是是否render,精确的控制渲染,提高性能。

componentWillUpdate在除了首次render之外,每次render前执行,componentDidUpdate在render之后调用。
这些函数只是为了精确控制。

但是这样编写代码过于复杂,如果可以用上箭头函数,装饰器等就更好了,下面说一说高阶技术。

无状态组件

React从15.0开始支持无状态组件,定义如下:

function Root(props){
  return <div>{props.name}</div>
}

ReactDOM.render(<Root name = 'root'/>,document.getElementById("root"));

当然可以使用箭头函数
let Root = (props) => <div>{props.name}</div>
ReactDOM.render(,document.getElementById(“root”));

就是将函数作为组件,不需要state状态,生命周期函数,只需要一个props然后返回React元素即可。

高阶组件

上面的无状态组件如果要进行增强,就要用到类似于装饰器,将Root组件的div外部再加一层div。
先做一个加强函数,看栗子,科里化,加箭头函数的变化:

let Wrapper = Component=>props =>
(<div>
  {props.name}
  <hr />
  <Component />
</div>);
let Root = props=> <div>{props.name}</div>;
let NewRoot = Wrapper(Root)
ReactDOM.render(<NewRoot name = 'root'/>,document.getElementById("root")); // 注意命名。开头大写

再加装饰器:

let Wrapper = Component=>props =>
(<div>
  {props.setName}
  <hr />
  <Component {...props}/>//在传进来的Component组件中加入属性,使得root可以使用props
</div>);

@Wrapper
class Root extends React.Component{
  render(){
    return <div>{this.props.setName}</div>;
  } 
}
ReactDOM.render(<Root setName = 'root'/>,document.getElementById("root"));

带参装饰器
就是多几个箭头函数的事。例如

let Wrapper =id => Component=>props =>
(<div>
  {props.setName}
  <hr />
  <Component {...props} ids={id}/>
</div>);

@Wrapper('this id')
class Root extends React.Component{
  render(){
    return <div> {this.props.setName, this.props.ids}</div>;
  } 
}

ReactDOM.render(<Root setName = 'root'/>,document.getElementById("root"));

可以将参数传到各个组件中。这样更像面向对象语言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值