React 模式之纯组件使用示例详解

目录
  • 什么是纯组件
  • 纯组件解决了什么问题
  • 怎么使用纯组件
    • CC: shouldComponentUpdate() 和 React.PureComponent
    • FC: React.memo()
  • 你可能并不需要纯组件

什么是纯组件

纯组件(Pure Component)这概念衍生自纯函数。纯函数指的是返回结果只依赖于传入的参数,且对函数作用域外没有副作用的函数。这种函数在相同参数下,返回结果是不变的。纯函数的返回值能被安全地缓存起来,在下次调用时,跳过函数执行,直接读取缓存。因为函数没有外部副作用,不执行函数对整个程序没有影响。

与纯函数类似,如果一个组件在 props 和 state 相同的情况下,每次 render 的结果都是相同的,那这个组件就是纯组件。也就是说,纯组件的 render 结果只依赖于 props 和 state,如果两次渲染中,props 和 state 是相同的,那它们的 render 结果也是一样的。

纯组件解决了什么问题

在理解纯组件有什么用处之前,先来看一下 React 如何更新组件。

React 在更新组件时,会从触发组件开始,递归地调用整颗子树的 render 函数,构建新的虚拟树。即使子组件的 props 和 state 没有变化,render 函数还是会被执行。

看这个例子:

const defaultMessage = "Hello!";
function Messager() {
  console.log("render in parent");
  const [messageInput, setMessageInput] = useState(defaultMessage);
  const [messageData, setMessageData] = useState({
    id: 0,
    message: "",
  });
  return (
    <div className="logger">
      <input
        value={messageInput}
        onChange={(e) => setMessageInput(e.target.value)}
      ></input>
      <button
        onClick={() =>
          setMessageData((preData) => ({
            id: preData.id + 1,
            message: messageInput,
          }))
        }
      >
        发送消息
      </button>
      <MessageText message={messageData.message} />
    </div>
  );
}
function MessageText(props) {
  console.log("render in child");
  return <div>最新消息:{props.message}</div>;
}

连续点击几次按钮:

父组件 Messager 由于状态更新,需要重新执行 render 函数来更新组件,这个是符合预期的。子组件 MessageText 中,第一次更新,由于 message 属性改变,也需要更新,这个也容易理解。

问题在后面几次更新:

  • 子组件的 props 没有变化,为什么执行了 render 函数?
  • render 函数执行了,是不是意味着 DOM 也更新了,只是我们看不出变化?
  • 组件在 props 没有变化时,绘制的视图都是不变的,能不能跳过 render 函数的执行?

让我一个一个来回答。

Q:为什么需要执行 render 函数?

A:你的组件可能使用了任何变量,包括全局变量、环境变量等,React 没有能力做到监听这些变量,这些变量的变化是 React 无法感知的。为了保证渲染的视图与数据是一致的,React 只能牺牲性能,在每次更新的时候,都去执行 render 函数,获取最新的 render 输出。

Q: 执行了 render 函数就一定会更新 DOM 吗?

A:不一定。render 函数的输出结果是 React 虚拟 DOM,执行了 render 函数会更新虚拟 DOM,但 React 足够聪明,能够比对出浏览器 DOM 需要更新的地方,让浏览器只进行必要的重绘。这也是 React 能够保证性能的重要手段。

Q:能避免不必要的 render 执行吗?

A:可以。如果是 class 组件(CC),你可以重写 shouldComponentUpdate() 方法或继承 React.PureComponent。如果是函数组件(FC),你可以使用 React.memo() 。这样能够避免不必要的 render 执行,在一定程度上提升页面的性能,尤其是当 render 函数内有复杂计算时。这也正是纯组件想要解决的问题。

怎么使用纯组件

CC: shouldComponentUpdate() 和 React.PureComponent

这一小节的内容基于 class 组件,FC 不适用。

React 组件更新前,会调用 shouldComponentUpdate(nextProps,nextState),当返回 true 时,组件就会 re-render。所以,你可以重写这个方法,当不希望组件更新时,返回 false

