事件处理
通过 onXxxx 属性指定事件处理函数(小驼峰形式)
通过 event.target 可以得到发生事件的 dom 元素
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
在原生 DOM 中,我们可以通过返回 false 来阻止默认行为,但是这在 React 中是行不通的,在 React 中需要明确使用 preventDefault() 来阻止默认行为。
事件回调函数里的 event 是经过 React 特殊处理过的(遵循 W3C 标准),所以可以放心地使用它,而不用担心跨浏览器的兼容性问题。
注意:在使用事件回调函数的时候,需要特别注意 this 的指向问题。
因为在 React 里,除了构造函数和生命周期钩子函数里会自动绑定 this 为当前组件外,其他的都不会自动绑定 this 的指向为当前组件,因此需要我们自己注意好 this 的绑定问题。
通常而言,在一个类方式声明的组件里事件回调,需要在组件的 constructor 里绑定回调方法的 this 指向。
render函数
当组件的 state 或者 props 发生改变的时候,render 函数就会重新执行
当父组件的 render 函数重新执行时,子组件的 render 函数也会重新执行
关于虚拟DOM:
1.本质是object类型的对象(一般对象)
⒉.虚拟DOM比较"轻”,真实DOM比较"重”,因为虚拟DOM是React内部在用,无需真实DOM上:那么多的属性。3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
大致过程:
state数据
jsx模版
数据+模版 结合,生成虚拟DOM
(虚拟DOM就是一个JS对象,用它来描述真实的DOM)(损耗了性能)
用虚拟DOM的结构生成真实的DOM来显示
state发生改变
数据+模版 生成新的虚拟DOM(极大提升了性能)
比较原始虚拟DOM和新的虚拟DOM的区别,找差异(极大提升了性能)
直接操作DOM,改变内容
优点:
性能提升
使得跨端应用得以实现
虚拟 DOM 中的 key 的作用:
当状态中的数据发生改变时,react 会根据新数据生成新虚拟 DOM
随后 react 会进行新虚拟 DOM和旧虚拟 DOM的 diff 算法比较
若旧 DOM中找到了与新 DOM相同的 key,则会进一步判断两者的内容是否相同
如果也一样,则直接使用之前的真实 DOM,如果内容不一样,则会生成新的真实 DOM,替换掉原先的真实 DOM
若旧 DOM中没找到与新 DOM相同的 key,则直接生成新的真实 DOM,然后渲染到页面
不用 index 作为 key 的原因:
若对数据进行逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实 DOM 更新无法正确对比,造成效率低下,数据丢失
如果结构中还包含输入类的 dom,会产生错误 dom 更新,出现界面异常
diff算法
在 react 中如果某个组件的状态发生改变,react 会把此组件以及此组件的所有后代组件重新渲染
不过重新渲染并不代表会全部丢弃上一次的渲染结果,react 还是会通过 diff 去比较两次的虚拟 dom 最后 patch 到真实的 dom 上
diff 算法只比较同一层级,不跨级比较
tag 不相同则直接删掉重建,不再深度比较;tag 和 key 两者都相同,则认为是相同节点,也不再深度比较
虽然如此,如果组件树过大,diff 其实还是会有一部分的开销
因此react 内部通过 fiber 优化 diff 算法,外部建议开发者使用 SCU 和pureComponent
生命周期
旧版本
一共有四条路线
挂载阶段
这个阶段是做初始化操作,主要有这几个钩子函数:
static defaultProps
设置 props 的默认值
static defaultProps = {
name: '子组件设置的默认props'
}
static propTypes
props 数据类型检查
static propsTypes = {
name: PropTypes.string // 限定name为string类型
}
constructor(props)
构造函数的作用:
- 初始化 props and state
- 绑定事件处理函数
constructor(props) { super(props); this.state = {number: 0}; this.handlexxx = this.handlexxx.bind(this) }
componentWillMount()
组件挂载前钩子(17版本后不推荐使用)**
由于 React 未来的版本中推出了异步渲染,DOM 被挂载之前的阶段都可以被打断重来,导致 componentWillMount 、 componentWillUpdate 、 componentWillReceiveProps 在一次更新中可能会被触发多次,因此那些只希望触发一次的副作用应该放在 componentDidMount 中。
这也就是为什么要把异步请求放在 componentDidMount 中,而不是放在 componentWillMount 中的原因,是为了向后兼容。
componentWillMount() {
// componentWillMount在渲染过程中可能会执行多次
}
componentDidMount()
组件挂载成功钩子,该过程组件已经成功挂载到了真实 DOM 上。
由于在渲染过程中只执行一次,因此常用来:
- 监听事件;
- 获取到真实 DOM;
- 请求后台接口。
componentDidMount(){
fetch('https://api.github.com/users').then(res=>res.json()).then(users=>{
console.log(users);
this.setState({users});
});
}
更新阶段
这个阶段主要是做状态更新操作,主要有这几个钩子函数:
componentWillReceiveProps(newProps)
父组件更新 props 钩子**(17版本后不推荐使用)**
shouldComponentUpdate(nextProps, nextState)
组件是否更新钩子
由于 React 父组件更新,必然会导致子组件更新,因此我们可以在子组件中通过手动对比 props 与 nextProps,state 与 nextState 来确定是否需要重新渲染子组件,如果需要则返回true,不需要则返回 false。该函数默认返回 true。
shouldComponentUpdate(nextProps, nextState) {
console.log('Counter', nextProps, nextState);
console.log('5. shouldComponentUpdate 询问组件是否需要更新');
return true;
}
componentWillUpdate()
组件更新前钩子**(17版本后不推荐使用)**
componentDidUpdate()
此生命周期方法在组件更新完后被调用。
- 因为组件已经重新渲染了所以这里可以对组件中的 DOM 进行操作;
- 在比较了 this.props 和 nextProps 的前提下可以发送网络请求。
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
卸载阶段
这个阶段主要是从 DOM 树中删除组件的操作,它的钩子只有一个 componentWillUnmount 。
componentWillUnmount()
这是 unmount 阶段唯一的生命周期,在这里进行的是善后工作:清理计时器、取消网络请求或者取消事件监听等。
新版本生命周期
新增了两个生命周期函数:
- static getDerivedStateFromProps(nextProps, prevState)
- getSnapshotBeforeUpdate(prevProps, prevState)
删除了以下生命周期函数:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
想使用 删除的那三个 从这个版本开始,只有新的 UNSAFE_ 生命周期名称将起作用:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
static getDerivedStateFromProps(nextProps, prevState)
在 render 前调用,在初始挂载以及后续更新时都会被调用。
此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。
他应该返回一个对象来更新 state。如果返回 null 则不更新任何内容。
它接收两个参数,一个是传进来的 nextProps 和之前的 prevState。
static getDerivedStateFromProps(nextProps, prevState){
console.log('getDerivedStateFromProps',nextProps,prevState);
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState)
在更新阶段 render 后挂载到真实 DOM 前进行的操作,它使得组件能在发生更改之前从 DOM 中捕获一些信息。此组件返回的任何值将作为 componentDidUpdate 的第三个参数。
getSnapshotBeforeUpdate(prevProps, prevState){
return "getSnapshotBeforeUpdate";
}
// 组件更新成功钩子
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot); // "getSnapshotBeforeUpdate"
}
17版本执行顺序图谱:
以上就是 Class 组件的生命周期函数以及使用方法,然而不论 Class 组件多好用,我们已经拥抱Hooks。
react脚手架
使用 create-react-app 快速构建 React 开发环境
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 Webpack + ES6 。
npm install -g create-react-app
文件结构
public — 静态资源文件夹
- favicon.icon — 网站偏爱图标
- index.html — 主页面(重要)
- logo192.png — logo图
- logo512 — logo图
- manifest.json — 应用加壳的配置文件
- robots.txt — 爬虫协议文件
src — 源码文件夹
- App.css — App组件的样式
- App.js — App组件 (重要)
- App.test.js — 给App测试
- index.css — 样式
- index.js — 入口文件 (重要)
- logo.svg — logo图
- reportWebVitals.js — 页面性能分析文件(需要web-vitals库的支持)
- setupTests.js — 组件单元测试的文件(需要jest-dom库的支持)
gitignore — git的选择性上传的配置文件
配置不会上传的文件信息。git ignore 从名称就可以看到。
package.json — Webpack配置和项目包管理文件
包含项目中依赖的第三方包(包的版本)和一些常用命令配置都在这个里边进行配置,当然脚手架已经为我们配置了一些了,目前位置,我们不需要改动。如果你对webpack了解,对这个一定也很熟悉。如果你的node_modules包删掉了,也可以借助package.json内容,执行npm install 或yarn install 重新生成node_modules。
README.md ----项目介绍文件
yarn.lock
Yarn使用确定性算法,在将文件放置到需要的位置之前构建整个依赖关系树。安装过程中重要信息存储到yarn.lock文件中,以便可以在安装依赖关系的每个系统之间共享!此文件包含有关已安装的每个依赖项的确切版本的信息以及代码的校验和以确保代码完全相同。
另外react脚手架默认隐藏了webpack配置文件。可以使用npm run eject,暴露webpack配置文件,但是无法再次隐藏起来。
react ajax
为了更好的演示跨域问题,下面使用nodejs建立了服务端程序。
nodejs的express框架搭建服务端软件,用来接受请求 。node server.js 即可运行
const express = require('express')
const app = express()
app.use((request,response,next)=>{
console.log('有人请求服务器1了');
console.log('请求来自于',request.get('Host'));
console.log('请求的地址',request.url);
next()
})
app.get('/students',(request,response)=>{
const students = [
{id:'001',name:'tom',age:18},
{id:'002',name:'jerry',age:19},
{id:'003',name:'tony',age:120},
]
response.send(students)
})
app.listen(5000,(err)=>{
if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students');
})
2. react程序使用axios发送请求
如下会报错跨域。数据可以发送给服务端,服务端返回的数据回不到客户端,因为客户端有ajax的同源策略限制。 下面程序默认运行在localhost:3000端口
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
getStudentsInfo = () => {
axios.get('http://localhost:5000/students').then(
rsp => { console.log('收到数据', rsp) },
error => { console.log('错误', error) }
)
}
render() {
return (
<button onClick={this.getStudentsInfo}>点我获取学生信息</button>
)
}
}
3.axios发送get请求和post请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
4.react脚手架配置代理解决跨域
第一种 3000没有向5000要
因为代理已配置所以请求时 向自己(客户端)请求 所以向3000发,而且如果请求的自己有就只向自己要数据不向5000转发
第二种
src建立serupProxy.js文件
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
```
说明:
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
代理后,react程序请求的地址为代理所在的端口和代理映射路径
小技巧:配置代理后,axios相当于给本机的react使用的端口发请求,然后代理给实际的地址(自己给自己的端口发请求)。那么axios请求中的url http://localhost:port/部分可以省略不写,axios会自动发到本react程序运行的端口。
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
getStudentsInfo = () => {
axios.get('http://localhost:3000/api1/students').then(
rsp => { console.log('收到数据', rsp) },
error => { console.log('错误', error) }
)
}
//http://localhost:3000/api2/cars 可简写为/api2/cars
//localhost:3000代理所在端口 api2为setupProxy配置的映射路径。
getCarsInfo = () => {
axios.get('http://localhost:3000/api2/cars').then(
rsp => { console.log('收到数据', rsp) },
error => { console.log('错误', error) }
)
}
render() {
return (
<div>
<button onClick={this.getStudentsInfo}>点我获取学生信息</button>
<button onClick={this.getCarsInfo}>点我获取车辆信息</button>
</div>
)
}
}