vue 动态生成按钮,@click 绑定方法名称,handler.apply is not a function

本文介绍了在Vue中处理权限管控模块的一个常见问题,即子组件如何根据服务端返回的权限信息动态生成并调用父组件的方法。通过实例展示了当方法名作为字符串从后端获取时,如何修正错误并实现子组件调用父组件方法。最后讨论了两种不同的解决方案,一种使用`$emit`,另一种直接通过`$parent`调用,以及它们各自的优缺点。
摘要由CSDN通过智能技术生成

需求来源于一个管理系统的权限管控模块。
需求描述:进入页面,触发方法,从服务端获取当前用户在当前页面下的操作权限,生成对应的操作按钮。

正常情况,接到这种需求,都会想到使用子组件获取操作权限,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>
    

看上去就简洁了一丢丢~

效果……和上面一样,就不截图了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HolaSecurity

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值