在 vue3 中使用 高阶组件(HOC) - 预备篇

723 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

在 vue3 中使用 高阶组件(HOC)

本篇是关于 vue 编写高阶组件的理论篇,后面会出一篇来编写代码实例。

对于HOC的理解开始是来自 React 文档中:

高阶组件 (HOC) 是 React 中用于重用组件逻辑的高级技术。HOC 本身并不是 React API 的一部分。它们是从 React 的组合性质中出现的一种模式。

因为一直使用 vue 模板语法开发,对于高阶组件的使用一直迷迷糊糊,直到项目启动重构,在使用 vue3 中引入了 jsx 写法,vue2 其实也可以通过引入插件来支持 jsx,而不是说 vue2 就不能写。

本篇我们先了解,下一篇开始编写高阶组件

  • 什么是高阶函数
  • 什么是高阶组件
  • vue渲染函数 h() 应用

高阶函数(HOF)

为了能够理解 Vue 中的高阶组件,我们首先需要了解 JavaScript 中的高阶函数是什么。Javascript 中的高阶函数是将其他函数作为参数并返回另一个函数的函数。

我们可能曾经在 JavaScript 中使用过高阶组件

高阶函数是将函数作为参数接受或将函数作为返回的函数。

一些示例

function add(number, count, callback) {
    return callback(number + count)
}

我们可能在不知道高阶函数之前,也不知不觉中使用过高阶函数,看看 Javascript 中内置的一些高阶函数

例如,一些比较常用的Array方法,这些方法都将一个函数作为参数。

  • map: 接收一个函数作为回调,每次迭代的返回值组成一个数组作为该函数的返回值
  • forEach: 接受一个函数值作为回调,返回 undefined
  • filter
  • reduce
  • ...

JavaScript 中有很多高阶函数可以使用,为我们日常开发提供了便利。

有时候也可以使用其他第三方库的高阶函数,如 Lodash...

  • curry
  • compose
  • partial
  • ...

纯函数

高阶函数对它们接收的函数或者值没有影响。它们不会改变输入的参数的值。

高阶组件也必须是纯函数,对它们接收的组件没有影响,它们不会改变输入组件的所有东西。

高阶组件(HOC)

上文提到,高阶函数是将另一个函数作为参数接受或将函数作为返回值的函数;那么 HOC 就是可以将另一个组件作为参数接受或将组件作为返回值的函数。

使用 HOC 我们可以包装子组件,并且在其基础上添加业务逻辑或扩展子组件本身的功能。

返回的组件渲染将包含传递的子组件,但是具有更高级的功能。

渲染函数 h 的应用

关于语法,vue 文档已经非常清楚了,我们通过一个例子来看看在 vue 中的一种使用场景。

我们想要编写这样一个列表:

image.png

Item.vue

<template>
  <li>
    <slot/>
  </li>
</template>

ListContainer.vue

<template>
    <ul>
      <slot />
    </ul>
</template>

app.vue

<template>
    <ListContainer>
        <Item v-for="item in 3" :key="item">
          <img width="50" height="50" src="https://images.dog.ceo/breeds/brabancon/n02112706_292.jpg" alt="">
        </Item>
    </ListContainer>
</template>

可能你们觉得到这里就结束了,现在又有个列表,想要输出一个有序的,如:

image.png

或者仅仅将其渲染出来:

image.png

现在开始改造 ListContainer.vue

<script setup lang="ts">
import { Component, defineProps, h, useAttrs, useSlots } from 'vue'

interface FlexContainerProps {
  tag: keyof HTMLElementTagNameMap;
}

const slots = useSlots()
const attrs = useAttrs()

const props = withDefaults(defineProps<FlexContainerProps>(), {
  tag: 'ul'
})

const ListContainer: Component = () => h(props.tag, attrs, slots)
</script>

<template>
  <ListContainer>
    <slot />
  </ListContainer>
</template>

通过使用 h(props.tag, attrs, slots),我们编写了一个函数式组件,只需要传递 tag 标签名,就可以渲染对应的标签。

<ListContainer tag="ul">
  <Item v-for="item in 3" :key="item">
    <img width="50" height="50" src="https://images.dog.ceo/breeds/brabancon/n02112706_292.jpg" alt="">
  </Item>
</ListContainer>
<ListContainer tag="ol">
  <Item v-for="item in 3" :key="item">
    <img width="50" height="50" src="https://images.dog.ceo/breeds/brabancon/n02112706_292.jpg" alt="">
  </Item>
</ListContainer>
<ListContainer tag="div">
  <Item v-for="item in 3" :key="item">
    <img width="50" height="50" src="https://images.dog.ceo/breeds/brabancon/n02112706_292.jpg" alt="">
  </Item>
</ListContainer>

image.png

这个属性 tag 也可以是组件,那能做的事情就很多了。

我们下一篇就开始编写一些实例来理解它。