重写上面的 MessageText 组件:

class MessageText extends React.Component {
  shouldComponentUpdate(nextProps) {
    if (nextProps.message === this.props.message) {
      return false;
    }
    return true;
  }
  render() {
    console.log("render in child with message=" + this.props.message);
    return <div>最新消息:{this.props.message}</div>;
  }
}

这样,render 函数只会在 props.message 变化的时候才会被调用。当然,你可以自己决定哪些条件下跳过 render。

shouldComponentUpdate 返回 false 时并不能保证跳过 render。React 后续可能会增加自己的判断,只把这个返回结果作为一种提示。所以这个方法应该只能被用于性能优化,不能作为逻辑依赖。

大部分时候,我们期望在 props 和 state 不变的时候,跳过 render,因为这经常导致不必要的更新。上面的例子只有一个属性,有点过于简单了,组件可能会多个 props 和 state,需要在 () 中穷举比较。

因为这种模式太过常见,React 提供了 React.PureComponent 类,你可以继承这个类,来实现纯组件的效果,即当 props 和 state 不变(浅比较)时,跳过 render。

class MessageText extends React.PureComponent {
  render() {
    console.log("render in child with message=" + this.props.message);
    return <div>最新消息:{this.props.message}</div>;
  }
}

FC: React.memo()

先回答一个普遍疑惑的问题。

Q:FC 是纯组件吗?或者无状态的 FC 是纯组件吗? A:并不是。从最上面的例子就可以看出来。无状态 FC 与纯组件是独立的概念,状态并不是影响纯组件的因素,关键在于组件函数除了 state 和 props 有没有外部依赖,对外部有没有影响。

Q:既然如此,怎么把 FC 改造成纯组件? A:很简单,用 class 重写组件并继承 React.PureComponent 就可以了。

说笑了,这年头,谁写 React 还用 class 啊。

然而,很遗憾,hooks 无法覆盖 shouldComponentUpdate() 的使用场景,FC 没有等效于 React.PureComponent 的写法。

不过,倒是可以使用 React.memo() 实现一个半吊子的纯组件。

const memorizedFC=React.memo(FC,arePropsEqual(preProps,nextProps)=>{
    // 返回true,跳过render
    // 返回false,执行render
})

React.memo() 把上次调用的结果保存在内存中,下次调用时,如果 arePropsEqual() 返回 true,那就直接使用上次的结果,不需要执行 FC。arePropsEqual 参数可选,默认使用浅比较。

利用 React.memo(), 把 MessageText 改造成纯组件 PureMessageText

function MessageText(props) {
  console.log("render in child with message=" + props.message);
  return <div>最新消息:{props.message}</div>;
}
const PureMessageText = React.memo(MessageText);

注意 React.memo() 并不等效于 React.PureComponent,前者只能比较 props,对于状态导致的更新,FC 依然会执行。这也是为什么说是“半吊子纯组件”。

如果 FC 无状态,那 React.memo() 就可以等效于 React.PureComponent 了。既然如此,对有状态 FC,可以利用状态上移把 state 转为 props,再应用 React.memo(), 实现纯组件的效果。

所以,绝大多数情况下,React.memo() 已经足够了。

你可能并不需要纯组件

了解纯组件的概念,以及它对 React 应用性能的影响,对一个开发者有很大帮助,但这并不意味着你需要经常使用它。

React 提供了良好的性能保证,大部分情况下,你的应用不会有性能上的问题,使用纯组件反而增加理解成本。

即使出现了性能问题,一些通用的性能优化手段可能更有效果。只有当性能瓶颈出现在特定组件的 render,并且这个组件可以被改造成纯组件时,这个措施才会有效果。

