前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

HOC真的就那么高级吗|博客系列

# 1. HOC的概念

其实如果有过React开发经验的小伙伴对HOC的概念应该就不陌生了,不过既然是介绍它的话,那也稍微正式一点:

HOC,全称Higher-Order Components,即高阶组件。

它的概念应该是来源于JavaScript的高阶函数,我们知道高阶函数就是接受函数作为输入或者输出的函数。

通俗来说就是一个函数,它的参数可以是一个函数,它的返回值也可以是一个函数😄,这样的函数就被称为高阶函数。

例如🌰下面的这两个函数:

// 1. 参数为函数
const test1 = fn => {
  setTimeout(() => fn(), 1000)
};
const log1 = () => console.log('我爱学习');
test1(log1); // 1s后打印

// 2. 返回值为函数
const test2 = () => {
  const log2 = name => console.log(name);
  return log2;
}
test2()('学习不爱我'); // 理解打印
@前端进阶之旅: 代码已经复制到剪贴板

那么其实,高阶组件它也仅仅只是一个接受组件作为输入并返回组件的函数。认为它并不是一个新的API或者一个新的什么玩意,仅仅是一种模式吧,或者说是一种技巧,这种技巧能够帮助我们复用组件逻辑。

就像下面👇这样的用法:

让我们来创建一个FinalComponent.js

import React from 'react';

function MyHOC (WrappedComponent) {
  return class extends React.Component {
    render () {
      return <WrappedComponent />;
    }
  }
}
class TestComponent extends React.Component {
  render () {
    return (
      <div>我就是个普通的组件</div>
    )
  }
}
const FinalComponent = MyHOC(TestComponent);
export default FinalComponent;
@前端进阶之旅: 代码已经复制到剪贴板

在这个案例中,我做了这么几件事:

  • 创建了一个名为MyHOC的函数,它接收一个名为WrappedComponent的参数,并返回一个新的匿名组件
  • 这个匿名组件的render返回的是传递进来的WrappedComponent组件
  • 之后创建了一个名为TestComponent的组件
  • 再调用MyHOC函数并把TestComponent传递进去赋值给FinalComponent变量
  • 此时的FinalComponent其实就是那个匿名组件,我们将它导出。

在其它地方使用FinalComponent这个组件的话,就能正常渲染出"我就是个普通的组件"了。

可以看到,上面👆的这种用法其实就叫高阶组件,它首先需要定义一个函数,然后这个函数接收一个组件并返回一个新的组件。

大家要注意这里的命名哟,MyHOC函数的参数必须大写开头的,因为后面需要把它当成组件来返回,而我们知道,在React中如果是组件的话,它的命名开头必须是要大写,React会将小写开头的组件当成普通的HTML标签处理,这样就会报错。

(另外还有一点,HOC并不是React里独有的,其它框架也可以使用,比如晨曦老哥的这篇文章就介绍了它在Vue中的用法: Vue 进阶必学之高阶组件 HOC)

好的,既然在上面谈到了高阶组件主要是可以帮助我们复用组件逻辑,那大家会不会想到另一个叫Mixin的东西呢?但是因为ES6本身是不包含任何Mixin支持的,所以当你在React中使用ES6 class时,将不支持Mixin ,而且使用它本身会有很多问题,现在也是不推荐使用了。

既然你把高阶组件吹的这么牛,那它具体怎么用呢?

咦~这么着急干嘛?哈哈哈哈,咱接着往下看。

表情包不要着急

# 2. 如何实现高阶组件

# 2.1 属性代理

其实在上面👆已经向大家展示了高阶组件的基本用法,让我们来简单回顾一下前面是怎么做的:

import React from 'react';

function MyHOC (WrappedComponent) {
  return class extends React.Component {
    render () {
      return <WrappedComponent />;
    }
  }
}
class TestComponent extends React.Component {
  render () {
    return (
      <div>我就是个普通的组件</div>
    )
  }
}
const FinalComponent = MyHOC(TestComponent);
export default FinalComponent;
@前端进阶之旅: 代码已经复制到剪贴板

定义一个函数(MyHOC)且接收一个组件,之后返回一个新的组件。

那大家试想一下,如果此时我在页面中引用了FinalComponent组件,并且需要向TestComponent传递一些属性,也就是props,该怎么做呢?

<FinalComponent id={1} />
@前端进阶之旅: 代码已经复制到剪贴板

