道招

邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比

如果您发现本文排版有问题,可以先点击下面的链接切换至老版进行查看!!!

邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比

前几天写了收件人组件,它实际就是一个既能输入搜索又能标签形式展示的组件,我称它为SmartInputSelect(以下简称sis组件),在实现下列需求时遇到了些问题,需求就是想实现多个sis组件的邮箱地址能够相互拖拽,效果类似剪切操作。比如从sis_a中的邮箱地址拖拽到sis_b中,同时需要sis_a中的那个会被移除。

下面是具体的代码实现,每行的具体作用已经有注释了。 将拖拽数组逻辑简单写成了一个hook

// hooks.js
import React from 'react';
// 修改版useReducer
export function useDispatch (initialState) {
  const [state, dispatch] = useReducer((state, action) => {
    return {
      ...state,
      ...action,
    }
  }, initialState);

  const modifyStateWhileDispatch = (payload, value) => {
    if (toString.call(payload) !== '[object Object]') {
      payload = {
        [payload]: value,
      }
    }
    Object.assign(state, payload); // state changed(like vue) but dom not
    dispatch(payload); // dom changed with react
  };
  return [state, modifyStateWhileDispatch];
}

/**
 * 根据指定count使用sis组件
 * @param count
 * @returns {[unknown[], setState]}
 */
export function useSis(count) {
 const [state, dispatch] = useDispatch(Array(count).fill(1).reduce((acc, curr, index) => ({
   ...acc,
   [index]: []
 }), {}));
 // 最终初始化为 `{0: [], 1: [], 3: []} `具体多少个由入参`count`决定

 function updateSis(index, value) {
   dispatch(index, value);
 }

 return [Object.values(state), updateSis];
}
代码结构
import { useDispatch, useSis } from './hooks.js';

const Test = (props) => {
    <!--方案a开始 -->
    const [state, dispatch] = useDispatch({
    targetsArr: [],
    ccTargetsArr: [],
    bccTargetsArr: [],
  });

  const { targetsArr, ccTargetsArr, bccTargetsArr } = state;

  function updateSis(index, value) {
    const index2StringMap = ['targetsArr', 'ccTargetsArr', 'bccTargetsArr'];
    const target = index2StringMap[index];
    dispatch({
      ...state,
      [target]: value,
    })
  }
    <!--方案a结束 -->

    <!--方案b结束 -->
    const [[targetsArr, ccTargetsArr, bccTargetsArr], updateSis] = useSis(3);
    <!--方案b结束 -->

    return ({
    <table>
        <tbody>
            <tr>
              <td>{ window.i18n_data_mail['email.abstract.recipient'] }</td>
              <td>
                <SmartInputSelect id="id4t_sis_targets"
                                  uniqueKey="sis0"
                                  onChange={(payload) => updateSis(0, payload)}
                                  fetchListMethod={fetchAddressList} validateMethod={validateEmail}
                                  list={targetsArr}/>
            </td>
            </tr>
            <tr style={{ display: emailConfig.showCc ? '' : 'none' }}>
              <td>{ window.i18n_data_mail['email.abstract.Cc'] }</td>
              <td>
                <SmartInputSelect id="id4t_sis_cc_targets"
                                  uniqueKey="sis1"
                                  onChange={(payload) => updateSis(1, payload)}
                                  fetchListMethod={fetchAddressList} validateMethod={validateEmail}
                                  list={ccTargetsArr}/>
              </td>
            </tr>
            <tr style={{ display: emailConfig.showBcc ? '' : 'none' }}>
              <td>{ window.i18n_data_mail['email.abstract.Bcc'] }</td>
              <td>
                <SmartInputSelect id="id4t_sis_bcc_targets"
                                  uniqueKey="sis2"
                                  limit={2}
                                  onChange={(payload) => updateSis(2, payload)}
                                  fetchListMethod={fetchAddressList} validateMethod={validateEmail}
                                  list={bccTargetsArr}/>
              </td>
            </tr>
        </tbody>
    </table>
    })
};
export default Test;
drag过程
function onDragStart(e) {
    e.dataTransfer.setData('IM+MailAddress', JSON.stringify({
      index: index, // 该元素在当前sis数组数据中的index
      uniqueKey: uniqueKey, // 当前sis的唯一标识
      data: {
        ...data,
      }
    }));
  }