以上就是React 模式之纯组件使用示例详解的详细内容,更多关于React 纯组件模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • React组件设计模式之组合组件应用实例分析

    本文实例讲述了React组件设计模式之组合组件应用.分享给大家供大家参考,具体如下: 这种模式本质上解决的是组件之间传值的问题.但是它对于传值以及一些内部操控的逻辑封装得更严密. 场景:希望减少上下级组件之间props的传递,简单来说就是不用传做显式地传值,来达到组件之间相互通信的目的 举例来说,某些界面中应该有Tabs这样的组件,由Tab和TabItem组成,点击每个TabItem,该TabItem会高亮, 那么Tab和TabItem自然要进行沟通.很自然的写法是像下面这样 <TabItem

  • React路由拦截模式及withRouter示例详解

    目录 一.路由拦截 二.路由模式 三.withRouter 一.路由拦截 在前面两篇 路由博客基础上,我们将ReactRouter.js的我的profile路由设置成路由拦截的: <Route path="/profile" render={() => isAuth() ? <Profile/> : <Redirect to="/login"></Redirect> }></Route> 新建Logi

  • React router动态加载组件之适配器模式的应用详解

    前言 本文讲述怎么实现动态加载组件,并借此阐述适配器模式. 一.普通路由例子 import Center from 'page/center'; import Data from 'page/data'; function App(){ return ( <Router> <Switch> <Route exact path="/" render={() => (<Redirect to="/center" />)}

  • React在组件中如何监听redux中state状态的改变

    目录 在组件中监听redux中state状态的改变 解决方式 React和redux的状态处理 在组件中监听redux中state状态的改变 解决方式 1.在组件中引入store 2.在constructor构造器方法中,重写store.subscribe方法(该方法即是监听state状态改变的放过) 组件完整代码如下: import React, { Component } from 'react' import CSSModules from 'react-css-modules'  imp

  • 解决react组件渲染两次的问题

    目录 react组件渲染两次 react总结之避免不必要的重复渲染 类组件PureComponent 使用插件seamless-immutable 使用插件pure-render-decorator react组件渲染两次 可能会有人问,问什么我的组件明明是就让渲染一次,但是实际上却渲染两次呢?其实我也遇到了这个问题,那么下面我提出一种解决这个问题的一种方法. 如果你使用了react-router低于4.x版本中的hashHistory,那么问题就来了,出现这种情况的原因是因为router中进行

  • react render props模式实现组件复用示例

    目录 一 render props的使用步骤 二 组件的复用 三 使用children名代替属性 一 render props的使用步骤 1 创建要复用的组件,在组件中提供要复用的状态逻辑代码2 将要复用的state作为方法的参数,暴露到组件外部 import React from "react"; import ReactDOM from "react-dom"; class App extends React.Component { render() { ret

  • React 模式之纯组件使用示例详解

    目录 什么是纯组件 纯组件解决了什么问题 怎么使用纯组件 CC: shouldComponentUpdate() 和 React.PureComponent FC: React.memo() 你可能并不需要纯组件 什么是纯组件 纯组件(Pure Component)这概念衍生自纯函数.纯函数指的是返回结果只依赖于传入的参数,且对函数作用域外没有副作用的函数.这种函数在相同参数下,返回结果是不变的.纯函数的返回值能被安全地缓存起来,在下次调用时,跳过函数执行,直接读取缓存.因为函数没有外部副作用,

  • 使用 React Hooks 重构类组件的示例详解

    目录 1. 管理和更新组件状态 2. 状态更新后的操作 3. 获取数据 4. 卸载组件时清理副作用 5.  防止组件重新渲染 6. Context API 7. 跨重新渲染保留值 8. 如何向父组件传递状态和方法? 9. 小结 最初,在 React 中可以使用 createClass 来创建组件,后来被类组件所取代.在 React 16.8 版本中,新增的 Hooks 功能彻底改变了我们编写 React 程序的方式,使用 Hooks 可以编写更简洁.更清晰的代码,并为创建可重用的有状态逻辑提供了

  • react中使用antd及immutable示例详解

    目录 一.react中使用antd组件库 二.Immutable 2.1 深拷贝和浅拷贝的关系 2.2 immutable优化性能方式 2.3 immutable的Map使用 2.4 immutable的List使用 2.5 实际场景formJS 三.redux中使用immutable 一.react中使用antd组件库 运行命令create-react-app antd-react创建新项目: 运行命令npm i antd安装: 使用: import React from 'react' im

  • React.memo函数中的参数示例详解

    目录 React.memo?这是个啥? React.memo的第一个参数 父组件 子组件 React.memo优化 React.memo的第二个参数 父组件 子组件 React.memo优化 父组件 子组件 小结 React.memo?这是个啥? 按照官方文档的解释: 如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现.这意味着在这种情况下,React 将跳过渲染组件的操作并直

  • React之错误边界 Error Boundaries示例详解

    目录 引言 注意 实现 错误边界应该放置在哪? 未捕获错误(Uncaught Errors)该如何处理? 注意:自 React 15 的命名更改 引言 过去,组件内的代码异常会导致 React 的内部状态被破坏,产生可能无法追踪的错误.但 React 并没有提供一种优雅处理这些错误的方式,也无法从错误中恢复. 默认情况下,若一个组件在渲染期间(render)发生错误,会导致整个组件树全部被卸载,这当然不是我们期望的结果. 部分组件的错误不应该导致整个应用崩溃.为了解决这个问题,React 16

  • flutter text组件使用示例详解

    目录 正文 Text组件 Text组件构造器上的主要属性 正文 flutter组件的实现参考了react的设计理念,界面上所有的内容都是由组件构成,同时也有状态组件和无状态组件之分,这里简单介绍最基本的组件. 在组件代码的书写方式上,web端开发的样式主要有由css进行控制,而客户端开发根据使用的技术栈不同,写法也稍微有些不同:ReactNative的写法和web比较类似,但是ReactNative是使用StyleSheet.create()方法创建样式对象,以内联的方式进行书写. import

  • React特征学习之Form格式示例详解

    目录 Form 样式 React hook Form 样式 首先来看一个简单Form, 式样如下 import * as React from 'react'; const LoginForm = () => { return ( <form> <div> // Notice: 这里要用htmlFor,相当于id <label htmlFor="email">Email</label> <input id="emai

  • Seata AT模式TM处理流程图文示例详解

    目录 TM的作用 源码分解 小结 TM的作用 我们根据源码解读画出了下图,该图示展现了TM在整个Seata AT模式的分布式事务中所起的作用: 从上图中可以看出,TM主要有两个作用: 开启分布式事务,以拿到XID作为分布式事务开启的标识:一定是从TC拿到XID,不是从调用方传递过来的XID: 根据所有RM的处理结果来决定是提交分布式事务还是回滚分布式事务: 转换成伪代码如下: try{ // 开启分布式事务 String xid = TM.beginGlobalTransaction(); //

  • Flutter之 ListView组件使用示例详解

    目录 ListView的默认构造函数定义 默认构造函数 ListView.builder ListView.separated 固定高度列表 ListView 原理 实例:无限加载列表 添加固定列表头 总结 ListView的默认构造函数定义 ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建).我们看看ListView的默认构造函数定义: ListView({ ... //可滚动widget公共参数 Axis scrollD

  • vue选项卡Tabs组件实现示例详解

    目录 概述 效果图 实现过程 组件分析 所需的前置知识 项目组件文件夹 Tabs.vue TabPane.vue render.js index.js 使用 总结 概述 前端项目中,多数页面涉及到选项卡切换,包括路由切换,指令v-if等,本质上其实和选项卡切换思想差不多,如果是个简单的选项卡,还是很简单的,我们也不需要什么组件库的组件,自己也能几行代码写出来,但是涉及到动画,尺寸计算,拖拽的功能的时候,多数情况下,自己写还是要花点时间的,组件库就提供了现成的,拿来改改样式就行,为了对这个组件更加

随机推荐