「React」之组件逻辑复用小技巧

news/2024/7/10 2:07:49 标签: java, js, python, vue, javascript
js_content">

编者荐语:

本文将介绍React组件逻辑复用的一些常用模式和技巧。包括一下几个方面:

  • 什么是高阶组件HOC

  • HOC解决了哪些问题

  • 如何封装一个简单的高阶组件

  • HOC在项目中常用的一些技巧和方法

  • 什么是Render Props

  • Render Props的特点和用法

  • Render PropsHOC React Hooks相比,有哪些优劣(重要面试题)

HOC高阶组件

高阶组件(HOC):是React中用于复用组件逻辑的一种高级技巧HOC自身不是React API的一部分,它是一种基于React的组合特性而形成的设计模式

高阶组件可以看做React装饰器模式的一种实现,具体而言,高阶组件是参数作为组件,返回值为新组件的函数

HOC解决的问题

  • 抽离公共组件,实现组件代码复用,常见场景:页面复用。

  • 条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景:权限控制。

  • 捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。

当我们项目中使用高阶组件开发时,能够让代码变得更加优雅,同时增强代码的复用性和灵活性,提升开发效率

高阶组件的基本框架

高阶组件的框架:

export default (WrappedComponent) => {
 return class NewComponent extends React.Component {
  // 可以自定义逻辑
    // 比如给 WrappedComponent组件传递props和methods
    render () {
   return <WrappedComponent {...this.props}/>
    }
  }
}

如果自定义了statemethods可以通过下面方式传递到子组件中

export default (WrappedComponent) => {
 return class NewComponent extends React.Component {
  state = {
      markTime: new Date().toLocaleTimeString(); // 获取组件当前渲染时的时间
    }
    printTime() {
      let myDate = new Date();
      let myTime= myDate.toLocaleTimeString(); 
      console.log('当前时间', myTime)
    }
    render () {
   return <WrappedComponent markTime={this.state.markTime} printTime={this.printTime}/>
    }
  }
}

这样在WrappedComponent组件中,如果是类组件就可以通过this.props.markTime获取,函数组件的话通过props.markTime来获取,方法获取和状态获取相同。

HOC可以做什么

属性代理——可操作所有传入的props

可以读取、添加、编辑、删除传给 WrappedComponent 的 props(属性)

「场景描述」:Hello组件传递show,hide方法,让其显示Loading加载框

const loading = message => OldComponent => {
  return class extends React.Component {
    // 显示一个 Loading的div
    state = {
      show: () => {
        let div = document.createElement('div');
        div.innerHTML = `<p id="loading" style="position: absolute; z-index:10; top: 10; color: red; border: 1px solid #000">${message}</p>`
        document.body.appendChild(div);
      },
      hide: () => {
        document.getElementById('loading').remove();
      }
    }
    render() {
      return (
        <div>
          <OldComponent {...this.props} {...this.state}/>
        </div>
      )
    }
  }
}
function Hello(props) {
  return (
    <div>hello
      <button onClick={props.show}>show</button>
      <button onClick={props.hide}>hide</button>
    </div>
  )
}

let HightLoadingHello = loading('正在加载')(Hello);
ReactDom.render(<HightLoadingHello/>, document.getElementById('root'));

效果如图:

抽离公共组件,最大化实现复用

「场景描述」:统计每个组件的渲染时间

class CalTimeComponent extends React.Component {
  componentWillMount() {
    this.start = Date.now(); // 初始渲染节点
  }
  componentDidMount() {
    console.log((Date.now() - this.start) + 'ms');
  }
  render() {
    return <div>calTimeComponent</div>
  }
}

ReactDom.render(<CalTimeComponent/>, document.getElementById('root'));

这样仅仅能计算当前组件的渲染时间,假如现在有这样一个需求,需要统计每个组件的渲染时间呢?

就应该想到把它抽离出去,比如:

// CalTimeComponent.js
export default function CalTimeComponent(OldComponent) {
  return class extends React.Component {
    state = {
      markTime: new Date().toLocaleTimeString()
    }
    componentWillMount() {
      this.start = Date.now();
    }
    componentDidMount() {
      console.log((Date.now() - this.start) + 'ms');
    }
    printTime() {
      let myDate = new Date();
      let myTime= myDate.toLocaleTimeString(); 
      console.log('当前时间', myTime)
    }
    render() {
      return <OldComponent markTime={this.state.markTime} printTime={this.printTime}/>
    }
  }
}

