65.9K
CodeProject 正在变化。 阅读更多。
Home

React 中的 Pure Component

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2018年3月6日

CPOL

8分钟阅读

viewsIcon

17914

在本文中,我们将学习什么是纯组件、它如何工作以及纯组件的优点。

在 React 中,如果您使用 React 生命周期钩子和状态,那么您会很清楚,每当我们使用 setState 方法更改 React 组件的状态时,React 组件总是会重新渲染并在视图上显示新的更改。如果您不了解“setState”方法,那么我将告诉您,使用“setState”方法,我们可以更改任何 React 组件的状态。当任何组件的状态发生变化时,它将再次调用“render”方法并再次刷新视图。

我认为这是 React 最好且必需的方法。因为如果我们要使新的更改可见,那么将使用这个“setState”方法。如果我们在组件的属性(props)中进行任何更改,那么组件将不会渲染,因为 propsreadonly 类型,我们不能在组件中更改 props 的值。我知道 React 组件的重新渲染方法(当状态改变时)是 React 最好的特性之一,但有时这个特性会为 React 应用程序的性能带来问题。

让我们看一个例子以便更好地理解。我有一个组件,下面是这个组件的上下文。

import React from 'react';    
import PropTypes from 'prop-types';  
  
class Main extends React.Component {    
    constructor(props) {  
      super(props);          
      this.state = {  
         city: "Alwar",  
      }  
   } 
   
   componentDidMount(){
       setInterval(()=>{
           this.setState(()=>{
               return { city: "Alwar"}
           })
       },2000)
   }
   render() {  
       console.log('Main Component render');  
      return (  
          <div>    
         <h2>    
            {this.state.title}   
         </h2>    
         <p>User Name: {this.props.name}</p>  
         <p>User Age: {this.props.age}</p>  
         </div>  
      );    
   }  
     
}    
Main.propTypes ={  
       name:PropTypes.string.isRequired,  
       age:PropTypes.number.isRequired  
   }  
  
Main.defaultProps = {  
  name: 'Pankaj Kumar Choudhary',  
  age: 24  
};  
    
export default Main;

在上面的代码行中,我定义了一些初始 props 和一个状态。在“componentWillMount”生命周期钩子中,我使用了 setInterval 方法,它将每 1 秒重复一次迭代,并使用了组件的“setState”方法。我保存了上面的代码,在浏览器中查看下面的输出。

因为我们使用的是“setState”方法,“setState”方法会调用组件的“render”函数并重置组件的视图。现在问题来了,我们真的需要每次都重新渲染组件吗?如果你正在考虑任何场景并得到肯定的答案,那么你可能是对的。但是如果你检查上面的例子,你会发现我们每次都调用“setState”方法,但之后新旧状态之间没有任何变化,它会调用组件的 render 方法。这有意义吗?

实际的实现应该是当新旧 stateprops 之间存在任何差异时才应该调用 render 方法。但问题是 React 组件不处理这种情况。那么解决这个问题的方法是什么?有两种方法。第一,我们使用“shouldComponentUpdate”生命周期在我们的末端处理这种情况。第二种是使用“纯组件”。我们稍后会阅读“纯组件”,但首先我们学习如何在我们的末端处理这种情况。

现在我对之前的代码进行了一些更改,新的更新代码如下:

import React from 'react';    
import PropTypes from 'prop-types';  
  
class Main extends React.Component {    
    constructor(props) {  
      super(props);          
      this.state = {  
         city: "Alwar",  
      }  
   } 
   
   componentDidMount(){
       setInterval(()=>{
           this.setState(()=>{
               return { city: "Alwar"}
           })
       },1000)

       setInterval(()=>{
        this.setState(()=>{
            return { city: "Jaipur"}
        })
    },6000)
   }

