![](https://i-blog.csdnimg.cn/blog_migrate/6025029c15bdfb58d42c4abc0c732b7b.jpeg)
本文主讲解类组件,函数组件会在后续文章中学习
一、组件化开发
1. 概念
组件化是一种分而治之的思想:
如果将一个页面中所有的处理逻辑放在一起,处理起来会变得非常复杂,不利于后续的管理以及扩展
但如果讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么页面的管理和维护就变得非常容易了
2. react组件化
组件化是React的核心思想
![](https://i-blog.csdnimg.cn/blog_migrate/ad5a2427adb09812276789b95293ea22.png)
3. 组件的应用
在开发中充分的利用组件化的思想 :
尽可能的将页面拆分成一个个小的、可复用的组件
让我们的代码更加方便组织和管理,并且扩展性也更强
4. 组件的分类
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
根据组件的定义方式
分成:函数组件(Functional Component )和类组件(Class Component) => 主要!
根据组件内部是否有状态需要维护
分成:无状态组件(Stateless Component )和有状态组件(Stateful Component)
根据组件的不同职责
分成:展示型组件(Presentational Component)和容器型组件(Container Component);
还有很多组件的其他概念:比如异步组件、高阶组件...
这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离
函数组件、无状态组件、展示型组件主要关注 => UI的展示
类组件、有状态组件、容器型组件主要关注 => 数据逻辑
01 - 类组件
概念
类组件的定义有如下要求
组件的名称是大写字符开头(无论类组件还是函数组件)
类组件需要继承自 React.Component
类组件必须实现render函数
在ES6之前,可以通过create-react-class 模块来定义类组件
但是目前官网建议我们使用ES6的class类定义
使用class定义一个组件:
constructor是可选的,我们通常在constructor中初始化一些数据
this.state中维护的就是我们组件内部的数据
render() 方法是 class 组件中唯一必须实现的方法
/**
* 内部有单独导出Component
* import React from 'react';
* import { Component } from 'react';
* 所以可以合并
*/
import React, { Component } from 'react';
export default class App extends Component {
constructor() {
super();
this.state = {};
}
render() {
return (
<div>
<h2>title</h2>
</div>
);
}
}
render函数的返回值
React 元素
通过jsx编写的代码就会被编译成React.createElement,所以返回的就是一个React元素
render() {
return (
<div>
<h2>title</h2>
</div>
);
}
数组或 fragments
react内部会一次遍历数组,展示在界面中
render() {
// return [1, 2, 3, 4];
return[
<h1>h1</h1>,
<h21>h2</h21>,
<h3>h3</h3>,
<h4>h4</h4>
]
}
字符串或数值类型
它们在 DOM 中会被渲染为文本节点
render() {
// return 'afdsafdsa';
return 123;
}
Portals
可以渲染子节点到不同的 DOM 子树中
布尔类型或 null
什么都不渲染
render() {
// 界面上什么都没有 => undefined、null、boolean;
// return undefined;
// return null;
// return true;
return false;
}
02 - 函数组件
函数组件是使用function来进行定义的函数
只是这个函数会返回和类组件中render函数返回一样的内容
函数组件有自己的特点(当然,后面有hooks后,就不一样了):
没有生命周期,也会被更新并挂载,但是没有生命周期函数
this关键字不能指向组件实例(因为没有组件实例)
没有内部状态(state)
// 函数式组件 => 很单纯,只是为了展示数据
function App(props) {
// 返回值: 和类组件中render函数返回的是一致
return <h1>App Functional Component</h1>
}
export default App
5. 题外话
Snippets 快捷键 - 插件
![](https://i-blog.csdnimg.cn/blog_migrate/45279b6f5ecc819a3bed10eb07c47702.png)
imr
import React from 'react'
rce
生成类组件
rpce
生成更优化的类组件
二、组件生命周期
1. 生命周期概念
很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期
生命周期和生命周期函数的关系:
生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段
比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程
比如更新过程(Update),组件状态发生变化,重新更新渲染的过程
比如卸载过程(Unmount),组件从DOM树中被移除的过程;
React内部为了告诉我们当前处于哪些阶段,会对组件内部实现的某些函数进行回调
这些函数就是生命周期函数:
比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调
比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调
比如实现componentWillUnmount函数:组件即将被移除时,就会回调
可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能
ps : React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的
2. 常用生命周期函数
图解析
![](https://i-blog.csdnimg.cn/blog_migrate/40b5e0f51570bef263133b0b76d4c8df.png)
constructor
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数
constructor中通常只做两件事情
通过给 this.state 赋值对象来初始化内部的state
为事件绑定实例(this)
import { Component } from 'react';
export default class App extends Component {
// 1. 第一执行
constructor() {
super();
// 初始化内部的state
this.state = {};
// 给方法绑定实例(this)
this.onClick = this.onClick.bind(this);
}
onClick() {
console.log('click', this);
}
// 2. 第二执行
render() {
return 'ohohoh';
}
}
componentDidMount
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用
通常进行的操作 :
依赖于DOM的操作可以在这里进行
在此处发送网络请求就最好的地方(官方建议)
可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
import { Component } from 'react';
export default class App extends Component {
// 1. 第一执行
constructor() {
super();
this.state = {};
}
// 2. 第二执行
render() {
return 'ohohoh';
}
// 3. 第三执行 : 组件被渲染到dom,已挂载
componentDidMount() {
// 操作dom
document.querySelector('#root');
// 网络请求
// axios.get().then().catch()
// 添加订阅
}
}
componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法
通常进行的操作 :
当组件更新后,可以在此处对 DOM 进行操作
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求
例如,当 props 未发生变化时,则不会执行网络请求)
import { Component } from 'react';
export default class App extends Component {
// 1. 第一执行
constructor() {
super();
this.state = {
message: '冲啊,宇智波海绵宝宝'
};
console.log('第一执行 : ', 'constructor');
}
// 2. 第二执行 || 第五执行
render() {
console.log('第二 || 第五执行 : ', 'render');
const { message } = this.state;
return (
<div>
<h2>{message}</h2>
<button onClick={_ => this.onClick()}>修改</button>
</div>
);
}
// 4. 第四执行
onClick() {
// 更改完成后,会重新执行render函数
this.setState({
message: '宇智波火炎阵!!!'
});
console.log('第四执行 : ', 'onClick');
}
// 3. 第三执行
componentDidMount() {
console.log('第三执行 : ', 'componentDidMount');
}
// 5. 第六执行
// 组件的Dom被更新完成 : Dom发生更新
componentDidUpdate() {
console.log('第六执行 : ', 'componentDidUpdate');
}
}
componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用
import { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<h2>我显示了</h2>
</div>
);
}
// 当App组件被移除时,会被调用该生命周期
componentWillUnmount() {
console.log('第五执行 : ', 'componentWillUnmount');
}
}
// 其他页面的操作
{
{
/* 比如刚开始isShowCommp这个为true,后面变为false */
}
isShowCommp && <App />;
}
通常进行的操作 :
在此方法中执行必要的清理操作
清除 time
取消网络请求
清除在 componentDidMount() 中创建的订阅等
3. 不常用生命周期函数
React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用 : React - 过往API
图解析
![](https://i-blog.csdnimg.cn/blog_migrate/5d6bc22429205cf543eab8555a48a615.png)
getDerivedStateFromProps
getDerivedStateFromProps() 在调用 render方法之前调用,在初始化和后续更新都会被调用
返回一个对象来更新 state, 如果返回 null 则不更新任何内容
参数 :
第一个参数为即将更新的 props
第二个参数为上一个状态的 state
可以比较props 和 state来加一些限制条件,防止无用的state更新
getDerivedStateFromProps 是一个静态函数, 不能使用this , 也就是只能作一些无副作用的操作
说实话,不知道干嘛的,先留着
shouldComponentUpdate
shouldComponentUpdate() 在调用 render方法之前调用,控制是否重新执行render函数
该生命周期函数可以做性能优化,
import { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<h2>我显示了</h2>
</div>
);
}
// 这么设定后,就算执行this.setState()后,render函数不会执行,页面不会刷新
shouldComponentUpdate() {
return false;
}
}
getSnapshotBeforeUpdate
在React更新DOM 之前回调的一个函数
可以获取DOM更新前的一些信息(比如说滚动位置)
![](https://i-blog.csdnimg.cn/blog_migrate/dfad9175301de052159405c2936229fd.png)
三、父子组件间的通信
1. 父组件传递子组件
父组件通过 属性=值 的形式来传递给子组件数据
子组件通过 props 参数获取父组件传递过来的数据
也可以直接展开
![](https://i-blog.csdnimg.cn/blog_migrate/97d39acebf68a2ce1ca7b61e482bc45c.png)
代码 : code
父组件
import React, { Component } from 'react';
import Text from './Text';
export class App extends Component {
render() {
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return (
<div>
App
{/** 传递属性 */}
<Text name='start' arr={arr} />
</div>
);
}
}
export default App;
子组件
import React, { Component } from 'react';
export class Text extends Component {
// 如果这里不定义state,也可以不写contructor,内部会自动做这一步操作
constructor(props) {
// 相当于this.props = props
super(props);
}
render() {
// 直接使用
const { name, arr } = this.props;
console.log('props', name, arr);
return (
<div>
<span>{name}</span>
<ul className='item'>
{arr.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
}
export default Text;
参数验证 : propTypes
可以使用Flow或者TypeScrip进行类型验证
也可以通过 prop-types来进行参数验证
![](https://i-blog.csdnimg.cn/blog_migrate/32d775e93bc56b0d72768893b7fccb89.png)
import React, { Component } from 'react';
// 1. 导入这个验证的包
import PropTypes from 'prop-types';
export class Text extends Component {
constructor(props) {
super(props);
this.state = {
name: 'start'
};
}
render() {
const { name, arr } = this.props;
console.log('props', name, arr);
return <div>{name}</div>;
}
}
// 2. 给类绑定一个propTypes
Text.propTypes = {
// 3. 设定name => 是个string类型,且是个必穿类型
name: PropTypes.string.isRequired,
// 4. 设定arr => 是个array类型
arr: PropTypes.array
};
export default Text;
更多的验证方式,可以参考官网: proptypes
比如验证数组,并且数组中包含哪些元素
比如验证对象,并且对象中包含哪些key以及value是什么类型
比如某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired
参数默认值 : defaultProps
方式一
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export class Text extends Component {
constructor(props) {
super(props);
this.state = {
name: 'start'
};
}
render() {
const { name, arr } = this.props;
console.log('props', name, arr);
return <div>{name}</div>;
}
}
Text.propTypes = {
name: PropTypes.string,
arr: PropTypes.array
};
// 给参数设定默认值
Text.defaultProps = {
name: '我是默认值',
arr: ['我', '是', '默', '认', '值']
};
export default Text;
方式二
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export class Text extends Component {
// 给参数设定默认值
static defaultProps = {
name: '我是默认值',
arr: ['我', '是', '默', '认', '值']
};
constructor(props) {
super(props);
this.state = {
name: 'start'
};
}
render() {
const { name, arr } = this.props;
console.log('props', name, arr);
return <div>{name}</div>;
}
}
Text.propTypes = {
name: PropTypes.string,
arr: PropTypes.array
};
export default Text;
2. 子组件传递父组件
通过props传递消息 : 父组件给子组件传递一个回调函数,子组件中调用这个函数即可
父组件
import React, { Component } from 'react';
import Text from './Text';
export class App extends Component {
constructor() {
super();
this.state = {
counter: 100
};
}
// 2. 子组件回调该方法
changeCount(count) {
this.setState({
counter: this.state.counter + count
});
}
render() {
const { counter } = this.state;
return (
<div>
当前计数 : {counter}
{/** 1. 传递方法到子组件 */}
<Text addClick={count => this.changeCount(count)} />
</div>
);
}
}
export default App;
子组件
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export class Text extends Component {
btnClick(count) {
console.log('count', count);
// 2. 调用父组件传递过来的方法,并且传递参数过去
this.props.addClick(count);
}
render() {
return (
<div>
<button onClick={() => this.btnClick(1)}>+1</button>
<button onClick={() => this.btnClick(10)}>+10</button>
<button onClick={() => this.btnClick(30)}>+30</button>
</div>
);
}
}
// 1. 设定接受参数为方法,也可不设定
Text.propTypes = {
addClick: PropTypes.func
};
export default Text;
3. 案例
效果
![](https://i-blog.csdnimg.cn/blog_migrate/598c570ba9df65ba8c50d0d29ebe96e1.gif)
父组件
import React, { Component } from 'react';
import Text from './Text';
export class App extends Component {
constructor() {
super();
this.state = {
list: ['首页', '详情', '个人'],
currentIndex: 0
};
}
itemChange(index) {
this.setState({
currentIndex: index
});
}
render() {
const { list, currentIndex } = this.state;
return (
<div>
<Text list={list} itemChange={(index) => this.itemChange(index)} />
{list[currentIndex]}
</div>
);
}
}
export default App;
子组件
css
.nav{
display: flex;
align-items: center;
justify-content: center;
list-style: none;
margin: 0;
padding: 0;
}
.nav .item{
flex: 1;
font-size: 25px;
padding: 10px;
text-align: center;
}
.nav .item.active{
color:red;
border-bottom: 1px solid red;
}
jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './style.css';
export class Text extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0
};
}
itemClick(index) {
this.setState({
currentIndex: index
});
this.props.itemChange(index);
}
render() {
const { currentIndex } = this.state;
const { list } = this.props;
return (
<ul className='nav'>
{list.map((item, index) => {
return (
<li
key={item}
className={`item ${currentIndex === index ? 'active' : ''}`}
onClick={() => this.itemClick(index)}>
<span>{item}</span>
</li>
);
})}
</ul>
);
}
}
Text.propTypes = {
list: PropTypes.array.isRequired
};
export default Text;
四、组件中的插槽 - slot
插槽 : 让使用者可以决定某一块区域到底存放什么内容
1. children实现插槽
每个组件都可以获取到 props.children:包含组件的开始标签和结束标签之间的内容
例如 : <子组件> 内容 </子组件>
当内容只有一个元素时 : children === 内容
当内容有多个元素时 : chindren 是一个数组
效果
![](https://i-blog.csdnimg.cn/blog_migrate/a58f0f40c41782ff9b294370416ecceb.png)
父组件
import React, { Component } from 'react';
import NavBar from './NavBar';
export class App extends Component {
render() {
return (
<div>
<NavBar>
<button>按钮</button>
<h2>h2</h2>
<i>i</i>
</NavBar>
<NavBar>
<h3>h3</h3>
<b>b</b>
<span>span</span>
</NavBar>
</div>
);
}
}
export default App;
子组件
jsx
import React, { Component } from 'react';
import './index.css';
export class index extends Component {
render() {
// 当有多个子元素时,children是一个数组
const { children } = this.props;
return (
<ul className='nav'>
{children.map((child, index) => {
return (
<li className='item' key={index}>
{child}
</li>
);
})}
</ul>
);
}
}
export default index;
css
*{
margin: 0;
padding: 0;
}
.nav{
display: flex;
align-items: center;
justify-content: center;
list-style: none;
margin: 0;
padding: 0;
height: 40px;
margin-bottom: 10px;
line-height: 40px;
}
.nav .item{
flex: 1;
height: 100%;
font-size: 25px;
text-align: center;
}
.nav .item:nth-child(1){
background-color: #f00;
}
.nav .item:nth-child(2){
background-color: #0f0;
}
.nav .item:nth-child(3){
background-color: #00f;
}
2. props实现插槽
通过children实现的方案虽然可行,但是有一个弊端
通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生
![](https://i-blog.csdnimg.cn/blog_migrate/b21fe4b42ae400c204826d3154222495.png)
五、祖孙组件通信 - Context
如果层级很多的话,一层层传递是非常麻烦,并且代码是非常冗余的
所以React提供了一个API:Context
Context 提供了一种在组件之间共享此类值的方式
而不必显式地通过组件树的逐层传递 props
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据
例如当前认证的用户、主题或首选语言
![](https://i-blog.csdnimg.cn/blog_migrate/5459b85bc9056c0ecafdbad2456efbf2.png)
相关API
React.createContext
创建一个需要共享的Context对象:
如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值
defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
// 封装成一个单独的js
import React from 'react';
export const MyContext = React.createContext();
Context.Provider
包裹需要使用数据的子组件
ps : 每个 Context 对象都会返回一个Provider React 组件,它允许消费组件订阅 context 的变化:
Provider 接收一个 value 属性,传递给消费组件
一个 Provider 可以和多个消费组件有对应关系
多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
import React, { Component } from 'react';
// 1. 导入
import { MyContext } from './context/MyContext';
import One from './One';
export class App extends Component {
render() {
const obj = { name: 'avc', age: 19 };
return (
<div>
{/* 2. 包裹 => value:需要传递的数据*/}
<MyContext.Provider value={obj}>
{/* 3. 子组件 */}
<One></One>
</MyContext.Provider>
</div>
);
}
}
export default App;
Class.contextType
在子组件中挂载一下context
ps : 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
可以使用 this.context 来消费最近 Context 上的那个值
可以在任何生命周期中访问到它,包括 render 函数中
import React, { Component } from 'react';
import Two from './Two';
// 1. 导入相同的contenxt
import { MyContext } from './context/MyContext';
export class One extends Component {
render() {
// 3. 使用context
console.log(this.context); // {name: 'avc', age: 19}
return (
<div>
One
{/* 子组件中的子组件,里面进行相同的操纵后也能拿到context传递的数据 */}
<Two />
</div>
);
}
}
// 2. 挂载过来
One.contextType = MyContext;
export default One;
Context.Consumer
如果子组件是函数式组件的时候,使用Consumer
多个context的时候,也可以使用
// 1. 导入context
import { MyContext } from './context/MyContext';
function Two() {
return (
<div>
<h2>Two</h2>
{/* 2. 使用MyContext.Consumer */}
<MyContext.Consumer>
{/* 3. 这里使用一个函数,value就是传递过来的数据 */}
{(value) => {
return <h2>{value.name}</h2>;
}}
</MyContext.Consumer>
</div>
);
}
export default Two;
多个Context使用
祖元素
import React, { Component } from 'react';
import { MyContext } from './context/MyContext';
import { MyContextTwo } from './context/MycontextTwo';
import One from './One';
export class App extends Component {
render() {
const obj = { name: 'aaa', age: 10 };
return (
<div>
{/* 外层context*/}
<MyContext.Provider value={obj}>
{/* 内层context*/}
<MyContextTwo.Provider value={{ name: 'ccc', age: 20 }}>
{/* 子组件 */}
<One></One>
</MyContextTwo.Provider>
</MyContext.Provider>
</div>
);
}
}
export default App;
子元素 : 类组件
import React, { Component } from 'react';
// 1. 导入相同的contenxt
import { MyContext } from './context/MyContext';
import { MyContextTwo } from './context/MycontextTwo';
export class One extends Component {
render() {
// 3. 使用挂载的context
console.log(this.context); // {name: 'avc', age: 19}
return (
<div>
{/* 4. 使用其他的context */}
<MyContextTwo.Consumer>
{/* {(value) => <h5>{value.name}</h5>} */}
{(value) => this.otherContext(value)}
</MyContextTwo.Consumer>
</div>
);
}
// 可以提取出来
otherContext(value) {
return <h5>{value.name}</h5>;
}
}
// 2. 挂载其中一个context过来
One.contextType = MyContext;
export default One;
子元素 : 函数组件
// 1. 导入context
import { MyContext } from './context/MyContext';
import { MyContextTwo } from './context/MycontextTwo';
function Two() {
return (
<div>
{/* 其中一个context */}
<MyContext.Consumer>
{ (value) => <h2>{value.name}</h2> }
</MyContext.Consumer>
{/* 另外一个context */}
<MyContextTwo.Consumer>
{ (value) => <h2>{value.name}</h2> }
</MyContextTwo.Consumer>
</div>
);
}
export default Two;
默认值defaultValue
context
import React from 'react';
// 定义context的默认值
const defaultValue = { name: '我是默认值' };
export const MyContext = React.createContext(defaultValue);
祖元素
import React, { Component } from 'react';
// 1. 导入context
import { MyContext } from './context/MyContext';
import One from './One';
export class App extends Component {
render() {
const obj = { name: 'aaa', age: 10 };
return (
<div>
{/* 2. 使用context.provider */}
<MyContext.Provider value={obj}></MyContext.Provider>
{/*3. 子组件没有在context的包裹中 */}
<One></One>
</div>
);
}
}
export default App;
子元素
import React, { Component } from 'react';
// 1. 导入相同的contenxt
import { MyContext } from './context/MyContext';
export class One extends Component {
render() {
// 3. 这里拿到的就是默认数据
console.log(this.context); // {name: '我是默认值'}
return <div>哈哈哈</div>;
}
}
// 2. 挂载context过来
One.contextType = MyContext;
export default One;
六、setState
1. 为什么使用setState
开发中并不能直接通过修改state的值来让界面发生更新
React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化
必须通过setState来告知React数据已经发生了变化
在组件中并没有实现setState的方法,为什么可以调用
原因很简单,setState方法是从Component中继承过来的
2. setState用法
用法一
原理 : Object.assign(this.state,newState)
ps : 把新传入的对象和原来的对象合并,若有相同的,取代
this.setState({
name: 'bbb',
age: 18
});
用法二
/**
* 传入回调函数 => 返回一个新对象
* 好处一 : 可以进行逻辑编写
* 好处二 : 当前回调函数会将组件之前的state和组件接收的props传递进来
*/
this.setState((preState, props) => {
// 返回一个新对象
return {
name: 'bbb',
age: 18
};
});
3. setState异步更新
setState的更新是异步的
this.setState({
name: 'bbb',
age: 18
});
console.log(this.state.name, this.state.age); // aaa 10
最终打印结果是aaa 10
可见setState是异步的操作,并不能在执行完setState之后立马拿到最新的state的结果
01 - 优点
setState设计为异步,可以显著的提升性能
如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低
最好的办法应该是获取到多个更新,之后进行批量更新
如果是同步,更新了state,但是还没有执行render函数,那么state和props不能保持同步
state和子组件的props不能保持一致性,会在开发中产生很多的问题
02 - 获取异步的结果
回调函数
setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行
itemClick() {
this.setState(
{
name: 'bbb',
age: 18
},
// 使用回调函数
() => {
console.log(this.state.name, this.state.age); // bbb 18
}
);
}
生命周期
itemClick() {
this.setState({
name: 'bbb',
age: 18
});
}
componentDidUpdate() {
// 生命周期获取修改后的值
console.log(this.state.name, this.state.age); // bbb 18
}
03 - React版本
React18之前
分成两种情况:
在组件生命周期或React合成事件中,setState是异步
在setTimeout或者原生dom事件中,setState是同步
![](https://i-blog.csdnimg.cn/blog_migrate/e5840677b54a1c6f9a0fe1952c837459.png)
React18之后
在React18之后,默认所有的操作都被放到了批处理中(异步处理)
![](https://i-blog.csdnimg.cn/blog_migrate/ab8fe7a7df31616f00727939b721b65b.png)
默认是异步,可以通过 flushSync 设置成同步
import React, { Component } from 'react';
// 1. 导入
import { flushSync } from 'react-dom';
export class App extends Component {
constructor() {
super();
this.state = {
name: 'aaa',
age: 10
};
}
itemClick() {
// 2. 包裹
flushSync(() => {
this.setState({
name: 'bbb'
});
});
// 3. 获取到值 => 会先执行rander,然后再执行这里
console.log(this.state); // {name:'bbb',age:10}
}
七、React性能优化
1. Diff算法优化
概念
渲染流程
![](https://i-blog.csdnimg.cn/blog_migrate/020675550f4e696dfb4a14d3dc9af44e.png)
rander函数中返回JSX
JSX会创建对应的ReactElement
ReactElement最终会形成一个树结构,这个树结构就是虚拟DOM
最后React会根据虚拟DOM渲染成真实DOM
更新流程
![](https://i-blog.csdnimg.cn/blog_migrate/92b5dd92907af6b745b3e453edbddb95.png)
diff算法
React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树
React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
这个判断的过程就是diff算法
优化
React对这个算法进行了优化,将其优化成了O(n)
同层节点之间相互比较,不会垮节点比较
![](https://i-blog.csdnimg.cn/blog_migrate/6be2ebeba1e9e4efd69d99d627ab9faa.png)
不同类型的节点,产生不同的树结构
![](https://i-blog.csdnimg.cn/blog_migrate/f75775651cdfef0b70d604197efa8c9e.png)
开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定
![](https://i-blog.csdnimg.cn/blog_migrate/5a181632579dba259263788705f68780.png)
2. 类组件性能优化SCU
问题
只要是修改了 App中的数据,所有的组件都需要重新render,进行diff算法,性能必然是很低的
事实上,很多的组件没有必须要重新render
调用render应该有一个前提,就是依赖的数据(state、 props)发生改变时,再调用自己的render方法
import React, { Component } from 'react';
import Home from './Home';
export class App extends Component {
constructor() {
super();
this.state = {
name: 'aaa',
age: 10
};
}
itemClick() {
this.setState({
name: 'bbb'
});
}
/**
* 问题一 : 调用this.setState后,就算没有相关依赖的变量,
Home子组件乃至子组件的子组件,它们的render方法也会执行
* 问题二 : this.setState({
name: 'aaa'
}) 没有修改内容,同样会全部重新渲染
* 效率极其低下
*/
render() {
console.log('render执行');
const { name, age } = this.state;
return (
<div>
<h1 onClick={() => this.itemClick()}>
{name} - {age}
</h1>
{/* 可以不必重新渲染 */}
<Home />
</div>
);
}
}
export default App;
方法
通过shouldComponentUpdate方法来控制render方法是否被调用
shouldComponentUpdate
![](https://i-blog.csdnimg.cn/blog_migrate/3a1f9f2f5d556f99eb43d0c72fa21015.png)
import React, { Component } from 'react';
import Home from './Home';
export class App extends Component {
constructor() {
super();
this.state = {
name: 'aaa',
age: 10
};
}
itemClick() {
this.setState({
name: 'bbb'
});
}
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, '没有props就为空');
console.log(`旧 : ${this.state.name}, 新 : ${nextState.name}`); // aaa bbb
// 当不同时,才重新渲染, render函数执行
// return this.state.name !== nextState.name || this.state.age !== nextState.age;
// 如果作为子组件,有props的情况下,还需要队props进行对比
return (
this.state.name !== nextState.name ||
this.state.age !== nextState.age ||
// props
this.props.message !== nextProps.message
);
}
render() {
console.log('render执行');
const { name, age } = this.state;
return (
<div>
<h1 onClick={() => this.itemClick()}>
{name} - {age}
</h1>
{/* 可以不必重新渲染 */}
<Home />
</div>
);
}
}
export default App;
PureComponent
如果所有的类,都需要手动来实现 shouldComponentUpdate,那么会给开发增加非常多的工作量
React已经考虑到了这一点,所以React已经默认实现好了
将class继承自PureComponent
// 1. 不导入Component,导入PureComponent
import React, { PureComponent } from 'react';
import Home from './Home';
// 2. 不继承Component,继承PureComponent
export class App extends PureComponent {
render() {
console.log('render执行');
return (
<div>
<h1>App</h1>
<Home />
</div>
);
}
}
export default App;
PureComponent : 本质是进行了一个浅层比较 => 只比较第一层,不比较深层
![](https://i-blog.csdnimg.cn/blog_migrate/f4f412e2481ce76db8e6d4b416fe47de.png)
3. 函数组件性能优化SCU
需要使用一个高阶组件memo => 包裹一下函数组件即可
import { memo } from 'react';
// 未使用memo的函数组件
// export default function Home(props) {
// console.log('home render');
// return <h2>123:{props.name}</h2>;
// }
// 使用了memo的函数组件
export default memo(function Home(props) {
console.log('home render');
return <h2>123:{props.name}</h2>;
});
4. 不可变数据的力量
不要直接操作,而是赋予新的地址
ps : 在setState中,进行结构即可,不管是整体,还是修改单个属性依旧如此
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
friend: ['a', 'b', 'c']
};
}
addClick() {
const newEl = 'd';
/**
* 直接修改原有state,重新设置一遍
在PureComponent中,是不会重新渲染页面的
因为shouldComponentUpdate回调函数里,nextState - this.state两个值是一样的,就不会触发页面的刷新
*/
// this.state.friend.push(newEl);
// this.setState({
// friend: this.state.friend
// });
/**
* 正确方式一
*/
// this.state.friend.push(newEl);
// this.setState({
// // 相当于拿新的数组,覆盖了原来的数组,地址不一样了
// friend: [...this.state.friend]
// });
/**
* 正确方式二
*/
const friend = [...this.state.friend];
friend.push(newEl);
this.setState({
friend
});
}
render() {
console.log('render执行');
const { friend } = this.state;
return (
<div>
<ul>
{friend.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
<button onClick={(e) => this.addClick()}>增加元素</button>
</div>
);
}
}
export default App;
八、Ref获取DOM和组件
Ref获取DOM
方式一 : 传入字符串
使用时通过 this.refs.传入的字符串格式 获取对应的元素 => 已经被废弃了
import React, { PureComponent } from 'react';
export class App extends PureComponent {
onNativeClick() {
// 2. 通过ref属性获取到DOM节点 不过已经被废弃了
console.log('this.refs.coderRef', this.refs.coderRef);
}
render() {
return (
<div>
{/* 1. 增加一个ref属性 */}
<h2 ref='coderRef' onClick={() => this.onNativeClick()}>
hello world
</h2>
</div>
);
}
}
export default App;
方式二 : 传入一个对象 => 推荐
对象是通过 React.createRef() 方式创建出来的
使用时获取到创建的对象其中有一个current属性就是对应的元素
// 通过createRef获取dom元素
import React, { PureComponent, createRef } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {};
// 1. 通过createRef创建一个ref对象,因为不需要响应式,所以不需要放在state中
this.coderRef = createRef();
}
onNativeClick() {
// 3. 通过current属性获取dom元素
console.log('this.coderRef', this.coderRef.current);
}
render() {
return (
<div>
{/* 2. 绑定到ref属性上 */}
<h2 ref={this.coderRef} onClick={() => this.onNativeClick()}>
hello world
</h2>
</div>
);
}
}
export default App;
方式三 : 传入一个函数
该函数会在DOM被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存
使用时,直接拿到之前保存的元素对象即可
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {};
// 1. 创建一个ref对象
this.coderRef = null;
}
onNativeClick() {
// 3. 拿到dom元素
console.log('this.coderRef', this.coderRef);
}
render() {
return (
<div>
<h2
// 2. 挂载后,会自动进行回调该函数,可以在这直接获取dom元素
ref={(el) => (this.coderRef = el)}
onClick={() => this.onNativeClick()}>
hello world
</h2>
</div>
);
}
}
export default App;
Ref获取类组件
ref 的值根据节点的类型而有所不同:
当 ref 属性用于 HTML 元素时
构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性
当 ref 属性用于自定义 class 组件时
ref 对象接收组件的挂载实例作为其 current 属性
import React, { PureComponent, createRef } from 'react';
// 子组件
class Text extends PureComponent {
text() {
console.log('Text组件的text方法');
}
render() {
return <h2>Text</h2>;
}
}
// 父组件
export class App extends PureComponent {
constructor() {
super();
this.state = {};
// 1. 通过createRef创建一个ref对象
this.textRef = createRef();
}
onComponentClick() {
// 3. 拿到子组件实例
console.log('this.textRef', this.textRef.current);
// 4. 调用子组件的方法
this.textRef.current.text();
}
render() {
return (
<div>
{/* 2. 绑定到ref属性上*/}
<Text ref={this.textRef} />
<button onClick={() => this.onComponentClick()}>获取子组件</button>
</div>
);
}
}
export default App;
Ref获取函数组件
不能在函数组件上使用 ref 属性,因为函数组件没有实例
但是某些时候,可能想要获取函数式组件中的某个DOM元素
这个时候可以通过 React.forwardRef 高阶组件
import React, { PureComponent, createRef, forwardRef } from 'react';
// 子组件
/**
* forwardRef 用于转发 ref,使得 ref 可以在函数组件中传递
* 1. forwardRef 接受一个渲染函数,函数的参数为 props 和 ref
* 2. forwardRef 返回一个组件,这个组件接受 props 和 ref 两个参数
*/
const Text = forwardRef(function (props, ref) {
return (
<div>
<h2>hello - 001</h2>
{/* 3. 把传递过来的ref绑定到这个元素上 */}
<h3 ref={ref}>hello - 002</h3>
<h4>hello - 003</h4>
</div>
);
});
// 父组件
export class App extends PureComponent {
constructor() {
super();
this.state = {};
// 1. 通过createRef创建一个ref对象
this.textRef = createRef();
}
onComponentClick() {
// 4. 拿到子组件的某一个dom元素
console.log('this.textRef', this.textRef.current);
}
render() {
return (
<div>
{/* 2. 绑定到ref属性上*/}
<Text ref={this.textRef} />
<button onClick={() => this.onComponentClick()}>获取子组件</button>
</div>
);
}
}
export default App;
九、受控组件 && 非受控组件
1. 受控组件
一般来说,当给表单组件绑定了value属性后,该表单组件就变成了受控组件
只能通过React来使用 setState()进行更新
在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value
这使得 React 的 state 成为唯一数据源
handleUsernameChange 在每次按键时都会执行并更新 React 的 state
因此显示的值将随着用户输入而更新
被 React 以这种方式控制取值的表单输入元素就叫做“受控组件
![](https://i-blog.csdnimg.cn/blog_migrate/c2723f6e0996360023f622460efa920e.png)
input
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
// 1. 定义需要绑定到输入框的数据
username: '',
password: ''
};
}
// 3. 监听到值改变后,更新message的值,再修改输入框的值
inputHandleChange(e) {
this.setState({
// 使用计算属性
[e.target.name]: e.target.value
});
}
submitHandle() {
console.log(this.state);
}
render() {
const { username, password } = this.state;
return (
<div>
{/* 2. 绑定到value属性上 => 监听onChange方法(不监听的话,无法输入值) */}
<label htmlFor='username'>
账号 :
<input
id='username'
type='text'
name='username'
value={username}
onChange={(e) => this.inputHandleChange(e)}
/>
</label>
<label htmlFor='password'>
密码 :
<input
id='password'
type='password'
name='password'
value={password}
onChange={(e) => this.inputHandleChange(e)}
/>
</label>
<button onClick={(e) => this.submitHandle(e)}>注册</button>
</div>
);
}
}
export default App;
checkbox
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
// 1. 定义需要绑定到输入框的数据
agree: false,
hobbies: [
{ key: 'sing', value: '唱', isChecked: false },
{ key: 'dance', value: '跳', isChecked: false },
{ key: 'rap', value: 'rap', isChecked: false }
]
};
}
// 3. 监听到值改变后,更新message的值,再修改输入框的值
checkHandleChange(e, index) {
// 单选
if (index === -1) {
return this.setState({
[e.target.name]: e.target.checked
});
}
// 多选
const { hobbies } = this.state;
hobbies[index].isChecked = e.target.checked;
this.setState({
// 使用计算属性
hobbies: [...hobbies]
});
}
submitHandle() {
console.log(this.state);
console.log(
'爱好',
this.state.hobbies.filter((item) => item.isChecked).map((item) => item.key)
);
}
render() {
const { agree, hobbies } = this.state;
return (
<div>
{/* 2. 绑定到value属性上 => 监听onChange方法(不监听的话,无法输入值) */}
<div className='one'>
<span>单选 === </span>
<label htmlFor='agree'>
<input
id='agree'
type='checkbox'
name='agree'
checked={agree}
onChange={(e) => this.checkHandleChange(e, -1)}
/>
同意
</label>
</div>
<div className='one'>
<span>多选 === </span>
{hobbies.map((item, index) => {
return (
<label htmlFor={item.key} key={item.key}>
<input
id={item.key}
type='checkbox'
name={item.key}
checked={item.isChecked}
onChange={(e) => this.checkHandleChange(e, index)}
/>
{item.value}
</label>
);
})}
</div>
<button onClick={(e) => this.submitHandle(e)}>注册</button>
</div>
);
}
}
export default App;
select
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
fruits: 'banana',
fruitsArr: ['banana']
};
}
// 3. 监听到值改变后,更新message的值,再修改输入框的值
selectHandleChange(e, type) {
// 单选
if (type === 'single') {
return this.setState({
fruits: e.target.value
});
}
// 多选 => e.target.selectedOptions 可以拿到所有多选的数据,按住shift多选
const options = Array.from(e.target.selectedOptions);
const optionsValue = options.map((item) => item.value);
this.setState({
// 使用计算属性
fruitsArr: [...optionsValue]
});
}
submitHandle() {
console.log(this.state);
}
render() {
const { fruits, fruitsArr } = this.state;
return (
<div>
{/* 2. 绑定到value属性上 => 监听onChange方法(不监听的话,无法输入值) */}
<div className='one'>
<span>单选 === </span>
<select value={fruits} onChange={(e) => this.selectHandleChange(e, 'single')}>
<option value='apple'>苹果🍎</option>
<option value='banana'>香蕉🍌</option>
<option value='orange'>橘子🍊</option>
</select>
</div>
<hr />
<hr />
<hr />
<div className='one'>
<span>多选 === </span>
<select
value={fruitsArr}
multiple
onChange={(e) => this.selectHandleChange(e, 'multiple')}>
<option value='apple'>苹果🍎</option>
<option value='banana'>香蕉🍌</option>
<option value='orange'>橘子🍊</option>
</select>
</div>
<button onClick={(e) => this.submitHandle(e)}>注册</button>
</div>
);
}
}
export default App;
2. 非受控组件
React推荐大多数情况下使用 受控组件 来处理表单数据
一个受控组件中,表单数据是由 React 组件来管理的
另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理
如果要使用非受控组件中的数据,可以使用 ref 来从DOM节点中获取表单数据
代码
import React, { PureComponent, createRef } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
message: '123'
};
// 1. 初始化ref
this.inputRef = createRef();
}
componentDidMount() {
// 3. 可以通过ref获取到DOM元素,然后绑定事件,监听值的变化
this.inputRef.current.addEventListener('input', (e) => {
console.log(e.target.value);
});
}
submitHandle() {
// 4. 通过ref获取到值
console.log(this.inputRef.current.value);
}
render() {
const { message } = this.state;
return (
<div>
{/* 2. 绑定ref,并使用defaultValue */}
<input type='text' defaultValue={message} ref={this.inputRef} />
<button onClick={(e) => this.submitHandle(e)}>注册</button>
</div>
);
}
}
export default App;
效果
![](https://i-blog.csdnimg.cn/blog_migrate/2d292698d0a4a37cf5a368b5820ec864.gif)
十、高阶组件
1. 定义
高阶组件是参数为组件,返回值为新组件的函数
高阶组件的英文是 Higher-Order Components,简称为 HOC
高阶组件本身不 是一个组件,而是一个函数
这个函数的参数是一个组件,返回值也是一个组件
/**
* 高阶组件是一个函数,参数是一个组件,返回值也是一个组件
*
* 高阶组件的作用是为了增强组件,给组件添加一些额外的功能
* 比如:给组件添加一些公共的状态、公共的逻辑、公共的生命周期
* 或者是给组件添加一些公共的UI
* 或者是给组件添加一些公共的数据
* 或者是给组件添加一些公共的方法等等
* 总之,高阶组件的作用就是为了增强组件,给组件添加一些额外的功能。
*/
// 定义高阶组件,参数是一个组件,返回值也是一个组件,
function hoc(wrapperComponent) {
// 1. 定义类组件
return class extends PureComponent {};
// 2. 定义函数组件
// return function () {
// return <div>函数组件</div>;
// }
}
高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式
2. 应用
props的增强
不修改原有代码的情况下,添加新的props
代码
import React, { PureComponent } from 'react';
// 1. 定义高阶组件
function enhanceProps(OriginComponent) {
return class extends PureComponent {
constructor(porps) {
super(porps);
this.state = { name: 'coder', age: 18 };
}
render() {
// 2. 注入props 本身的props和传入的props,都要注入到OriginComponent中
return <OriginComponent {...this.props} {...this.state} />;
}
};
}
// 3. 函数组件使用高阶组件
const Home = enhanceProps(function (props) {
const str = 'home';
return (
<div>
<h2>Home</h2>
<h3>
父组件传递过来的props ={'>'} level : {props.level}
</h3>
<h3>
增强的props ={'>'} name : {props.name} - age : {props.age}
</h3>
<h3>
本身定义的数据 ={'>'} str : {str}
</h3>
</div>
);
});
// 4. 类组件使用高阶组件
const Text = enhanceProps(
class extends PureComponent {
constructor(props) {
super(props);
this.state = { str: 'text' };
}
render() {
const { name, age, level } = this.props;
const { str } = this.state;
return (
<div>
<h2>Text:</h2>
<h3>
父组件传递过来的props ={'>'} level : {level}{' '}
</h3>
<h3>
增强的props ={'>'} name : {name} - age : {age}
</h3>
<h3>
本身定义的数据 ={'>'} str : {str}{' '}
</h3>
</div>
);
}
}
);
export class App extends PureComponent {
render() {
return (
<div>
<Home level='66' />
<Text level='99' />
</div>
);
}
}
export default App;
效果
![](https://i-blog.csdnimg.cn/blog_migrate/660781d7a9d07294058ea154991f9c42.png)
Context共享
共享全局主题颜色
context
import React from 'react';
export const ThemeContext = React.createContext();
父组件
import React, { PureComponent } from 'react';
import { ThemeContext } from './context/ThemeContext';
import Home from './components/Home';
export class App extends PureComponent {
render() {
return (
<div>
App
{/* 使用context提供主题数据 */}
<ThemeContext.Provider value={{ color: 'red', size: 30 }}>
<Home />
</ThemeContext.Provider>
</div>
);
}
}
export default App;
高阶组件
// 使用context
import { ThemeContext } from '../context/ThemeContext';
export default function withTheme(OriginComponent) {
// 返回一个函数组件
return (props) => {
// 渲染的内容
return (
<ThemeContext.Consumer>
{/* 使用context共享数据 */}
{
(value) => {
return <OriginComponent {...value} {...props} />;
}
}
</ThemeContext.Consumer>
);
};
}
子组件
import React, { PureComponent } from 'react';
import withTheme from '../hoc/with-theme';
export class Home extends PureComponent {
render() {
return (
<div>
Home
{/* 使用高阶组件中context的数据 */}
{this.props.color} {/* red */}
{this.props.size} {/* 30 */}
</div>
);
}
}
// 实际导出的是一个高阶组件
export default withTheme(Home);
渲染判断鉴权
父组件
import React, { PureComponent } from 'react';
import Home from './components/Home';
export class App extends PureComponent {
loginClick() {
// 模拟登录
sessionStorage.setItem('token', 123321);
// 因为没有调用setState,不会重新渲染页面
// 所以进行强制刷新, 重新渲染组件, 但是不推荐使用
this.forceUpdate();
}
render() {
return (
<div>
App
<Home />
<button onClick={() => this.loginClick()}>登录</button>
</div>
);
}
}
export default App;
高阶组件
export default function loginAuth(OriginComponent) {
return (props) => {
// 1. 从localStorage中获取token
const token = sessionStorage.getItem('token');
// 2. 判断token是否存在
if (token) {
// 2.1 如果存在,渲染组件
return <OriginComponent {...props} />;
} else {
// 2.2 如果不存在,跳转到登录页面
return <h2>请先登录</h2>;
}
};
}
子组件
import React, { PureComponent } from 'react';
import loginAuth from '../hoc/login-auth';
export class Home extends PureComponent {
render() {
return <div>Home</div>;
}
}
// 实际导出的是一个高阶组件
export default loginAuth(Home);
3. 意义
利用高阶组件可以针对某些React代码进行更加优雅的处理
01 - mixin
早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用:
Mixin 可能会相互依赖,相互耦合,不利于代码维护
不同的Mixin中的方法可能会相互冲突
Mixin非常多时,组件处理起来会比较麻烦,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
02 - hoc
HOC也有自己的一些缺陷:
HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套
这让调试变得非常困难
HOC可以劫持props,在不遵守约定的情况下也可能造成冲突
03 - hooks
Hooks的出现,是开创性的,它解决了很多React之前的存在的问题
比如this指向问题
比如hoc的嵌套复杂度问题
十一、Portals
某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中
默认都是挂载到id为root的DOM元素上的
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
第一个参数(child)是任何可渲染的 React 子元素
例如一个元素,字符串或 fragment
第二个参数(container)是一个 DOM 元素
![](https://i-blog.csdnimg.cn/blog_migrate/31b28057e1969fab4ca7065e4b112037.png)
1. 基本使用
在根组件新创建一个
<div id="root"></div>
<div id="coder"></div>
代码
import React, { PureComponent } from 'react';
// 1. 导入 createPortal,用于创建一个传送门
import { createPortal } from 'react-dom';
export class App extends PureComponent {
render() {
return (
<div>
App
{/* 2. 使其挂载到根组件之外的coder -> div中 */}
{createPortal(<h2>我是传送门</h2>, document.querySelector('#coder'))}
</div>
);
}
}
export default App;
效果
![](https://i-blog.csdnimg.cn/blog_migrate/b75cb5260576a0d0b267f15b42a07008.png)
2. 封装
根文件index.html
![](https://i-blog.csdnimg.cn/blog_migrate/df4f81c251e57ff85cfd7163817be199.png)
父组件
import React, { PureComponent } from 'react';
import Coder from './components/Coder';
export class App extends PureComponent {
render() {
return (
<div>
App
{/* 在子组件中间写内容,类似插槽的效果 */}
<Coder>
<h2>coder</h2>
<div>hello world</div>
</Coder>
</div>
);
}
}
export default App;
子组件
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';
export class Coder extends PureComponent {
render() {
// createPortal(要渲染的内容, 挂载的DOM节点)
// this.props.children => 是从父组件传递过来的内容
return createPortal(this.props.children, document.getElementById('coder'));
}
}
export default Coder;
效果
![](https://i-blog.csdnimg.cn/blog_migrate/28d4fada72745487b8d5c563e23a43ce.png)
十二、fragment
之前的开发中,总是在一个组件中返回内容时包裹一个div元素
如果希望可以不渲染这样一个div应该如何操作
使用Fragment
Fragment 允许你将子列表分组,而无需向 DOM 添加额外节点
类似vue的template、小程序的block
1. 基本使用
import React, { PureComponent, Fragment } from 'react';
export class App extends PureComponent {
render() {
return (
// 把Fragment当做一个占位符使用,不会渲染到页面上
<Fragment>
<h2>App标题</h2>
<p>冲啊</p>
</Fragment>
);
}
}
export default App;
2. 语法糖🍬
Fragment的短语法:
它看起来像空标签 <> </>
但是,如果需要在Fragment中添加key,那么就不能使用短语法
render() {
return (
// Fragment语法糖🍬
<>
<h2>App标题</h2>
<p>冲啊</p>
</>
);】
}
十三、StrictMode
StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
与 Fragment 一样,StrictMode 不会渲染任何可见的 UI
它为其后代元素触发额外的检查和警告
严格模式检查仅在开发模式下运行
它们不会影响生产构建
可以为应用程序的任何部分启用严格模式
不会对 Header 和 Footer 组件运行严格模式检查
1. 全局启动
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { StrictMode } from 'react';
const root = ReactDOM.createRoot(document.querySelector('#root'));
// 开启严格模式,检查不安全的生命周期,过时的ref等
root.render(
<StrictMode>
<App />
</StrictMode>
);
2. 部分启动
import React, { PureComponent, StrictMode } from 'react';
import Home from './components/Home';
import Coder from './components/Coder';
export class App extends PureComponent {
render() {
return (
<>
<h2>App</h2>
{/* 给Home组件开启严格模式 => Home以及它的所有后代元素都将进行检查 */}
<StrictMode>
<Home />
</StrictMode>
<Coder />
</>
);
}
}
export default App;
3. 严格模式检查的内容
识别不安全的生命周期
![](https://i-blog.csdnimg.cn/blog_migrate/c3f6fcaf186fe023e3477806fd48c4a6.png)
使用过时的ref API
![](https://i-blog.csdnimg.cn/blog_migrate/1f82745dd32a1254dacd87c1837ae793.png)
检查意外的副作用
constructor、render、以及很多的生命周期函数,会在第一次渲染的时候, 刻意执行两次
这个组件的constructor会被调用两次
这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
在生产环境中,是不会被调用两次的
使用废弃的findDOMNode方法
在之前的React API中,可以通过findDOMNode来获取DOM,已经不推荐使用了
检测过时的context API
早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的
目前这种方式已经不推荐使用