关于 Vue render jsx 与 template 的实践总结

4,214 阅读2分钟

场景

iview Table 单元格数据是一个下拉选择框

起初直接 render 了一个经过二次封装的 iview Dropdown 组件,在接入后台数据后发现-等待请求返回数据造成一定时间延缓而不能正常 render 这个组件,所以又考虑采用 template 的写法,结果就出现参数 undefined 的问题:

(最后发现直接在 Table 上绑定 vif ,等数据请求完后再渲染整个 Table 也行)

render jsx 写法

template 写法:

问题分析

进行 template 写法测试过程中发现,父组件方法 handleChangeCharac 不能正常获取到下拉列表的 name 值,但在子组件 handleCommand 中是能获取到的,就产生疑问:为什么 render 中没有出现这个 undefined 问题呢?

于是回到封装组件 xz-dropdown 里面看逻辑,简单点说 Dropdown 提供了一个 @on-click 方法返回当前点击下拉列表的 name 值,我用 handleDropDown 接受这个 name并传给了一个类型为 Functionprop : handleCommand

// xz-dropdown.vue

解决方案

再次理清了一下流程, xz-dropdown 中触发 handleDropDown 方法并传 namehandleCommand,并同时调用 父组件的 handleChangeCharac 方法 , 这时 handleCommand 才能接收 name, 以上调用封装组件的写法是有问题的,父组件中应改写成

父组件调用:

    <template slot-scope="{ row }" slot="roleName">
        <xz-dropdown
            :title="row.roles[0].roleName"
            class="charac-type-btn"
            :transferDom="true"
            :handleCommad="handleChangeCharac"                  // 更改
            :commands="characCommands"
        ></xz-dropdown>
    </template>

结果:

此时父组件中是能接收到 name, 但是我还必须获取到 当前的行数据row, 如果直接在上面基础上写 :handleCommand="handleChangeCharac(row)" 这时是会覆盖 name 结构还是只能操作一个对象而不是两个。

进而应该审视一下 xz-dropdown 中的逻辑代码,看是否能够改善以上的问题,父子组件通信中常用的方式除了 prop外,还有$emit,于是开始逻辑修改:(采用$emit)

xz-dropdown.vue:

// template
    <Dropdown
      trigger="click"
      @on-click="handleOnClick"           // 更改
      :transfer="transferDom"
      @on-clickoutside="handleClickOutside"
    >
// js
methods: {
    
}

父组件:

    <template slot-scope="{ row }" slot="roleName">
        <xz-dropdown
            :title="row.roles[0].roleName"
            class="charac-type-btn"
            :transferDom="true"
            @handle-on-click="handleChangeCharac($event, row)"                // 更改
            :commands="characCommands"
        ></xz-dropdown>
    </template>

点击目标后(顺序是先触发xz-dropdown 中的 handleOnClick 接着触发父组件的 handleChangcharac

这样就能实现我的需求,同时获取到 namerow

回顾原因

导致出现问题的原因:子组件中通过自定义方法传一个参给类型为functionprop,此处才是调用该方法的地方,如果需要增加参数个数,也应该在此处增加。这种情况下父组件触发是能够获取到该参数(这种方式有点类似于子组件$emit出一个参数给父组件),却混淆了$emitprop的使用结果,

如果要坚持用prop的方式并达到需求,就需要更改逻辑为:

xz-dropdown.vue:

// template 
    <Dropdown
      trigger="click"
      @on-click="handleDropDown($event, item)"
      :transfer="transferDom"
      @on-clickoutside="handleClickOutside"
    >
    
// js 
    props: {
        item: {                            // 添加
            type: Object
        },
        handleCommand: {
            type: Function
        }
    },
    methods: {
        handleDropDown(name, item) {          // 添加
            this.handleCommand(name, item)
        }
    }

父组件:

    <template slot-scope="{ row }" slot="roleName">
        <xz-dropdown
            :title="row.roles[0].roleName"
            class="charac-type-btn"
            :transferDom="true"
            :item="row"                                       // 添加
            :handleCommad="handleChangeCharac($event, row)"
            :commands="characCommands"
        ></xz-dropdown>
    </template>

为什么 render的写法能够避免出现这种问题呢? 其实就是这里起了作用

传给handleCommand的一个方法里面获得了 name, 再将 name 传给了 handleChangeCharac的同时,handleChangeCharac也获得了第二个参数row, 这恰恰是render写法的好处

它等价于:

handkeCommand={this.funtionA}

functionA(name) {
    this.handleChangeCharac(name)
}

而普通的template写法是不能同时获得上文的row

总结

使用vue过程中经常出现父子组件数据传递的现象,而prop$emit又是实现这种需求较为常用的方法,此次使用prop达到了一定的$emit效果,但也出现了弊端,混淆了对prop$emit的理解,是不能通过prop传递一个带参的方法给子组件。