drop过程
function onDrop(e) {
    if (!allowDrop) {
      return;
    }
    const addressStr = e.dataTransfer.getData('IM+MailAddress');
    if (!addressStr) {
      return;
    }
    e.preventDefault();
    const addressInfo = JSON.parse(addressStr);
    const target = getDropTarget(e.target); // target为空则表示放置至外层容器
    const targetIndex = target ? target.tabIndex : list.length; // 外层容器则让其直接插至末尾
    console.log('sis drag onDrop data -> ', addressStr);
    const newList = list.slice();
    if (addressInfo.uniqueKey === uniqueKey) { // 同一个sis之前的元素移动
      if (targetIndex === addressInfo.index) { // 原位不动
        console.log('sis drag afterDrop1');
        return;
      } else if(addressInfo.index > targetIndex ) { // 前移
        newList.splice(addressInfo.index, 1);
        newList.splice(targetIndex, 0, {
          ...addressInfo.data,
        });
        props.onChange(newList);
      } else { // 后移
        newList.splice(targetIndex, 0, {
          ...addressInfo.data,
        });
        newList.splice(addressInfo.index, 1);
        props.onChange(newList);
      }
    } else { // 不同sis间的元素移动
      newList.splice(targetIndex, 0, {
        ...addressInfo.data,
      });
      props.onChange(newList);
      console.log('sis drag drop external -> ', list.length, JSON.stringify(list.slice()), uniqueKey, ' <== ', addressInfo.uniqueKey);
      // 通知来源external的sis对拖拽元素进行移除
      MailMessageCenter.publish('sism.remove', [{
        key: addressInfo.uniqueKey,
        index: addressInfo.index,
      }]);
      MailMessageCenter.publish('sism.removeOverIndicator', [{
        key: addressInfo.uniqueKey,
      }]);
    }
    removeOverIndicator();
    console.log('sis drag afterDrop2');
  }

上述的props.onChange(newList);即是具体的父组件更新数据过程。

上面的处理过程已经经过Vue版在线上长期稳定运行,自认为是没有什么问题的。

但是这次在用React迁移自测过程中发现有点问题,特别是第一次进行跨栏拖拽时,容易出现数据丢失的情况。

使用方案a

具体可看动图 第一次尝试:初始时是6个元素在三个sis间进行拖拽,最后居然一个都不剩了,全部被“吃”了。。。 第二次尝试:初始时是6个元素在三个sis间进行拖拽,后面被“吃”一个后,一直保持5个。

改用方案b后

具体可看动图 暂未发现问题

其实方案a和方案b从代码上看并没有什么本质上的区别,为什么效果会有不同呢?难道仅仅是因为方案b测试的次数不够多?但是方案a出现bug的概率还是比较高的啊。 求指导。。。

更新时间:
上一篇:wordpress博客提示“Briefly unavailable for scheduled maintenance. Check back in a minute”下一篇:React router用hooks读取routeName、根据routeName跳转

相关文章

邮箱收件人组件(vue版)成长历程(一)

前期项目中需要优化原始的收件人、抄送、密送部分,换成更加现代化的样式和用户,当时将这部分抽象成一个组件了,最近的需求是发件人也要使用该组件,鉴于发件人比收件人等需要校验的地方和交互习惯变动点较多,进 阅读更多…

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

记得自己之前写过一篇 《邮箱收件人组件(vue版)成长历程(一)》 记得当时里面写到了自己使用的是可编辑div来进行输入的,同时提到 当时出于挑战自己和青铜的倔强,想试着换个方案,完全使用可编辑di 阅读更多…

关注道招网公众帐号
道招开发者二群