   shouldComponentUpdate(nextProps,nextState){
       return nextState.city!=this.state.city?true:false;
   }
   render() {  
       console.log('Main Component render '+Date.now());  
      return (  
          <div>    
         <h2>    
            {this.state.title}   
         </h2>    
         <p>User Name: {this.props.name}</p>  
         <p>User Age: {this.props.age}</p>  
         </div>  
      );    
   }  
     
}    
Main.propTypes ={  
       name:PropTypes.string.isRequired,  
       age:PropTypes.number.isRequired  
   }  
  
Main.defaultProps = {  
  name: 'Pankaj Kumar Choudhary',  
  age: 24  
};  
    
export default Main;

输出

在上面的代码中,我还实现了“shouldComponentUpdate”生命周期钩子,此方法调用在“render”方法调用之前。在此方法中,我们检查组件是否会渲染。如果此组件的返回值为“True”,则组件将重新渲染,否则不会。在此方法中,我们可以实现我们的逻辑并告诉组件它是否会重新渲染。所以我检查了当前和 nextState 的“city”属性,如果两个 statecity 属性之间有任何差异,那么“shouldComponentUpdate”方法返回 true,否则返回 false

这样我们就可以阻止组件的“不必要”渲染。我们在我们的末端阻止了这种渲染,但 React 还有另一个选项,那就是“纯组件”。

什么是纯组件?

PureComponent 与组件相同,它为我们处理 shouldComponentUpdate 方法,就像我们在上一个示例中所做的那样。每当组件的 stateprops 发生变化时,它都会进行浅比较并检查组件是否会重新渲染。纯组件为我们做了我们手动在 shouldComponentUpdate 中所做的相同任务,但它在内部进行,并提高了我们应用程序的性能。一个简单的组件不比较之前和下一个状态,而是在每次调用“shouldComponentUpdate”方法时渲染组件。让我们使用纯组件更新我们之前的示例。

import React from 'react';    
import PropTypes from 'prop-types';  
  
class Main extends React.PureComponent {    
    constructor(props) {  
      super(props);          
      this.state = {  
         city: "Alwar",  
      }  
   } 
   
   componentDidMount(){
       setInterval(()=>{
           this.setState(()=>{
               return { city: "Alwar"}
           })
       },1000)

       setInterval(()=>{
        this.setState(()=>{
            return { city: "Jaipur"}
        })
    },6000)
   }
   
   render() {  
       console.log('Main Component render '+Date.now());  
      return (  
          <div>    
         <h2>    
            {this.state.title}   
         </h2>    
         <p>User Name: {this.props.name}</p>  
         <p>User Age: {this.props.age}</p>  
         </div>  
      );    
   }      
}    
Main.propTypes ={  
       name:PropTypes.string.isRequired,  
       age:PropTypes.number.isRequired  
   }  
  
Main.defaultProps = {  
  name: 'Pankaj Kumar Choudhary',  
  age: 24  
};  
    
export default Main;

输出

什么是浅比较?

如果您注意到对于纯组件,我使用了“浅比较”这个术语。实际上,纯组件进行浅比较而不是深比较。浅比较意味着在检查之前的状态和 props 时,如果值是原始类型,那么它只检查或比较它们的值。但是 props 或 state 是复杂类型,如对象和数组,那么它检查它们的引用类型。在深度检查中,比较的是值而不是引用值。让我们举个例子。

var studentObj=function(name,age)
		{this.name=name;
		this.age=age};
var obj1=new studentObj('pankaj',24);
var obj2=obj1;

//compare both object
obj1==obj2  //output: true

//create another object

var obj3=new studentObj('pankaj',24)
//compare both object
obj2==obj3  //output: false  because in complex type object reference are checked that 
            //are not same in this case

//check the value of both object
 obj3.name==obj2.name  //output: true because in primitive type value are checked that 
                       //are same in this case

切勿在父组件中修改 Props 和 State

