问题

正在自定义一个 Ag-Grid 的 CellRenderer,说白了就是表格行的 Action Buttons. 为了给这些按钮绑定事件响应操作,需要获取用户传入的参数。但是我们的自定义控件创建的时候,用户的参数还没有初始化到 Ag-Grid 控件中。所以直接在 setup 中获取,将会得到 undefined.

想到的方法

方案 0:每个值都专门建立一个变量保存

这种适用于小组件。但是侵入式过强,并且让代码重复。直接否决。

方案 1:使用 nextTick

<template>
  <span>
    <n-button v-if="on" @click="on ('onView')" class="mr-1" size="tiny"> 查看 </n-button>
    <n-button v-if="on" @click="on ('onEdit')" class="mr-1" size="tiny"> 编辑 </n-button>
    <n-button v-if="on" @click="on('onDelete')" class="mr-1" type="error" size="tiny"
      > 删除 </n-button
    >
  </span>
</template>
<script lang="ts">
import { defineComponent, nextTick, PropType, ref } from "vue";

export default defineComponent({
  props: ["params"],
  mounted() {
    console.log("mountd", this.$props);
    
  },
  setup(props,  { attrs, slots, emit }) {
    const getEvtFunc = (fname) => {
      const f = props.params.colDef.params[fname];
      console.log(f);
      return f(props.params.data);
    };
    const evtFuncRef = ref(null);
    // still rendering, so wait
    nextTick(() => {
      evtFuncRef.value = getEvtFunc;
    });
    return {
      on: evtFuncRef,
    };
  },
});
</script>

缺点

第一次点击,延迟很大。因为 nextTick 的触发毕竟还是需要时间的。

方案 2:利用 mounted 获取

import { defineComponent, nextTick, PropType, ref } from "vue";

const evtFuncRef = ref(null);

function wrapGetEvtFunc(props) {
  const getEvtFunc = (fname) => {
    const f = props.params.colDef.params[fname];
    console.log(f);
    return f(props.params.data);
  };
  return getEvtFunc
}

export default defineComponent({
  props: ["params"],
  mounted() {
    console.log("mounted", this.$props);
    evtFuncRef.value = wrapGetEvtFunc(this.$props);
  },
  setup(props, { attrs, slots, emit }) {
    return {
      on: evtFuncRef,
    };
  },
});

缺点:代码结构太复杂。依赖关系:

on->evtFuncRef->wrapGetEvtFunc&props->getEvtFunc->colDef.params

并且这种方式直接耦合了 ag-grid 的特有属性 .params.colDef,不便于复用,这是最大的硬伤。

方案 3:使用任务队列

在 setup 创建任务,在 mounted 完成任务

import { defineComponent, nextTick, PropType, ref, ToRef } from "vue";
var taskList: Array<ToRef<any>> = [];

const awaitProp = function (key: string) {
  console.log("to await", key);
  if (taskList[key]) {
    console.log("already exists.");
  } else {
    taskList[key] = ref(null);
  }
  const ret = taskList[key].value;
  console.log("to ret", ret);
  
  return ret;
};

function getObjPropByPath(obj, path) {
  var parts = path.split("."),
    rv,
    index;
  for (rv = obj, index = 0; rv && index < parts.length; ++index) {
    rv = rv[parts[index]];
  }
  return rv;
}

export default defineComponent({
  props: ["params"],
  mounted() {
    for (var key in taskList) {
      console.log("key", key);

      taskList[key].value = getObjPropByPath(this.$props, key);
      console.log("got", taskList[key]);
    }
  },
  setup(props, { attrs, slots, emit }) {
    return {
      on(act: string) {
        return awaitProp("params.on" + act);
      },
    };
  },
});

优点:代码可以复用,使用简单,只需要 awaitProp (nameStr) 即可。

缺点:需要在 mounted 显式地完成任务。

最后我采用了这种方案。

组件总览:

ActionCell.vue:

<template>
  <span>
    <n-button v-if="on('view')" @click="on('view')(rowData)" class="mr-1" size="tiny"
      > 查看 </n-button
    >
    <n-button v-if="on('edit')" @click="on('edit')(rowData)" class="mr-1" size="tiny"
      > 编辑 </n-button
    >
    <n-button
      v-if="on('delete')"
      @click="on('delete')(rowData)"
      class="mr-1"
      type="error"
      size="tiny"
      > 删除 </n-button
    >
  </span>
</template>
<script lang="ts">
import { defineComponent, nextTick, PropType, ref, ToRef } from "vue";
var taskList: Array<ToRef<any>> = [];
var rowData = ref(null)
const awaitProp = function (key: string) {
  console.log("to await", key);
  if (taskList[key]) {
    console.log("already exists.");
  } else {
    taskList[key] = ref(null);
  }
  const ret = taskList[key].value;
  console.log("to ret", ret);
  
  return ret;
};

function getObjPropByPath(obj, path) {
  var parts = path.split("."),
    rv,
    index;
  for (rv = obj, index = 0; rv && index < parts.length; ++index) {
    rv = rv[parts[index]];
  }
  return rv;
}

export default defineComponent({
  props: ["params"],
  mounted() {
    for (var key in taskList) {
      console.log("key", key);
      taskList[key].value = getObjPropByPath(this.$props, key);
      console.log("got", taskList[key]);
    }

    rowData.value = getObjPropByPath(this.$props, "params.data");
  },
  setup(props, { attrs, slots, emit }) {
    return {
      rowData,
      on(act: string) {
        return awaitProp("params.on" + act);
      },
    };
  },
});
</script>

使用:

GameList.vue:

columnDefs: [
		// ...
        {
          headerName: "操作",
          field: "actions",
          cellRendererFramework: "action-cell",
          cellRendererParams: {
            onview: handleViewModel,
            onedit: handleEditModel,
            ondelete: handleDeleteModel,
          },
          pinned: "right",
        },
      ],
async function handleViewModel(rowData) {
  console.log("view", rowData.id);
}
async function handleEditModel(rowData) {
  console.log("edit", rowData.id);
}
// ...