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