邮箱收件人组件成长历程(二)(React hooks升级版)

710 阅读4分钟

转载自道招网 博客《邮箱收件人组件(React版)成长历程(二)》

记得自己之前写过一篇《邮箱收件人组件(vue版)成长历程(一)》记得当时里面写到了自己使用的是可编辑div来进行输入的,同时提到当时出于挑战自己和青铜的掘强,想试着换个方案,完全使用可编辑div来实现。。。这个小小的掘强为后续很多功能瓶颈埋下了隐患。。。 file

使用contenteditable的div缺点

具体的隐患是什么的?

  1. 因为方案中采用的点击插入新收件人时实际上就是向数组里面插入一个空收件人对象,为了确保用户在胡乱点击时永远保证只有一个空对象,就需要频繁的做是否有多余空对象的检查。这个会导致数组频繁的重新渲染,鼠标点入进行选中元素变得很困难。
  2. 普通用户、产品和设计师一般会认为这个收件人组件是一个输入框(你见过谁家的输入框里面能展示校验成功与否的图标的),这会导致他们的需求和改版是以input的某些特性来设计的,这会部分场景用contenteditable的div无法实现,比如用户聚焦后改变整个组件背景颜色。

这次赶上项目迁移至React,对这部分也做了设计上的调整,改用主流的input方法来做输入和编辑态。 响应点击部分还是跟之前一样 file

代码结构

file

  1. 全局只有一个input,解决上述频繁去空检查的问题,将input的输入内容和数组中的某个index进行关联,该index位置就是input内容后续的插入位置,index后面的内容自然向后移动一位。需要编辑或插入内容时,移动input至对应的index。
  2. 在整个组件内但是非已存在收件人信息以外的区域(上图中的蓝框区域)点击时,认为是在最后面添加新的收件人,让input直接移动在列表的最后面,它的输入值会关联到当前收件人数组list的最后一个,关联index为list.length。
  3. 点击到某个收件人预览态(图中的绿框)时将其高亮,如果是双击的化则将其置为编辑态(将input移动到当前收件人位置,并将其数据从list中删除,关联input与该收件人index,待输入完成后再插回此index位置);如果是单击它前面(第一个小箭头所指区域),则是将input移动到它前面,关联input与该收件人index,带输入完成后,将新增内容插入该index。
代码片段:

在变更list数据后React会重新渲染,根据上述代码结构,input会重新出现在列表的最后面,我们需要待渲染完毕后通过图中的ref移动input到它需要出现的位置。

// 回车上屏插入数据
  function onInputEnterInsert(data) {
    const result = getAutoCompleteResult(); // 当前是否选中了搜索候选词
    const finalData = result.value ? result : data; // 如果有候选信息,直接插入候选信息,无视输入信息
    insertData(finalData);
    afterDataInserted(state.inputIndex);
  }

插入数据过程

// 在列表中插入数据
  function insertData(data) {
    const dataInfo = {
      ...data,
      _addedTime: Date.now(), // 方便作为key
    };
    if (isLastIndexOfList(state.inputIndex)) { // 增加新的
      list.push(dataInfo);
    } else { // 双击后更新的
      list.splice(state.inputIndex, 0, dataInfo);
    }
    props.onChange(list.slice()); // 变更state触发渲染
    clearAddress();
  }

渲染后调整input位置

// 在当前inputIndex插入数据后的处理流程
  function afterDataInserted(indexBeforeInsert) {
    console.log('sis hook after inserted -> ', indexBeforeInsert, list[indexBeforeInsert], list);
    typeof afterEditHook === 'function' && afterEditHook(indexBeforeInsert, list[indexBeforeInsert], list);
    // 确保数据变更+input显示后再处理
    setTimeout(() => { // 此时重新渲染后输入框已在最后面
      if (!isLastIndexOfList(indexBeforeInsert)) { // 只要不是在最后面插入的,都需要将列表render后的置于最后面的input移动至当前上次锚定节点的前面
        const targetNode = containerRef.current.children[indexBeforeInsert + 1]; // list里面已经新插入了一个,故原锚定节点的index+1
        containerRef.current.insertBefore(inputContainerRef.current.node, targetNode);
        // dom调整后修正index和input的指向
        dispatch({
          inputIndex: indexBeforeInsert + 1,
        });
      }
      doInputFocus(0);
    }, 0);
  }

可以看到上面还加入了部分hook,方便用户在对应周期处理其它逻辑。 之前的Vue版本在产品迭代过程加入了一些新功能

  1. 列表数量上限
  1. 候选列表只有一个时自动选中
  2. 收件人直接拖动以及跨栏拖动

这部分功能在本次React版本也会同步支持,后面单独写一篇讲一下。