React高阶组件使用教程详解

目录
  • 高阶组件(HOC)
    • 概述
    • 使用HOC解决横切关注点问题
    • 不用改变原始组件使用组合
    • 约定-将不相关的 props 传递给被包裹的组件
    • 约定-最大化可组合性
    • 约定-包装显示名称以便轻松调试
    • 使用高阶组件的注意事项

高阶组件(HOC)

概述

是React复用组件逻辑的一种高级技巧,是一种基于React组合特性而形成的设计模式

高阶组件是参数为组件,返回值为新组件的函数

简单理解:

  • 高阶组件本身是 函数,传参数是组件,返回值也是组件;
  • 高阶组件不用关心数据是如何渲染的,只用关心逻辑即可
  • 被包装的组件本身不用关心数据是怎么来的,只用负责渲染即可
  • 最后渲染的是高阶组件返回的组件

高阶组件的调用过程类似于这样:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

应用场景:redux 中的 connect

具体怎么编写呢?往下看…

使用HOC解决横切关注点问题

横切关注点问题:指的是一些具有横越多个模块的行为,使用传统的软件开发方法不能够达到有效的模块化的一类特殊关注点。

组件是React 中代码复用的基本单元,但某些模式并不适合传统组件

假设有一个 CommentList 组件,订阅外部数据源,用于渲染评论列表:

class CommentList extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       // 假设 "DataSource" 是个全局范围内的数据源变量,来自外部,自身带有很多方法
       comments: DataSource.getComments()  //假设getComments()这个方法可以获取所有的评论
     };
   }
   componentDidMount() {
     // 订阅更改;监听  DataSource ,发生变化时更新数据
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     // 清除订阅
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     // 当数据源更新时,更新组件状态
     this.setState({
       comments: DataSource.getComments()  //假设getComments()这个方法可以获取所有的评论
     });
   }
   render() {
     return (
       <div>
         {this.state.comments.map((comment) => (
           <Comment comment={comment} key={comment.id} />
         ))}
       </div>
     );
   }
 }
 // 假设 DataSource:来自外部;它自身有很多方法,如:getComments(),addChangeListener,removeChangeListener 等
//  假设 <Comment /> 是子组件,父组件 CommentList 需要将 comment 、key 传递给它

假设有个 订阅单个博客帖子的组件BlogPost,与上面的模式类似:

class BlogPost extends React.Component {
 constructor(props) {
   super(props);
   this.handleChange = this.handleChange.bind(this);
   this.state = {
     blogPost: DataSource.getBlogPost(props.id)
   };
 }
 componentDidMount() {
   DataSource.addChangeListener(this.handleChange);
 }
 componentWillUnmount() {
   DataSource.removeChangeListener(this.handleChange);
 }
 handleChange() {
   this.setState({
     blogPost: DataSource.getBlogPost(this.props.id)
   });
 }
 render() {
   return <TextBlock text={this.state.blogPost} />;
 }
}

以上两个组件的不同点

  • 调用方法不用

以上两个组件的相同点

  • 在挂载时,向 DataSource 添加一个更改侦 听器在侦 听器
  • 内部,当数据源发生变化时,调用 setState
  • 在卸载时,删除侦 听器

上面两个组件相同点的地方被不断的重复调用,在大型项目中,所以我们需要将这些共同使用的地方给抽象出来,然后让许多组件之间共享它,这正是高阶组件擅长的地方。

编写一个创建组件函数,这个函数接收两个参数,一个是要被包装的子组件,另一个则是该子组件订阅数据的函数。

 const CommentListWithSubscription = withSubscription(
    CommentList,
    (DataSource) => DataSource.getComments()
  );
  const BlogPostWithSubscription = withSubscription(
    BlogPost,
    (DataSource, props) => DataSource.getBlogPost(props.id)
  );
//以上写法相当于高级组件的调用,withSubscription为自定义的高阶组件;CommentList:被包装的子组件;CommentListWithSubscription:返回的包装后的组件

当渲染 CommentListWithSubscription 和 BlogPostWithSubscription 时, CommentList 和 BlogPost 将传递一个 data prop,其中包含从 DataSource 检索到的最新数据

 // 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
 // ...并返回另一个组件...
 return class extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       data: selectData(DataSource, props)
     };
   }
   componentDidMount() {
     // ...负责订阅相关的操作...
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     this.setState({
       data: selectData(DataSource, this.props)
     });
   }
   render() {
     // ... 并使用新数据渲染被包装的组件!
     // 请注意,我们可能还会传递其他属性
     return <WrappedComponent data={this.state.data} {...this.props} />;
   }
 };
}

HOC不会修改传入的组件,也不会使用继承来复制其行为,相反HOC是通过将组件包装在容器组件中来组成新的组件,HOC是纯函数,没有副作用

  • 被包装组件接收来自容器组件的所有prop,同时也接收一个新的用于render的data prop
  • HOC不用关心数据的使用方式,被包装组件也不用关心数据是怎么来的

不用改变原始组件使用组合

