无用渲染
组件会有自己本地的状态(state), 当它们的值由于用户的操作而发生改变时,组件就会重新渲染。在项目中,一个组件可能会被频繁地进行渲染,这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大降低我们应用的性能。
类组件的无用渲染解决方案
import React from "react";
import Des from "./des";
class App extends React.Component {
render() {
return (
<div>
<Des></Des>
</div>
)
}
}
export default App;
import React, { Component } from 'react'
export default class Des extends Component {
state={
num:1
}
change=()=>{
this.setState({num:20});
}
render() {
console.log("render运行,页面重新渲染");
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.change}>change</button>
</div>
)
}
}
以上代码中第一次修改了数据页面刷新,render函数再次运行。之后数据没有发生变化,页面没有重新渲染,但是再次调用this.setState使得render函数再次运行,这就是无用的渲染就使得性能降低。
类组件可以使用shouldComponentUpdate和PureComponent来解决无用的渲染。
shouldComponentUpdate生命周期函数
当React想要渲染一个组件的时候,它将会调用这个组件的shouldComponentUpdate钩子函数, 这个函数会告诉它是不是真的要渲染这个组件。返回false就不会重新渲染也就不会执行render函数,返回true就会重新渲染,默认返回true。可以自己实现逻辑决定是否让页面重新渲染。
shouldComponentUpdate(nextProps, nextState) {
}
其中各个参数的含义是:
nextProps: 组件将会接收的下一个参数props
nextState: 组件的下一个状态state
shouldComponentUpdate函数一直返回true,这就告诉React,无论何种情况都要重新渲染该组件。
这个方法的返回值是false,React永远都不会重新渲染我们的组件。
import React, { Component } from 'react'
export default class Des extends Component {
state={
num:1
}
change=()=>{
this.setState({num:20});
}
shouldComponentUpdate(nextprops,nextstate){
if(nextstate.num==this.state.num){
return false;
}
return true;
}
render() {
console.log("render运行,页面重新渲染");
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.change}>change</button>
</div>
)
}
}
PureComponent
React在进行组件更新时,如果发现这个组件是一个PureComponent,它会将组件现在的所有state和props和其下一个state和props进行比较,如果它们的值没有变化,就不会进行更新。要想让你的组件成为PureComponent,只需要继承React.PureComponent
import React, { PureComponent } from 'react'
export default class Des extends PureComponent {
state={
num:1
}
change=()=>{
this.setState({num:20});
}
render() {
console.log("render运行,页面重新渲染");
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.change}>change</button>
</div>
)
}
}
在扩展React.PureComponent时不应该使用shouldComponentUpdate,两者使用其一即可。
区别
- PureComponent会内置一个shouldComponentUpdate,通过 prop 和 state 的浅比较来实现,当 prop 或 state 是基本数据其值或者引用数据的引用地址发生改变时,组件就会发生更新。
- shouldComponentUpdate可以处理引用类型的深层次比较,但也会影响效率。
函数组件无用渲染的解决方案
函数组件不存在继承关系,所以函数组件提供了React.memo,它其实就是函数组件的React.PureComponent,是用来控制函数组件是否要重新渲染的。
import React from "react";
import First from "./First";
class App extends React.Component {
state={
info:'父组件的数据'
}
change=()=>{
this.state.info='父组件数据被修改,会导致render函数运行然后重新传值给子组件';
this.setState(this.state);
}
render() {
return (
<div>
<First data={this.state.info}></First>
<button onClick={this.change}>change</button>
</div>
)
}
}
export default App;
export default function First(props) {
console.log('First组件被重新渲染');
return (
<div>
<p>{props.data}</p>
</div>
)
}
子组件接受的属性并没有改变,页面并没有刷新,但是父组件执行了this.setState执行后子组件的render函数重新运行了。
React.memo
React.memo会返回一个纯化的组件MemoFuncComponent,这个组件将会在JSX标记中渲染出来。当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染。
只需要调用memo函数,将函数组件作为参数,再导出返回的组件即可。
import React from "react";
import First from "./First";
class App extends React.Component {
state={
info:'父组件的数据'
}
change=()=>{
this.state.info='父组件数据被修改,会导致render函数运行然后重新传值给子组件';
this.setState(this.state);
}
render() {
console.log(1111);
return (
<div>
<First data={this.state.info}></First>
<button onClick={this.change}>change</button>
</div>
)
}
}
export default App;
import {memo} from 'react'
function First(props) {
console.log('First组件被重新渲染');
return (
<div>
<p>{props.data}</p>
</div>
)
}
export default memo(First);
父组件的render函数运行了,但是函数组件接受的属性并没有再改变,所以函数组件的render函数没有再运行。
React Context上下文对象
Context属于React的高级API,官方并不建议在稳定版的App中使用Context。很多优秀的React组件都通过Context来完成自己的功能,比如react-redux就是通过Context提供一个全局态的store和,路由组件react-router通过Context管理路由状态等。
当需要在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。
就类似于Vue中的provider和inject,但是在React中需要自己实现逻辑,将提供者放在一份单独的js文件,使用时导入这份js文件。要Context发挥作用,需要用到两种组件一个是Context生产者(Provider)通常是一个父节点,另外是一个Context的消费者通常是一个或者多个子节点,所以Context的使用基于生产者消费者模式。
使用Context
创建一个上下文的容器(组件)
import React from "react";
// 创建上下文对象,提供者
const userContext=React.createContext();
export default userContext;
通过静态方法React.createContext()创建一个Context对象,这个Context对象包含两个组件,<Provider />和<Consumer />
import React from "react";
// 引入上下文对象文件
import userCtx from './userCtx.js'
import First from "./First";
class App extends React.Component {
render() {
return (
<div>
<p>爷爷组件</p>
<userCtx.Provider value={{ username: '6ara 4ever', info: 'yyds' }}>
<First></First>
</userCtx.Provider>
</div>
)
}
}
export default App;
import Des from "./des"
import React, { Component } from 'react'
export default class First extends Component {
render() {
return (
<div>
<p>子组件</p>
<Des></Des>
</div>
)
}
}
Provider(生产者),用于生产共享数据的地方。Provider 接收一个 value 属性,并将其传递给消费组件。
import React, { Component } from 'react'
import userCtx from './userCtx.js'
export default class Des extends Component {
render() {
return (
<div>
<userCtx.Consumer>
{
(ctxdata) => {
return (
<div>
<p>孙组件</p>
<userCtx.Consumer>
{
(ctxdata) => {
console.log(ctxdata);
return (
<div>
<h1>{ctxdata.username}</h1>
<p>{ctxdata.info}</p>
</div>
)
}
}
</userCtx.Consumer>
</div>
)
}
}
</userCtx.Consumer>
</div>
)
}
}
Consumer消费者,该组件中消费者必须使用函数接收当前的 context 值,然后返回一个 React 节点。传递给函数的 value 值就是Provider 提供的 value 值。
可以直接获取Context的地方,除了实例的context属性(this.context),React组件还有很多个地方可以直接访问父组件提供的Context
类组件:
- constructor(props, context)
- componentWillReceiveProps(nextProps, nextContext)
- shouldComponentUpdate(nextProps, nextState, nextContext)
- componetWillUpdate(nextProps, nextState, nextContext)
- 所有能访问this的地方都可以使用this.context来获取上下文对象,就可以得到提供者的数据
ps:如果想要用this.context来获取上下文对象必须在接受值的后代组件名.contextType = 引入的上下文对象(即在后代消费者组件导出时给组件添加一个contextType属性,属性值就是引入的上下文对象),这样才能在消费者组件中用this.context来获取上下文对象,否则是个空对象。
import React, { Component } from 'react'
import userCtx from './userCtx.js'
export default class Des extends Component {
printctx=()=>{
console.log(this.context,777777);
}
render() {
return (
<div>
<p>孙组件</p>
<userCtx.Consumer>
{
(ctxdata) => {
console.log(ctxdata,20090729);
return (
<div>
<h1>{ctxdata.username}</h1>
<p>{ctxdata.info}</p>
<button onClick={this.printctx}>print context</button>
</div>
)
}
}
</userCtx.Consumer>
</div>
)
}
}
import Des from "./des"
import React, { Component } from 'react'
import userContext from "./userCtx";
class First extends Component {
print=()=>{
console.log(this.context,666666);
}
render() {
return (
<div>
<p>子组件</p>
<Des></Des>
<button onClick={this.print}>print ctx 子组价</button>
</div>
)
}
}
First.contextType=userContext;
export default First