问题
正在自定义一个 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);
}
// ...