如果您有任何纯子组件,切勿在父组件的 propsstate 中修改对象和数组。原因是,如果您更新父组件中的对象或数组,并且将此对象或数组传递给纯子组件,那么子组件将不会重新渲染,因为如我之前所解释的,纯组件检查 propsstate 的引用。由于您更新(修改)了对象或数组,并且引用没有改变,因此子组件无法检测到更改。让我们举一个例子。

我创建了另一个组件并将其命名为“child.jsx”。这个子组件本质上是纯组件。

child.jsx

import React from 'react';    
import PropTypes from 'prop-types';  
  
class Child extends React.PureComponent {    
    constructor(props) {  
      super(props);
   } 
    
   render() {  
       console.log('Child Component render '+Date.now());  
      return (  
          <div>    
          <p>Props Values is {this.props.childProps}</p>
         </div>  
      );    
   }  
     
}    
Child.propTypes ={  
    childProps:PropTypes.array.isRequired,  
   }    
    
export default Child;

现在我们将这个组件用于“Main.jsx”组件,它将是父组件。

main.jsx

import React from 'react';    
import PropTypes from 'prop-types';  
import Child from '../child/child';  
class Main extends React.Component {    
    constructor(props) {  
      super(props);          
      this.state = { 
        name:'Pankaj',
         city: "Alwar",
         data:[Date.now()]  
      }  
   } 

   addData(){
       var arrayData=this.state.data;
       arrayData.push(Date.now());
       this.setState({data:arrayData});
   }
   
   render() {  
       console.log('Main Component render');  
      return (  
          <div>        
         <p>User Name: {this.state.name}</p>  
         <p>User City: {this.state.city}</p>
         <p>User Data: {this.state.data.join("  ,")}</p> 
         <Child childProps={this.state.data}></Child>
         <input type="button" value="UpdateData" onClick={this.addData.bind(this)} /> 
         </div>  
      );    
   }       
}    
 export default Main;

在“main.jsx”组件中,我定义了组件的状态,并且“main.jsx”组件的状态包含三个属性(namecitydata)。“data”属性的类型是 Array 类型,我们将其作为“Child”组件的属性传递。在此组件中,我们还创建了一个按钮,并在该按钮的点击事件上更新组件的状态(更新 data 属性)。

在“child”组件中,我们显示“data”属性的值。现在,当我们点击“UpdateData”按钮时会发生什么?它会更新“Main”组件的状态,“Main”组件将重新渲染,但 child 组件永远不会重新渲染。因为“data”属性的引用没有改变。

如果您将子组件定义为简单组件而不是“PureComponent”,那么子组件将开始重新渲染。

所以,永远不要修改数组类型和对象类型属性,而是总是赋予新值。

现在对“Main.jsx”组件进行一些更改,如上图所示,保存更改后,您会发现每次点击按钮,子组件也会渲染,因为现在“data”属性的每个类型引用也正在改变。

不要在 Render 方法中绑定函数

有些开发人员会犯在 render 方法中绑定函数的错误,如下所示:

上述模式的问题在于,当我们在 render 方法中绑定函数时,它总是在每次渲染时为子组件提供函数的新实例或引用。例如,如果您有一个父组件(Main.jsx),并且您将一个函数作为 prop 传递给子组件,并且您在父组件的 render 方法中绑定了该函数。那么每当父组件(main.jsx)的 render 方法调用时,它都会向子组件传递一个新实例。由于子组件获得了 prop 的新引用,即使您的子组件是纯组件,它也会重新渲染。让我们看一个例子来理解这个问题。

Main.jsx

import React from 'react';    
import PropTypes from 'prop-types';  
import Child from '../child/child';  
class Main extends React.Component {    
    constructor(props) {  
      super(props);          
      this.state = { 
        name:'Pankaj',
         city: "Alwar",
         data:[Date.now()]  
      }
   } 

