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

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

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

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

成功实现!

但是,刚开始也说了,这个需求中,操作按钮是有很多的,每个父组件都定义自己的方法也就算了(这是必须的,因为每个父组件中的功能逻辑都有差异),每个组件引用时还要:

<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[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>
    

看上去就简洁了一丢丢~

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

上一篇:前端打包工具Esbuild--模块化、ESM、esbuild-loader、


下一篇:111111