不要试图在 HOC 中修改组件原型(或以其他方式改变它)

function logProps(InputComponent) {
 InputComponent.prototype.componentDidUpdate = function(prevProps) {
   console.log('Current props: ', this.props);
   console.log('Previous props: ', prevProps);
 };
 // 返回原始的 input 组件,暗示它已经被修改。
 return InputComponent;
}
// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent)
//上面这种写法会造成另一个同样会修改componentDidUpate的HOC增强它,那么前面的HOC就会失效。

HOC不应该修改传入组件,而应该使用组合的方式,将组件包装在容器组件中实现功能。

function logProps(WrappedComponent) {
    return class extends React.Component {
      componentDidUpdate(prevProps) {
        console.log('Current props: ', this.props);
        console.log('Previous props: ', prevProps);
      }
      render() {
        // 将 input 组件包装在容器中,而不对其进行修改。Good!
        return <WrappedComponent {...this.props} />;
      }
    }
  }

约定-将不相关的 props 传递给被包裹的组件

HOC为组件添加特性,自身不应该大幅改变约定,HOC应该透传与自身无关的props,大多数HOC都应该包含一个类似于下面的render方法

render() {
  // 过滤掉非此 HOC 额外的 props,且不要进行透传
  const { extraProp, ...passThroughProps } = this.props;
  // 将 props 注入到被包装的组件中。
  // 通常为 state 的值或者实例方法。
  const injectedProp = someStateOrInstanceMethod;
  // 将 props 传递给被包装组件
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}	

约定-最大化可组合性

有时候它仅接受一个参数,也就是被包裹的组件:

const NavbarWithRouter = withRouter(Navbar);

HOC通常也可以接收多个参数

const CommentWithRelay = Relay.createContainer(Comment, config);

常见的HOC签名(React Redux的connect函数):

// React Redux 的 `connect` 函数const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

拆分connect函数

  // connect 是一个函数,它的返回值为另外一个函数。
  const enhance = connect(commentListSelector, commentListActions)
  // 返回值为 HOC,它会返回已经连接 Redux store 的组件
 const ConnectedComment = enhance(CommentList);

约定-包装显示名称以便轻松调试

HOC创建的容器组件会和任何其他组件一样,显示在React Developer Tools中,为了方便调试,需要选择显示一个名称,以表明他是HOC的产物

function withSubscription(WrappedComponent) {
 class WithSubscription extends React.Component {/* ... */}
 WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
 return WithSubscription;
}
function getDisplayName(WrappedComponent) {
 return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

使用高阶组件的注意事项

不要在render方法中使用HOC

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}

务必复制静态方法

   // 定义静态函数
 WrappedComponent.staticMethod = function() {/*...*/}
 // 现在使用 HOC
 const EnhancedComponent = enhance(WrappedComponent);
 // 增强组件没有 staticMethod
 typeof EnhancedComponent.staticMethod === 'undefined' // true
//为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:
function enhance(WrappedComponent) {
   class Enhance extends React.Component {/*...*/}
   // 必须准确知道应该拷贝哪些方法 :(
   Enhance.staticMethod = WrappedComponent.staticMethod;
   return Enhance
 }

Refs 不会被传递

虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。