通过这样传递的id虽然不能直接被TestComponent组件给拿到,但是却可以在MyHOC中拿到,因为此时FinalComponent确实就是MyHOC函数中导出的那个匿名组件,这样的话,我们就可以通过this来访问到这个匿名组件的一些属性,包括使用这个组件时传递的一些props:

function MyHOC (WrappedComponent) {
  return class extends React.Component {
    render () {
      console.log(this.props); // { id: 1 }
      return <WrappedComponent />;
    }
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

好的👌!我们已经成功拿到调用FinalComponent时传递的props,接下里需要把它传递给WrappedComponent,这就很简单了,只需要使用ES6的对象展开操作符即可实现,也就是这样:

function MyHOC (WrappedComponent) {
  return class extends React.Component {
    render () {
      console.log(this.props); // 一、{ id: 1 }
      return <WrappedComponent {...this.props} />;
    }
  }
}
class TestComponent extends React.Component {
  render () {
    return (
      console.log(this.props); // 二、在这里能拿到
      <div>我就是个普通的组件</div>
    )
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

这个过程其实就是一个浅拷贝的过程,如果this.props有多个属性的话,都会将其展开传递给WrappedComponent:

console.log(this.props); // { id: 1, uid: 123 }

<WrappedComponent {...this.props} />
// 等价于 =>
<WrappedComponent id={1} uid={123} />
@前端进阶之旅: 代码已经复制到剪贴板

可以看到,在每次调用WrappedComponent组件的时候,都必然要经过MyHOC函数,也就是说MyHOC成了WrappedComponent的"代理",那么我们是不是就可以对在这一层做一些额外的操作,例如操作前面提到的this.props,或者是WrappedComponent的静态属性方法。

像这种函数返回一个我们自己定义的组件,然后在render中返回要包裹的组件,同时在函数中做一些额外处理的方式,我们就称之为属性代理,根据它的功能来看这个名字是不是很好理解呢?😊。

一起来看个简写:

function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

表情包开心

好的👌,既然已经告诉了你们这种全新的使用方式了,那你能想到通过这种名为"属性代理"的东西能让我们做哪些好玩有趣的事吗?

首先第一点来给你起个头:在我们定义的高级组件的那个函数中,是可以对要返回的组件的属性进行二次加工的,那我们是不是就可以给this.props添加上一些新的属性,并传递给WrappedComponent?OK👌,让我们来看看"属性代理"它的第一种用法。

# 2.1.1 操作props

就像上面👆说的,我们可以给this.props添加上一些新的属性并向下传递,但是这个添加不是让你去直接修改this.props哈,比如下面👇这种用法肯定就是不行的了:

render () {
 this.props.remark = '别自闭'
 return <WrappedComponent {...this.props} />
}
@前端进阶之旅: 代码已经复制到剪贴板

因为React是单向数据流,它不允许你去修改props,因此你会发现控制台直接就报错了:

HOC21.jsx:10 Uncaught TypeError: Cannot add property remark, object is not extensible
@前端进阶之旅: 代码已经复制到剪贴板

那好的,我新建一个对象再添加额外属性就不行了😄?比如我们可以把前面的案例改造一下,这样做:

import React from 'react';

function MyHOC (WrappedComponent) {
  return class extends React.Component {
    render () {
      const newProps = { // 重点看这里
        ...this.props,
        remark: '别自闭'
      }
      return <WrappedComponent {...newProps} />
    }
  }
}

class TestComponent extends React.Component {
  render () {
    console.log(this.props) // { id: 1, remark: '别自闭' }
    return <div>我就是个普通div</div>
  }
}
const FinalComponent = MyHOC(TestComponent);
export default FinalComponent;
@前端进阶之旅: 代码已经复制到剪贴板

使用:

<FinalComponent id={1} />
@前端进阶之旅: 代码已经复制到剪贴板

大家可以看到,虽然FinalComponent我们在调用的时候只传递了一个属性,也就是id,但是最终TestComponent组件接收到的props却经过MyHOC添加上了额外的属性"自闭",呸,是"别自闭"。

这样就达到了在调用TestComponent的时候,可以额外新加一些属性的功能。例如现在如果我们有好几个组件,都需要添加一些相同的属性,那么我们是不是只需要定义好一个MyHOC,然后让这些组件都经过MyHOC过一遍就可以了。

Good boy!现在我们已经会"属性代理"的其中一种用法了,不说了,学累了,喝口水去,顺便想想还可以怎样用。

表情包休息时间

# 2.1.2 组合渲染

唔,欢迎回来呀。就在刚刚看表情包的时间,大家有想到什么其它的用法不?

← Dva实践总结React Router原理 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合