// HelloComponent.js
import withTracker from '../../Components/CalTimeComponent.js';

class HelloComponent extends React.Component{
  render() {
    console.log(this.props);
    this.props.printTime()
    return <div>hello</div>
  }
}

let HighHelloComponent = CalTimeComponent(HelloComponent);
ReactDom.render(<HighHelloComponent/>, document.getElementById('root'));

这样就能最大化的实现CalTimeComponent组件复用了,把它引入到想要计算时间的组件里,并传入当前组件就好了。

Render Props

特点1render props指在一种React组件之间使用一个值为函数的props共享代码的简单技术。

特点2:具有render props的组件接收一个函数,该函数返回一个React元素并调用它而不是实现一个自己的渲染逻辑。

特点3render props是一个用于告知组件需要渲染什么内容的函数(props)

特点4:也是组件逻辑复用的一种实现方式

接下来,我通过一个例子带大家分别认识上面的四种特点

「场景描述」: 在多个组件内实时获取鼠标的x、y坐标

原生实现:不复用逻辑

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x: 0,
      y: 0,
    }
  }
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        <h1>请移动鼠标</h1>
        <p>当前鼠标的位置是:x:{this.state.x} y:{this.state.y}</p>
      </div>
    )
  }
}

ReactDom.render(<MouseTracker/>, document.getElementById('root'));

上面,这是在一个组件内完成的,假如现在要在多个div内完成上面的逻辑该怎么办,就该想到复用了,看看render prop是怎么帮我们完成的?

Render Props

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x: 0,
      y: 0,
    }
  }
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    console.log(this.props)
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}

ReactDom.render(
<MouseTracker render={
  props => (
    <React.Fragment>
      <h1>请移动鼠标</h1>
      <p>当前鼠标的位置是: x:{props.x} y:{props.y}</p>
    </React.Fragment>
  )
}></MouseTracker>, document.getElementById('root'));

