在 vue3 中使用 高阶组件(HOC) - 实战篇

2,743 阅读1分钟

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

上一篇文章中,我们简单了解了什么是高阶函数、什么是高阶组件、h函数的应用场景。

这篇文章开始使用一些案例学习:

  • HOC的主要作用是什么
  • 正确使用 hoc 以及在哪些场景可以使用;
  • 重用逻辑的提取;

使用HOC(高阶组件),我们可以创建一个函数组件,这些函数组件既可以接受内部使用的选项,也可以传递额外的功能。这个功能有助于实现许多事情,而无需在使用它的每个组件中编写相同的逻辑。

作用

在业务开发中,不使用高阶组件也能完成项目的开发,使用它让我更加有编写代码的兴致,使用它写的组件变得比较灵活,也能够提高项目代码复用性,提升开发效率。

1. 注入属性

最常见的功能,将组件的属性,注入到子组件,强化子组件。

我们之前将所有属性注入到单个组件的时候,使用了 v-bind="$attrs",现在使用函数式组件,就需要使用 useAttrs() 这个hook来拿到所有的属性包括事件。

type AttrType = Record<string, unknown>
function WithAttr(WrapperComponent: DefineComponent<{
    a: number,
} & Record<string, unknown>>) {
    return defineComponent({
        setup() {
            const number = ref(123)
            onMounted(() => {
                console.log('with attr mounted')
            })
            const attr: AttrType = useAttrs()

            return () => h(WrapperComponent, {
                ...attr,
                a: number.value
            })
        }
    })
}
// base.vue
<template>
  <div>{{ a }} --- {{ b }}</div>
</template>

<script lang="ts" setup>
defineProps({
  a: {
    type: Number,
    default: 0
  },
  b: {
    type: String,
    default: ''
  }
})
</script>
<template>
    <useBaseHoc :b="'bbbb'"></useBaseHoc>
<template>
<script lang="ts" setup>
const UseBaseHoc = withHoc.WithBase(baseHoc)
</script>

image.png

2. 状态修改、属性代理

function WithCounter(WrapperComponent: DefineComponent, count) {
    return defineComponent({
        setup() {
            const counter = ref(0)
            const increment = () => {
                counter.value = counter.value + count
            }
            const attrs = useAttrs()
            return () => h(WrapperComponent, {
                increment,
                counter: counter.value,
                ...attrs
            })
        }
    })
}

使用同理,传递第二个参数即可,我们将为计数器创建两个组件,CounterOne.vueCounterFive.vue,第一个组件加上一,第二个组件加上五。

注意:通过属性代理方式实现的高阶组件无法直接操作原组件的属性,但是可以通过 props 回调函数对 属性 进行更新。️

// countHoc.vue
<template>
<div>
  <div>count: {{ counter }}</div>
  <button @click="increment">+++</button>
</div>
</template>
<script setup lang="ts">
defineProps({
  counter: {
    type: Number,
    default: 0
  },
  increment: {
    type: Function,
    default() {
      return () => null
    }
  }
})
</script>
const CounterOne = WithCounter(countHoc, 1)
const CounterFive = WithCounter(countHoc, 5)

image.png image.png

3. 控制渲染

应用场景:按钮权限组件,页面权限,懒加载等等。

内部组件不需要也不能操控是否渲染,而是把这一切交给外层组件来判断,比如使用了高阶组件判断访问页面组件是否拥有权限,没有的话通过 useRouter() 跳转到登录页面...

按钮权限:可以封装一个权限按钮

export const superBtn = withAuth(Button, 'super')  // 此时 roleType: 'super'
// export const xxxBtn = withAuth(Button, 'xxx')
const { query } = useRouter()
const { role }= query
const isDisable = role === roleType
return () => {
    h(WrapperComponent, {
        ...attr,
        disable: isDisable
    })
}

通过这样来渲染一个按钮是否显示或者禁用。

4. 列表渲染通用逻辑复用

我们采用之前一篇文章的例子来做修改:

动画scroll2.gif

WithRequestDog

function WithRequestDog(WrapperComponent: DefineComponent) {
    return (title: string, url: string) => {
        return defineComponent({
            setup() {
                const list = ref([])
                const count = ref(2)
                const loading = ref(false)
                const page = ref(1)
                const attr = useAttrs()

                const fetchDog = async () => {
                    loading.value = true
                    const res = await axios.get(`${url}/${count.value}`)
                    list.value = res.data.message
                    loading.value = false
                }

                onMounted(async () => {
                    await fetchDog()
                })

                const loadMore = async () => {
                    count.value += 2
                    await fetchDog()
                    page.value += 1
                }

                return () => h(WrapperComponent, {
                    list: list.value,
                    page: page.value,
                    loadMore,
                    title,
                    loading: loading.value,
                    ...attr
                })
            }
        })
    }
}

视图组件

<template>
  <div>
    <h1>{{ title }}</h1>
    <ul v-if="list.length >0">
      <li v-for="item in list">
        <img :src="item" alt="">
      </li>
    </ul>
    <p>当前第{{ page }}页</p>
    <template v-if="loading">loading...</template>
    <button @click="loadMore">加载更多</button>
  </div>
</template>
<script lang="ts" setup>
import { useAttrs } from 'vue'

defineProps({
  list: {
    type: Array,
    default() {
      return []
    }
  },
  title: {
    type: String,
    default: ''
  },
  page: {
    type: Number,
    default: 0
  },
  loadMore: {
    type: Function,
    default() {
      return () => {
      }
    }
  },
  loading: {
    type: Boolean,
    default: false
  }
})

console.log(useAttrs())
</script>

<style>
img {
  width: 50px;
  height: 50px;
}
</style>

逻辑组件 UseDogA

<template>
  <UseDogA></UseDogA>
</template>

<script lang="ts" setup>
// ...
const UseDogA = withbase.WithRequestDog(Base)('Adog', 'https://dog.ceo/api/breeds/image/random')
</script>

逻辑组件 UseDogB

<template>
  <UseDogB :a="'asdasda'"></UseDogB>
</template>

<script lang="ts" setup>
// ...
const UseDogB = withbase.WithRequestDog(Base)('Bdog', 'https://dog.ceo/api/breeds/image/random')
</script>

总结

以上就是关于 vue 编写 hoc 的方法与作用的例子,感谢观看