到此这篇关于React高阶组件使用教程详解的文章就介绍到这了,更多相关React高阶组件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React 高阶组件入门介绍

    高阶组件的定义 HoC 不属于 React 的 API,它是一种实现模式,本质上是一个函数,接受一个或多个 React 组件作为参数,返回一个全新的 React 组件,而不是改造现有的组件,这样的组件被称为高阶组件.开发过程中,有的功能需要在多个组件类复用时,这时可以创建一个 Hoc. 基本用法 包裹方式 const HoC = (WrappendComponent) => { const WrappingComponent = (props) => ( <div className=&

  • react高阶组件添加和删除props

    唠叨几句啦 在看程墨老师的深入浅出高阶组件,开头一点提了一个需要,创建两个高阶组件,一个能给传入的元素自定义添加props,一个是删除特定的props.我刚刚做了一下,发现高阶组件需要区分好传入的是class还是react element, 同时也需要注意好return回去的是啥.顺便提一下高阶组件的概念,就说一个函数,能够接受一个组件作为参数,然后返回的时候,这个组件就带有这个高阶组件给的某些特性.我理解就跟掉泥坑了,得带点土出来一个道理. 对比一下两个组件,贴代码时刻来啦 删除属性的高阶组件

  • React 高阶组件与Render Props优缺点详解

    目录 高阶组件 增强型高级组件 注入型高阶组件 高阶组件 VS Render Props 总结 高阶组件 高阶组件(HOC)是一个接受组件作为参数并返回一个新组件的函数,如果多个组件有相同的逻辑,将这些逻辑用函数封装,使它们能跨组件共用,这种用法称为高阶组件.下面的代码演示什么是高阶组件: export default function WithPrintLog(InnerComponent) { return class extends React.Component{ componentDi

  • 利用React高阶组件实现一个面包屑导航的示例

    什么是 React 高阶组件 React 高阶组件就是以高阶函数的方式包裹需要修饰的 React 组件,并返回处理完成后的 React 组件.React 高阶组件在 React 生态中使用的非常频繁,比如react-router 中的 withRouter 以及 react-redux 中 connect 等许多 API 都是以这样的方式来实现的. 使用 React 高阶组件的好处 在工作中,我们经常会有很多功能相似,组件代码重复的页面需求,通常我们可以通过完全复制一遍代码的方式实现功能,但是这

  • React高阶组件的使用浅析

    目录 高阶函数 高阶组件 react常见的高阶函数 高阶组件形式 在学习高阶组件前,首先我们了解一下高阶函数 高阶函数 把一个函数作为另一个函数的参数,那么这个函数就是高阶函数 高阶组件 一个组件的参数是组件,并且返回值是一个组件,我们称这类组件为高阶组件 react常见的高阶函数 withRouter() memo() react-redux中connect 高阶组件形式 React中的高阶组件主要有两种形式:属性代理和反向继承 属性代理:一个函数接收一个WrappedComponent组件作

  • React 高阶组件HOC用法归纳

    一句话介绍HOC 何为高阶组件(HOC),根据官方文档的解释:"高阶组件是react中复用组件逻辑的一项高级技术.它不属于react API的组成部分,它是从react自身组合性质中抽离出来的一种模式.具体来说,高阶组件是函数,它接受一个组件作为参数,然后返回一个新的组件 使用场景 将几个功能相似的组件里面的方法和react特性(如生命周期里面的副作用)提取到HOC中,然后向HOC传入需要封装的组件.最后将公用的方法传给组件. 优势 使代码简洁优雅.代码量更少 HOC(高阶组件) /* HOC(

  • React HOC高阶组件深入讲解

    目录 1. 概念 2. 属性代理 2.1 代理props 2.2 条件渲染 2.3 添加状态 3. 反向继承 3.1 拦截渲染 3.2 劫持生命周期 3.3 操作state 3.4 修改react树 3.5 记录渲染性能 4. 使用装饰器 4.1 安装和配置 4.2 使用 5.总结 1. 概念 高阶组件和高阶函数的类似,使用函数接收一个组件,并返回一个组件. function withList(WrapComponent) { return class extends Component { r

  • 浅谈React高阶组件

    前段时间在工作中写Hybrid页面时遇到了这样的一个场景,公司需要一系列的活动组件,在每个组件注册的时候都需要调用App端提供的一个接口.一开始也考虑了几种方式,包括mixin.组件继承以及react高阶组件.但经过了种种衡量,最后选择使用了高阶组件的做法. 那什么是高级组件?首先你得先了解请求ES6中的class只是语法糖,本质还是原型继承.能够更好的进行说明,我们将不会修改组件的代码.而是通过提供一些能够包裹组件的组件, 并通过一些额外的功能来增强组件.这样的组件我们称之为高阶组件(High

  • React高阶组件使用教程详解

    目录 高阶组件(HOC) 概述 使用HOC解决横切关注点问题 不用改变原始组件使用组合 约定-将不相关的 props 传递给被包裹的组件 约定-最大化可组合性 约定-包装显示名称以便轻松调试 使用高阶组件的注意事项 高阶组件(HOC) 概述 是React复用组件逻辑的一种高级技巧,是一种基于React组合特性而形成的设计模式 高阶组件是参数为组件,返回值为新组件的函数 简单理解: 高阶组件本身是 函数,传参数是组件,返回值也是组件: 高阶组件不用关心数据是如何渲染的,只用关心逻辑即可 被包装的组

  • React为 Vue 引入容器组件和展示组件的教程详解

    如果你使用过 Redux 开发 React,你一定听过 容器组件(Smart/Container Components) 或 展示组件(Dumb/Presentational Components),这样划分有什么样的好处,我们能否能借鉴这种划分方式来编写 Vue 代码呢?这篇文章会演示为什么我们应该采取这种模式,以及如何在 Vue 中编写这两种组件. 为什么要使用容器组件? 假如我们要写一个组件来展示评论,在没听过容器组件之前,我们的代码一般都是这样写的: components/Comment

  • 使用Vue开发动态刷新Echarts组件的教程详解

    需求背景:dashboard作为目前企业中后台产品的"门面",如何更加实时.高效.炫酷的对统计数据进行展示,是值得前端开发工程师和UI设计师共同思考的一个问题.今天就从0开始,封装一个动态渲染数据的Echarts折线图组件,抛砖引玉,一起来思考更多有意思的组件. 准备工作 项目结构搭建 因为生产需要(其实是懒),所以本教程使用了 ==vue-cli==进行了项目的基础结构搭建. npm install -g vue-cli vue init webpack vue-charts cd

  • python高级特性和高阶函数及使用详解

    python高级特性 1.集合的推导式 •列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if codition - 可选 •字典推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:{key_exp:value_exp for item in collection if codition} •集合推导式 语法:{exp for item in collection if

随机推荐