昨天使用FineUIGrid写了一个购物车,主要是改变数量和单价计算总价的功能,纯前端。在实现时遇到了坑,解决后使用了闭包的结构优化,这里推演下应用的过程

1. 代码概览

首先购物车需要两个列,数量和单价,可以编辑,这里使用了 RendererFunction 进行绘制,绘制函数就是返回个 input 标签
购物车的项需要新增和删除,这里对应两个方法 adddataremovedata,在修改数据后需要计算合计价格:updateTotal ,所以有代码如下,这里抽象一下,细节不重要

/**绘制输入数量*/
const renderNumber = function(v, { rowId, rowValue }, m, g){
     return `<input  id="Number_${rowId}" value="${v}" class="number" style="width:98%;" type="text">`;
}
/**
 * 新增方法
 * @param {[MsINVMB]} values 新增的数据数组
 */
function adddata(values){
    F.ui.Grid1.addNewRecords(data, true);
    //更新总价
    updateTotal();
}
/**
 * 删除方法
 * @param {ID} rowId 删除的ID
 */
function removedata(rowId){
    F.ui.Grid1.deleteRow(rowId, true);
    //更新总价
    updateTotal();
}
/**
 * 更新总价
 */
function updateTotal(){
    //计算总价
    let res = GetSummary();
    //更新总价
    F.ui.Grid1.setSummaryData(res);
}

2. 问题

熟悉FineUI前端的能感觉到坑来了,调用过 addNewRecordsdeleteRow 的同学都知道,这两个方法会重绘Grid,即触发 renderNumber 函数,造成输入的值会被重置,比如我在某行的数量输入了2,然后新增了一行,执行 addNewRecords ,然后输入的2没了,成默认项了,这时总价也不对了;这里不讨论为什么会触发绘制,对外可能是逆天的,但是对内是自洽的;
最直接的解决方法就是在 新增 或 删除 行之前先得到录入的值,执行新增后再给赋上,然后计算总价;所以多了两个方法 GetInputValSetInputVal ,改造如下;

/**
 * 新增方法
 * @param {[MsINVMB]} values 新增的数据数组
 */
function adddata(values){
    let rawvalue = GetInputVal();
    F.ui.Grid1.addNewRecords(data, true);
    SetInputVal(rawvalue);
    //更新总价
    updateTotal();
}

/**
 * 删除方法
 * @param {ID} rowId 删除的ID
 */
function removedata(rowId){
    let rawvalue = GetInputVal();
    F.ui.Grid1.deleteRow(rowId, true);
    SetInputVal(rawvalue);
    //更新总价
    updateTotal();
}

3. 优化

这时让我别扭的地方来了,我最讨厌割裂,明显 GetInputValSetInputVal 是一对,在调用时中间间隔其他的逻辑,而且他俩本身不参与其他逻辑,SetInputVal 只接收 GetInputVal 返回的值,rawvalue 外露没有意义还有可能被篡改,所以我感觉他俩应该是一个函数,为什么方法我还要写两个,我能不能塞到一个函数里,并且中间还可以跨其他的逻辑;

/**得到值*/
function GetInputVal(){}
/**设置值*/
function SetInputVal(values){}

这个场景下,我想到了使用闭包的方式,有一个函数 RestoreInputVal ,如果要跨过其他逻辑,这个函数返回的应该是 SetInputVal 的实现而不是调用,它应该这么写

/**还原值*/
function RestoreInputVal(){
    const GetInputVal = ()=>...;
    const SetInputVal = (values)=>...;
    let rawvalue = GetInputVal();
    return ()=>{
        SetInputVal(rawvalue);
    };
}

用的时候像这样

/**
 * 新增方法
 * @param {[MsINVMB]} values 新增的数据数组
 */
function adddata(values){
    let restorval = RestoreInputVal();
    F.ui.Grid1.addNewRecords(data, true);
    restorval();
    //更新总价
    updateTotal();
}

/**
 * 删除方法
 * @param {ID} rowId 删除的ID
 */
function removedata(rowId){
    let restorval = RestoreInputVal();
    F.ui.Grid1.deleteRow(rowId, true);
    restorval();
    //更新总价
    updateTotal();
}

这样就实现了 rawvalue 的闭包,以此作为一个典型的应用案例

其实要按照面向对象的思路应该是一个 class ,这里也不考虑继承扩展,所以就不深入了

class InputRestore {

  #rawValue;

  constructor() {
    this.#rawValue = this.#getInputValue(); 
  }

  #getInputValue() {
    // 原逻辑
  }

  #setInputValue(value) {
   // 原逻辑 
  }

  restore() {
    this.#setInputValue(this.#rawValue);
  }

}