邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比
如果您发现本文排版有问题,可以先点击下面的链接切换至老版进行查看!!!
邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比
前几天写了收件人组件,它实际就是一个既能输入搜索又能标签形式展示的组件,我称它为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的概率还是比较高的啊。 求指导。。。
- 分类:
- Web前端
更新时间:
相关文章
邮箱收件人组件(vue版)成长历程(一)
前期项目中需要优化原始的收件人、抄送、密送部分,换成更加现代化的样式和用户,当时将这部分抽象成一个组件了,最近的需求是发件人也要使用该组件,鉴于发件人比收件人等需要校验的地方和交互习惯变动点较多,进 阅读更多…
邮箱收件人组件成长历程(二)(React hooks升级版)
记得自己之前写过一篇 《邮箱收件人组件(vue版)成长历程(一)》 记得当时里面写到了自己使用的是可编辑div来进行输入的,同时提到 当时出于挑战自己和青铜的倔强,想试着换个方案,完全使用可编辑di 阅读更多…