Skip to content

Commit

Permalink
fix: tree 组件 修正 data 变更时,视图绑定丢失的问题 (#1976)
Browse files Browse the repository at this point in the history
* fix(tree): 解决 tree 组件指定 keys 属性后,next 版本可能导致 dom 无限刷新的问题

fix #445

* fix(tree): 解决 tree 组件指定 keys 属性后,next 版本可能导致 dom 无限刷新的问题

fix #445

* test(tree): 迁移 keys 校验脚本到组件目录内部

* docs(tree): 完善调试文档

* test(tree): tree组件属性变更示例,用随机时间戳作为属性变更例子

* test(tree): tree 组件单元测试文件命名规范化

* test(tree): 改进测试代码传参方式,完善代码注释

* test(tree): 完善组件单元测试,提供对公共 api 的单元测试代码

* test(tree): tree 组件,完善节点插入方法的单元测试

* refactor(tree): 提取 store, cache 相关方法到 composition api

* refactor(tree): tree 组件 composition api 重构,添加原先的公共方法

* refactor(tree): tree 组件重新实现空数据与类名的生成

* refactor(tree): tree组件提取节点创建方法到 composition api

* refactor(tree): tree 组件 composition api 实现节点呈现

* refactor(tree): tree 组件,迁移主要节点操作逻辑到 composition api

* refactor(tree): tree 组件,完善监听逻辑的重构

* refactor(tree): tree 组件,移除调试用的注释代码

* refactor(tree): tree 组件,完成基本 composition api 重构,细节还有待调试

* refactor(tree): tree 组件重构解决操作区域未渲染的问题

* refactor(tree): td-tree 组件 composition api 重构通过单元测试

* refactor(tree): tree-item 组件重构为 composition api , 实现基础渲染逻辑抽离

* refactor(tree): tree-item 组件初步实现列表形式渲染的 composition api 重构

* chore(tree): treeItem 组件完成 composition api 基本重构

* refactor(tree): 移除嵌套布局代码

* refactor(tree): 解决单元测试时代码类型报错的问题

* refactor(tree): tree 组件动画样式完善

* refactor(tree): 更新依赖的 common 组件

* refactor(tree): tree 组件更新 common 依赖

* test(tree): tree 组件适配 vitest

* test(tree): tree 组件单测改进,移除 done 方法,替换为 promise api

* chore(tree): merge upsteam

* refactor(tree): td-tree 合并 draggable 事件功能

* refactor(tree): 实现节点普通事件的抽离封装

* refactor(tree): 封装事件派发方法

* refactor(tree): tree 组件,进一步拆分逻辑到多个文件,onDrag mixin 变更为 composition api 代码

* refactor(tree): tree item 组件进一步抽离子组件

* refactor(tree): 实现拖动能力的 composition api 基本重构,有待调试

* refactor(tree): 实现取得节点 dom

* refactor(tree): 更新 _common 依赖

* refactor(tree): 实现对接拖动排序逻辑

* refactor(tree): 完善拖动,修复动画与图标偏移

* refactor(tree): 改进上层属性传递方式

* refactor(tree): tree 组件重构,解决过滤结果为空时空列表状态未呈现的问题

* refactor(tree): 更新 src/_common

* fix(tree): 解决依赖缺失的问题

* chore(tree): 更新 src/_common

* chore(tree): 更新 src/_common

* chore(tree): 更新 src/_common

* fix(tree): 修正动态改变 data 时,视图绑定丢失的问题

fix #1966

* test(tree): tree 组件,解决 data change 问题后,空列表测试需做相应改造
  • Loading branch information
TabSpace committed Dec 29, 2022
1 parent 5656cef commit 2f70578
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 15 deletions.
4 changes: 3 additions & 1 deletion src/tree/__tests__/index.test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { mount } from '@vue/test-utils';
import Tree from '@/src/tree/index.ts';
import { delay } from './kit';

describe('Tree:init', () => {
vi.useRealTimers();
describe(':props.data', () => {
it('传递空数据时,展示兜底界面', () => {
it('传递空数据时,展示兜底界面', async () => {
const wrapper = mount({
render() {
return (
Expand All @@ -16,6 +17,7 @@ describe('Tree:init', () => {
);
},
});
await delay(1);
expect(wrapper.find('.tree-empty').exists()).toBe(true);
});

Expand Down
3 changes: 2 additions & 1 deletion src/tree/_example/data.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const data2 = [
children: [
{
value: '1.1',
label: '1.1 custom label',
children: [
{
value: '1.1.1',
Expand Down Expand Up @@ -127,7 +128,7 @@ export default {
this.items = this.items === data1 ? data2 : data1;
},
label(createElement, node) {
return node.value;
return node.label || node.value;
},
},
};
Expand Down
34 changes: 25 additions & 9 deletions src/tree/hooks/useTreeNodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,39 @@ export default function useTreeNodes(props: TypeTreeProps, context: SetupContext

const cacheMap = new Map();

let clearStep = 0;
const clearCacheNodes = () => {
cacheMap.clear();
// 重构为 hooks api 之后发现一个问题
// 立即执行 cacheMap.clear() 之后,renderTreeNodes 执行了 2 次
// 其中第一次在 nodes.value 变更之前执行
// 导致 cacheMap 缓存重新建立,并引发了视图绑定异常
// 因此用 clearStep 方式解决
clearStep = 1;
};

const nodes: Ref<TreeNode[]> = ref([]);
const nodesFilterEmpty = ref(false);
const nodesEmpty = ref(false);
const refresh = () => {
// 渲染为平铺列表
nodes.value = store.getNodes();
};

const renderTreeNodes = (h: CreateElement) => {
let isFilterEmpty = true;
const treeNodeViews = nodes.value.map((node: TreeNode) => {
let treeNodeViews: TypeVNode[] = [];
let isEmpty = true;
const list = nodes.value;
if (clearStep) {
cacheMap.clear();
clearStep = 0;
nodesEmpty.value = !list.some((node: TreeNode) => node.visible);
return treeNodeViews;
}
treeNodeViews = list.map((node: TreeNode) => {
// 如果节点已经存在,则使用缓存节点
let nodeView = cacheMap.get(node.value);
let nodeView: TypeVNode = cacheMap.get(node.value);
if (node.visible) {
// 任意一个节点可视,过滤结果就不是空
isFilterEmpty = false;
isEmpty = false;
// 如果节点未曾创建,则临时创建
if (!nodeView) {
// 初次仅渲染可显示的节点
Expand All @@ -63,12 +77,13 @@ export default function useTreeNodes(props: TypeTreeProps, context: SetupContext
}
return nodeView;
});
nodesFilterEmpty.value = isFilterEmpty;
nodesEmpty.value = isEmpty;

// 更新缓存后,被删除的节点要移除掉,避免内存泄露
nextTick(() => {
cacheMap.forEach((view: TypeVNode, value: string) => {
if (!store.getNode(value)) {
const node = store.getNode(value);
if (!node) {
cacheMap.delete(value);
}
});
Expand All @@ -81,7 +96,8 @@ export default function useTreeNodes(props: TypeTreeProps, context: SetupContext
store.emitter.on('update', refresh);

return {
nodesFilterEmpty,
refresh,
nodesEmpty,
clearCacheNodes,
renderTreeNodes,
};
Expand Down
12 changes: 8 additions & 4 deletions src/tree/td-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default defineComponent({

useDragHandle(props, context, state);
const { setActived, setExpanded, setChecked } = useTreeAction(props, context, state);
const { renderTreeNodes, clearCacheNodes, nodesFilterEmpty } = useTreeNodes(props, context, state);
const { renderTreeNodes, clearCacheNodes, nodesEmpty } = useTreeNodes(props, context, state);

watch(refProps.data, (list) => {
clearCacheNodes();
Expand Down Expand Up @@ -111,7 +111,7 @@ export default defineComponent({
setExpanded,
setChecked,
renderTreeNodes,
nodesFilterEmpty,
nodesEmpty,
};
},
// 在 methods 提供公共方法
Expand Down Expand Up @@ -205,7 +205,7 @@ export default defineComponent({
},
render(h) {
const {
cache, classList, updateStoreConfig, renderTreeNodes, nodesFilterEmpty,
cache, classList, updateStoreConfig, renderTreeNodes, nodesEmpty,
} = this;

updateStoreConfig();
Expand All @@ -219,10 +219,14 @@ export default defineComponent({

// 空数据判定
let emptyNode: TNodeReturnValue = null;
if (treeNodeViews.length <= 0 || nodesFilterEmpty) {
if (nodesEmpty) {
const useLocale = !this.empty && !this.$scopedSlots.empty;
const emptyContent = useLocale ? this.t(this.global.empty) : renderTNodeJSX(this, 'empty');
emptyNode = <div class={`${cname}__empty`}>{emptyContent}</div>;
} else if (treeNodeViews.length <= 0) {
// 数据切换时,有闪现的缓存节点呈现
// 用这个替换内容置空
emptyNode = <div></div>;
}

// 构造列表
Expand Down

0 comments on commit 2f70578

Please sign in to comment.