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

298 阅读3分钟

本文转载自道招网【邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比】 前几天写了收件人组件,它实际就是一个技能输入搜索又能标签形式展示的组件,我称它为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) => { 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, }) }

<!--方案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的概率还是比较高的啊。 求指导。。。