注意:render props 是因为模式才被称为 render props ,你不一定要用名为 renderprops 来使用这种模式。render props 是一个用于告知组件需要渲染什么内容的函数 `prop

那如果改写成高阶组件呢?

高阶组件写法

改写成高阶组件,并将公共组件抽离出去, ShowPosition子组件中可以拿到withTracker父组件中传递的x、y坐标值

// withTracker.js
export default function withTracker (OldComponent) {
  return class MouseTracker extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        x: 0,
        y: 0,
      }
    }
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      });
    }
    render() {
      console.log(this.props)
      return (
        <div onMouseMove={this.handleMouseMove}>
          <OldComponent {...this.state}/>
        </div>
      )
    }
  }
}

// ShowPosition.js
import withTracker from '../../Components/withTracker.js';

function ShowPosition(props) {
  return (
    <React.Fragment>
      <h1>请移动鼠标</h1>
      <p>当前鼠标的位置是: x:{props.x} y:{props.y}</p>
    </React.Fragment>
  )
}

// 在 ShowPosition 组件中 可以拿到 withTracker 传递过来的坐标值
let HightShowPosition = withTracker(ShowPosition);

ReactDom.render(<HightShowPosition/>, document.getElementById('root'));

hoc、render props、react-hooks的优劣如何?

HOC的优势:

  • 抽离公共组件,实现组件代码复用,常见场景:页面复用。

  • 条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景:权限控制。

  • 捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。

  • 属性代理,可以给一些子组件传递层次比较远的属性,并按需求操作他们

HOC的缺陷:

  • 扩展性限制HOC无法从外部访问子组件(被包裹组件WrappedComponent)的state,因此无法通过shouldComponentUpdate过滤掉不必要的更新(React支持ES6之后,提供了React.pureComponent来解决这个问题)

  • Ref传递问题Ref由于组件被高阶组件包裹,导致被隔断,需要后来的React.forwardRef来解决这个问题

  • 层级嵌套HOC可能出现多层包裹组件的情况(一般不超过两层,否则不好维护)多层抽象增加了复杂度和理解成本

  • 命名冲突:如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,覆盖老属性

Render Props优点:

  • 上述HOC的缺点,Render Props都可以解决

Render Props缺陷:

  • 使用繁琐HOC只需要借助装饰器/高阶函数的特点就可以进行复用,而Render Props需要借助回调嵌套

  • 嵌套过深Render Props虽然摆脱了组件多层嵌套的问题,但是转化为了函数回调的嵌套

React Hooks的优点(重点):

  • 简洁React Hooks解决了HOCRender Props的嵌套问题,更加简洁

  • 解耦React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦

  • 无影响复用组件逻辑Hook 使你在无需修改组件结构的情况下复用状态逻辑

  • 函数友好:React Hooks为函数组件而生,从而解决了类组件的几大问题

    • this 指向容易错误

    • 分割在不同声明周期中的逻辑使得代码难以理解和维护

    • 代码复用成本高(高阶组件容易使代码量剧增)

React Hooks的缺陷(重点):

  • 额外的学习成本(Functional Component 与 Class Component 之间的困惑)

  • 写法上有限制(不能出现在条件、循环中,只能在最外层调用Hooks),并且写法限制增加了重构成本

  • 破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)(依赖项不变,可解决该问题)

  • 使用不当,可能会造成闭包陷阱问题

  • React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)

关于react-hooks的评价来源于官方react-hooks RFC

最后

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 欢迎加我微信「qianyu443033099」拉你进技术群,长期交流学习...

  3. 关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。

点个在看支持我吧,转发就更好了


http://www.niftyadmin.cn/n/717510.html

相关文章

百度百科中关于推荐算法的描述

目录 1基于内容的推荐 2基于协同过滤的推荐 3基于关联规则推荐 4基于效用推荐 5基于知识推荐 6组合推荐 7主要推荐方法的对比 所谓推荐算法就是利用用户的一些行为&#xff0c;通过一些数学算法&#xff0c;推测出用户可能喜欢的东西。推荐算法主要分为两种。1基于内容的推荐编…

路由配置演示程序

路由配置演示程序~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~转载于:https://blog.51cto.com/wang3d/133485

分类测试

这是我的博客内容转载于:https://blog.51cto.com/sunkeydev/1697645

Oracle学习笔记:一个特殊的ORA-12541错误原因

ORA-12541: TNS: 无监听程序 原因&#xff1a;netca创建的listner监听配置里 host 不是计算机名&#xff0c;而是某个网卡的ip地址&#xff01;类似的&#xff1a;通过netca配置的tnsname.ora文件里&#xff0c;本地服务的host配置也会出现此种情况。一定要注意检查&#xff01…

前端监控原理,深入浅出,从上手到实战

前端监控分为性能监控和错误监控。其中监控又分为两个环节&#xff1a;数据采集和数据上报。本文主要讲的就是如何进行数据采集和数据上报。数据采集性能数据采集性能数据采集需要使用 window.performance API。Performance 接口可以获取到当前页面中与性能相关的信息&#xff…

转载关于视觉SCI期刊

ChanLee_1整理的计算机视觉领域稍微容易中的期刊 模式识别&#xff0c;计算机视觉领域&#xff0c;期刊 (1)pattern recognition letters, 从投稿到发表&#xff0c;一年半时间 (2)Pattern recognition 不好中&#xff0c;时间长 (3)IEICE Transactions on Information and Sys…

iOS:网络编程解析协议一:HTTP超文本传输协议

HTTP传输数据有四种方式&#xff1a;Get方式、Post方式、同步请求方式、异步请求方式。具体的介绍&#xff0c;前面已经有过系统的讲解&#xff0c;这次主要进行具体的举。 说明&#xff1a;同步和异步请求方式在创建链接对象和创建请求对象时&#xff0c;用Get方式或Post方式中…

ruby中判断数字类型+一道算法题

data.is_a?(Integer) 整数的判断data.is_a?(Float)浮点数的判断 data.is_a?(Numeric)是否数字的判断data.class Fixnum数字类型题目&#xff1a;一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&#…