   addData(){
       var arrayData=this.state.data;
       arrayData.push(Date.now());
       this.setState({data:new Array(arrayData)});
   }
    alertMethod(data){
       console.log(data);
   }
   render() {  
       console.log('Main Component render');  
      return (  
          <div>        
         <p>User Name: {this.state.name}</p>  
         <p>User City: {this.state.city}</p>
         <p>User Data: {this.state.data.join("  ,")}</p> 
         <Child childProps={(e)=>this.alertMethod(e)} name="pankaj"></Child>
         <input type="button" value="UpdateData" onClick={this.addData.bind(this)} /> 
         </div>  
      );    
   }       
}    
 export default Main;

child.jsx

import React from 'react';    
import PropTypes from 'prop-types';  
  
class Child extends React.PureComponent {    
    constructor(props) {  
      super(props);
      this.transferData=this.transferData.bind(this);
   } 
    
   transferData(e){
        this.props.childProps(e.target.value);
   }
   render() {  
       console.log('Child Component render '+Date.now());  
      return (  
          <div>    
          <input type="text" onChange ={this.transferData}/><br/>
          <input type="button" value="Child Button" onClick={this.props.childProps} /> 
         </div>  
      );    
   }       
}    
Child.propTypes ={  
    childProps:PropTypes.func.isRequired,  
   }    
    
export default Child;

当您点击“UpdateData”方法时,它将更新“main”组件的状态。由于状态改变,它将重新渲染视图,并向子组件提供“alertMethod”的新引用,结果是子组件也重新渲染。

为了克服这个问题,我们必须避免在 render 方法中绑定函数。相反,我们必须在组件的构造函数中绑定方法。让我们修改我们的主组件并检查差异。

Main.jsx

import React from 'react';    
import PropTypes from 'prop-types';  
import Child from '../child/child';  
class Main extends React.Component {    
    constructor(props) {  
      super(props);          
      this.state = { 
        name:'Pankaj',
         city: "Alwar",
         data:[Date.now()]  
      }
      this.alertMethod=this.alertMethod.bind(this); 
   } 

   addData(){
       var arrayData=this.state.data;
       arrayData.push(Date.now());
       this.setState({data:new Array(arrayData)});
   }
    alertMethod(data){
       console.log(data);
   }
   render() {  
       console.log('Main Component render');  
      return (  
          <div>        
         <p>User Name: {this.state.name}</p>  
         <p>User City: {this.state.city}</p>
         <p>User Data: {this.state.data.join("  ,")}</p> 
         <Child childProps={this.alertMethod} name="pankaj"></Child>
         <input type="button" value="UpdateData" onClick={this.addData.bind(this)} /> 
         </div>  
      );    
   }       
}    
 export default Main;

如您所见,我们没有在 render 方法中绑定函数,而是在组件部分绑定了函数。所以现在,如果我们点击“UpdateData”方法,它将不会渲染子组件。因为现在它不会每次都提供“alertMethod”的新实例。

关于纯组件的注意事项

  • 当您想阻止组件不必要的重新渲染时,请使用纯组件而不是简单组件。
  • 切勿在 render 方法中绑定方法或派生数据,因为它会增加子组件的重新渲染率。
  • 纯组件可以提高应用程序的性能,但只有在真正需要时才使用纯组件,因为额外和不必要的使用也可能降低应用程序的性能。
  • 纯组件不仅会阻止自身重新渲染,还会阻止子组件重新渲染。纯组件的最佳用途是当您没有纯组件的任何子组件并且不依赖于应用程序的全局状态时。
  • 如果您修改了从父组件作为 props 传递给纯组件的对象数组,则纯组件将不会渲染。

结论

在本文中,我们学习了什么是纯组件,它是如何工作的,以及纯组件的好处。因此,最终的结论是,使用纯组件可以提高您的应用程序性能,因此请尝试更多地使用它。如果您对本文有任何疑问或问题,请在下面的评论部分提出。感谢您阅读本文。

© . All rights reserved.