利用vue组件中的render函数抽离组件中的可复用逻辑

2,047 阅读2分钟

需求(一个简单的栗子):

在涉及到权限控制的应用中,经常遇到这样的需求:根据用户角色判断某个组件(例如某个删除按钮、标签页)是否需要显示。最直接的解决方法就是利用v-if或者v-show对这种组件挨个判断。

例如:

<template>
<div>
    <button v-if='user.type === "admin"'>管理员才可见的按钮</button>
    <button v-if='user.type === "user"'>普通用户才可见的按钮</button>
</div>
</template>
<script>
import { useState } from 'vuex';

export default {
    computed: {
        ...useState(['user'])   //存储的user信息
    }
}
</script>

但如果项目中遇到大量这样的组件时,每个组件文件都需要编写获取用户信息、判断用户角色的重复代码,怎么才能把这些代码优雅地抽离复用呢?

初步解决方案:

编写已封装好业务逻辑的组件,比如组件ButtonForAdmin:

<template>
<button v-if='user.type === "admin"'>管理员才可见的按钮</button>
</template>
<script>
import { useState } from 'vuex';

export default {
    computed: {
        ...useState(['user'])   //存储的user信息
    }
}
</script>

这种方法可以一定程度上减轻代码重复度,缺点是当项目中每有一个不同的组件需要实现该需求时都要封装一遍。

进一步抽离:

为了让每个不同的组件都可以复用一个逻辑,可以使用slot机制并使用传参的方式统一判断用户角色:

<template>
<div>
    <slot v-if='roles.indexOf(user.type) !== -1'></slot>
</div>
</template>
<script>
import { useState } from 'vuex';

export default {
    props: {
        roles: {
            type: Array,    //在哪些用户角色时可见
            default() {
                return [];
            }
        }
    },
    computed: {
        ...useState(['user'])   //存储的user信息
    }
}
</script>

这个方法可以通过slot机制对任何传入组件并通过传入的参数统一判断是否需要显示,但此方法依然优缺点:由于slot不能作为组件根元素,使用该组件会比原来多增加一个DOM元素,并影响原来的样式。

使用render方法劫持渲染内容

<script>
import { useState } from 'vuex';

export default {、
    name: 'role-limit',
    props: {
        roles: {
            type: Array,    //在哪些用户角色时可见
            default() {
                return [];
            }
        }
    },
    computed: {
        ...useState(['user'])   //存储的user信息
    },
    render() {
        const slot = this.$slots.default;
        if (roles.indexOf(this.user.type) !== -1) {
            return slot;
        }
        return null;    //不渲染内容
    }
}
</script>

使用Vue.Component()注册为全局组件后,可在项目中随意通过用户角色控制某个组件的显示:

<template>
<div>
    <role-limit :roles='["admin"]'>
        <button>管理员才可见的按钮</button>
    </role-limit>
    <role-limit :roles='["user"]'>
        <button>普通用户才可见的按钮</button>
    </role-limit>
</div>
</template>