4

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

 3 years ago
source link: https://www.daozhao.com/10011.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

前几天写了收件人组件,它实际就是一个既能输入搜索又能标签形式展示的组件,我称它为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

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

改用方案b后

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK