需求来源于一个管理系统的权限管控模块。
需求描述:进入页面,触发方法,从服务端获取当前用户在当前页面下的操作权限,生成对应的操作按钮。
正常情况,接到这种需求,都会想到使用子组件获取操作权限,v-for
遍历生成按钮,父组件调用,如下:
-
子组件
mybutton.vue
<template> <div> <el-button v-for="item in buttonlist" :key="item.id" @click="item.methodName"> {{ item.name }} </el-button> </div> </template> <script> export default { name: 'Mybutton', props: { }, data() { return { /* 模拟数据。 * 正常情况应该是从服务器端获取: * 入参:用户名、当前页面路由 * 考虑安全性的原因,其实这些数据都应该由服务端来获取,因为前端传递的数据是不可信任的 * 出参:有权限的操作按钮列表。 * 含“按钮名称”、“图标”、“类型”、“需要触发的方法名称” */ buttonlist: [ { id: 1, name: '查询', type: 'success', methodName: 'handleQuery' }, { id: 2, name: '添加', type: 'danger', methodName: 'handleAdd' } ] } }, methods: { } } </script>
-
父组件
index.vue
<template> <div class="app-container"> <div> <!-- 调用子组件 --> <my-button /> </div> </div> </template> <script> import MyButton from './components/mybutton' export default { name: 'MyTest', components: { MyButton }, methods: { /* 实际需求中按钮有很多种,不同页面的方法逻辑也有差异,所以不能写在组件中 */ handleQuery() { this.$alert('handleQuery') }, handleAdd() { this.$alert('handleAdd') } } } </script>
但是,这时候,点击按钮,会发现
vue.runtime.esm.js?2b0e:1888 TypeError: handler.apply is not a function
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.invoker (vue.runtime.esm.js?2b0e:2179)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.Vue.$emit (vue.runtime.esm.js?2b0e:3882)
at VueComponent.handleClick (element-ui.common.js?5c96:9417)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at HTMLButtonElement.invoker (vue.runtime.esm.js?2b0e:2179)
at HTMLButtonElement.original._wrapper (vue.runtime.esm.js?2b0e:6911)
报错了!
原因呢?是因为,我们的操作按钮对应的方法名,是从后端获取的,它并不是一个真正意义的方法,而是一个字符串形式的方法名称。
也就是说,这一段代码
<el-button v-for="item in buttonlist" :key="item.id" @click="item.methodName">
{{ item.name }}
</el-button>
在加载了 buttonlist
的数据之后,我们期望它是这样的:
<el-button type="success" @click="handleQuery">查询</el-button>
<el-button type="danger" @click="handleQuery">添加</el-button>
而实际上,它变成了类似这样(并不完全等同,只是强调 item.methodName
是一个字符串类型,而不是方法):
<el-button type="success" @click="'handleQuery'">查询</el-button>
<el-button type="danger" @click="'handleQuery'">添加</el-button>
这种情况该如何处理?子组件已知字符串形式的方法名,又是子组件调用父组件方法,首先想到的应该 this.$emit(methodName)
-
子组件
mybutton.vue
<template> <div> <el-button v-for="item in buttonlist" :key="item.id" @click="_click(item.methodName)"> {{ item.name }} </el-button> </div> </template> <script> export default { name: 'Mybutton', props: { }, data() { return { buttonlist: [ { id: 1, name: '查询', type: 'success', methodName: 'handleQuery' }, { id: 2, name: '添加', type: 'danger', methodName: 'handleAdd' } ] } }, methods: { _click(func) { this.$emit(func) } } } </script>
-
父组件
index.vue
修改调用子组件的部分:<!-- 调用子组件 --> <my-button @handleQuery="handleQuery" @handleAdd="handleAdd"/>
成功实现!
但是,刚开始也说了,这个需求中,操作按钮是有很多的,每个父组件都定义自己的方法也就算了(这是必须的,因为每个父组件中的功能逻辑都有差异),每个组件引用时还要:
<my-button @handleQuery="handleQuery"
@handleAdd="handleAdd"
@handleEdit="handleEdit"
@handleDelete="handleDelete"
@handleExport="handleExport"
......
/>
写十几二十个,看着也挺烦的。
于是,还有下面这种写法:
-
子组件
mybutton.vue
<template> <div> <el-button v-for="item in buttonlist" :key="item.id" @click="_click(item.methodName)"> {{ item.name }} </el-button> </div> </template> <script> export default { name: 'Mybutton', props: { }, data() { return { buttonlist: [ { id: 1, name: '查询', type: 'success', methodName: 'handleQuery' }, { id: 2, name: '添加', type: 'danger', methodName: 'handleAdd' } ] } }, methods: { _click(func) { /* 这里不一定完全是 this.$parent */ /* 需要根据自己当前页面的节点来定,目的是获取到组件的根节点即可 * 如果子组件在很多节点下,有可能就会是 this.$parent.$parent,甚至可能是 this.$parent.$parent * 当然,如果节点很多比较难找到根节点的话,可以使用 ref、$refs 来确定位置,但是这就还需要将 ref 的值传递告知子节点,可以尝试一下 */ this.$parent[func]() } } } </script>
-
父组件
index.vue
<template> <div class="app-container"> <my-button /> </div> </template> <script> import MyButton from './components/mybutton' export default { name: 'MyTest', components: { MyButton }, methods: { handleQuery() { this.$alert('handleQuery') }, handleAdd() { this.$alert('handleAdd') } } } </script>
看上去就简洁了一丢丢~
效果……和上面一样,就不截图了