diff --git a/src/tree/__tests__/api.test.jsx b/src/tree/__tests__/api.test.jsx
index 400a2dedfc..6688c6b18c 100644
--- a/src/tree/__tests__/api.test.jsx
+++ b/src/tree/__tests__/api.test.jsx
@@ -123,7 +123,7 @@ describe('Tree:api', () => {
expect(wrapper.find('[data-value="t1"]').text()).toBe('节点1');
});
- it('可以设置节点属性 checked,触发视图更新', async () => {
+ it('可以设置节点属性 checked, 触发视图更新', async () => {
const data = [
{
value: 't1',
@@ -142,13 +142,37 @@ describe('Tree:api', () => {
],
},
];
+ let changeParams = null;
+ let changeCount = 0;
+ const onChange = (checked, context) => {
+ changeCount += 1;
+ changeParams = {
+ checked,
+ context,
+ };
+ };
const wrapper = mount({
+ data() {
+ return {
+ checked: [],
+ };
+ },
render() {
- return ;
+ return (
+
+ );
},
});
- await delay(10);
+ await delay(1);
const { tree } = wrapper.vm.$refs;
tree.setItem('t1', {
checked: true,
@@ -156,14 +180,24 @@ describe('Tree:api', () => {
tree.setItem('t2', {
checked: true,
});
- await delay(10);
+
+ expect(wrapper.vm.checked.length).toBe(2);
+ expect(wrapper.vm.checked[0]).toBe('t1.1');
+ expect(wrapper.vm.checked[1]).toBe('t2.1');
+ await delay(1);
expect(wrapper.find('[data-value="t1"] .t-checkbox').classes('t-is-checked')).toBe(true);
expect(wrapper.find('[data-value="t1.1"] .t-checkbox').classes('t-is-checked')).toBe(true);
expect(wrapper.find('[data-value="t2"] .t-checkbox').classes('t-is-checked')).toBe(true);
expect(wrapper.find('[data-value="t2.1"] .t-checkbox').classes('t-is-checked')).toBe(true);
+
+ expect(changeCount).toBe(2);
+ expect(changeParams.checked.length).toBe(2);
+ expect(changeParams.checked[0]).toBe('t1.1');
+ expect(changeParams.checked[1]).toBe('t2.1');
+ expect(changeParams.context.node.value).toEqual('t2');
});
- it('可以设置节点属性 expanded,触发视图更新', async () => {
+ it('可以设置节点属性 expanded, 触发视图更新', async () => {
const data = [
{
value: 't1',
@@ -182,13 +216,23 @@ describe('Tree:api', () => {
],
},
];
+
+ let expandParams = null;
+ let expandCount = 0;
+ const onExpand = (expanded, context) => {
+ expandCount += 1;
+ expandParams = {
+ expanded,
+ context,
+ };
+ };
const wrapper = mount({
render() {
- return ;
+ return ;
},
});
- await delay(10);
+ await delay(1);
const { tree } = wrapper.vm.$refs;
tree.setItem('t1', {
expanded: true,
@@ -196,13 +240,80 @@ describe('Tree:api', () => {
tree.setItem('t2', {
expanded: true,
});
- await delay(10);
+ await delay(1);
const t1d1 = wrapper.find('[data-value="t1.1"]');
expect(t1d1.exists()).toBe(true);
expect(t1d1.classes('t-tree__item--visible')).toBe(true);
const t2d1 = wrapper.find('[data-value="t2.1"]');
expect(t2d1.exists()).toBe(true);
expect(t2d1.classes('t-tree__item--visible')).toBe(true);
+
+ expect(expandCount).toBe(2);
+ expect(expandParams.expanded.length).toBe(2);
+ expect(expandParams.expanded[0]).toBe('t1');
+ expect(expandParams.expanded[1]).toBe('t2');
+ expect(expandParams.context.node.value).toEqual('t2');
+ });
+
+ it('可以设置节点属性 actived, 触发视图更新', async () => {
+ const data = [
+ {
+ value: 't1',
+ children: [
+ {
+ value: 't1.1',
+ },
+ ],
+ },
+ {
+ value: 't2',
+ children: [
+ {
+ value: 't2.1',
+ },
+ ],
+ },
+ ];
+
+ let activeParams = null;
+ let activeCount = 0;
+ const onActive = (actived, context) => {
+ activeCount += 1;
+ activeParams = {
+ actived,
+ context,
+ };
+ };
+ const wrapper = mount({
+ render() {
+ return ;
+ },
+ });
+
+ await delay(1);
+ const t1d1 = wrapper.find('[data-value="t1.1"]');
+ const t2d1 = wrapper.find('[data-value="t2.1"]');
+
+ const { tree } = wrapper.vm.$refs;
+ tree.setItem('t1.1', {
+ actived: true,
+ });
+ expect(activeCount).toBe(1);
+ expect(activeParams.actived.length).toBe(1);
+ expect(activeParams.actived[0]).toBe('t1.1');
+ expect(activeParams.context.node.value).toEqual('t1.1');
+ await delay(1);
+ expect(t1d1.classes('t-is-active')).toBe(true);
+
+ tree.setItem('t2.1', {
+ actived: true,
+ });
+ expect(activeCount).toBe(2);
+ expect(activeParams.actived.length).toBe(1);
+ expect(activeParams.actived[0]).toBe('t2.1');
+ expect(activeParams.context.node.value).toEqual('t2.1');
+ await delay(1);
+ expect(t2d1.classes('t-is-active')).toBe(true);
});
});
diff --git a/src/tree/__tests__/event.test.jsx b/src/tree/__tests__/event.test.jsx
index 38aacc158a..2f67deb5e5 100644
--- a/src/tree/__tests__/event.test.jsx
+++ b/src/tree/__tests__/event.test.jsx
@@ -54,6 +54,7 @@ describe('Tree:props:events', () => {
expect(rsActived.length).toBe(1);
expect(rsActived[0]).toBe('t2');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.actived).toBe(true);
}, 300);
it('active 事件可触发', async () => {
@@ -98,6 +99,7 @@ describe('Tree:props:events', () => {
expect(rsActived.length).toBe(1);
expect(rsActived[0]).toBe('t2');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.actived).toBe(true);
}, 300);
});
@@ -161,6 +163,7 @@ describe('Tree:props:events', () => {
expect(rsExpanded.length).toBe(1);
expect(rsExpanded[0]).toBe('t2');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.expanded).toBe(true);
}, 300);
it('expand 事件可触发', async () => {
@@ -222,6 +225,7 @@ describe('Tree:props:events', () => {
expect(rsExpanded.length).toBe(1);
expect(rsExpanded[0]).toBe('t2');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.expanded).toBe(true);
}, 300);
});
@@ -286,6 +290,7 @@ describe('Tree:props:events', () => {
expect(rsValue.length).toBe(1);
expect(rsValue[0]).toBe('t2.1');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.checked).toBe(true);
}, 300);
it('change 事件可触发', async () => {
@@ -348,6 +353,7 @@ describe('Tree:props:events', () => {
expect(rsValue.length).toBe(1);
expect(rsValue[0]).toBe('t2.1');
expect(rsContext.node.value).toBe('t2');
+ expect(rsContext.node.checked).toBe(true);
}, 300);
});
diff --git a/src/tree/_example/checkable.vue b/src/tree/_example/checkable.vue
index a49aa17223..9fa383e7c9 100644
--- a/src/tree/_example/checkable.vue
+++ b/src/tree/_example/checkable.vue
@@ -40,7 +40,7 @@ export default {
valueMode: 'onlyLeaf',
checkable: true,
checkStrictly: false,
- allChecked: ['1.1.1.1'],
+ allChecked: [],
valueOptions: [
{
value: 'onlyLeaf',
@@ -149,19 +149,14 @@ export default {
},
methods: {
onClick(context) {
- console.info('onClick:', context);
+ console.info('onClick context:', context);
const { node } = context;
- console.info(node.value, 'checked:', node.checked);
+ console.info(node.value, 'onClick context.node.checked:', node.checked);
},
onChange(checked, context) {
- console.info('onChange:', checked, context);
+ console.info('onChange checked:', checked, 'context:', context);
const { node } = context;
- console.info(node.value, 'checked:', node.checked);
- },
- propOnChange(checked, context) {
- console.info('propOnChange:', checked, context);
- const { node } = context;
- console.info(node.value, 'checked:', node.checked);
+ console.info(node.value, 'onChange context.node.checked:', node.checked);
},
selectInvert() {
const { tree } = this.$refs;
diff --git a/src/tree/_example/controlled.vue b/src/tree/_example/controlled.vue
index 3383f49a93..19af6726a5 100644
--- a/src/tree/_example/controlled.vue
+++ b/src/tree/_example/controlled.vue
@@ -169,39 +169,60 @@ export default {
this.expanded = ['1', '1.2'];
},
onClick(context) {
- console.info('onClick:', context);
- const { node } = context;
- console.info(node.value, 'checked:', node.checked, 'expanded:', node.expanded, 'actived:', node.actived);
+ console.info('onClick context:', context);
},
onChange(vals, context) {
- console.info('onChange:', vals, context);
- const checked = vals.filter((val) => val !== '2.1');
- console.info('节点 2.1 不允许选中');
+ console.info('onChange value:', vals, 'context:', context);
+ const { node } = context;
+ // onChange 事件发生时,context.node 状态预先发生变更,此时拿到预先变更的节点状态
+ console.info(node.value, 'context.node.checked:', node.checked);
if (this.syncProps) {
+ const checked = vals.filter((val) => {
+ if (val === '2.1') {
+ console.info('节点 2.1 不允许选中');
+ return false;
+ }
+ return true;
+ });
+ // 受控状态下, tree 的 props.value 可被修改为预期的值
+ console.log('before set this.checked, expect checked:', checked);
this.checked = checked;
}
- const { node } = context;
- console.info(node.value, 'checked:', node.checked);
+ // 赋值变更后的选中态之后,nextTick 之后触发视图更新
+ // node.checked 状态发生变更,符合 tree 的 props.value 的取值
+ this.$nextTick(() => {
+ console.info(node.value, 'nextTick context.node.checked:', node.checked);
+ });
},
onActive(vals, context) {
- console.info('onActive:', vals, context);
- const actived = vals.filter((val) => val !== '2.2');
- console.info('节点 2.2 不允许激活', actived);
+ console.info('onActive actived:', vals, 'context:', context);
+ const { node } = context;
+ console.info(node.value, 'context.node.actived:', node.actived);
+ const actived = vals.filter((val) => {
+ if (val === '2.2') {
+ console.info('节点 2.2 不允许激活');
+ return false;
+ }
+ return true;
+ });
if (this.syncProps) {
this.actived = actived;
}
- const { node } = context;
- console.info(node.value, 'actived:', node.actived);
},
onExpand(vals, context) {
- console.info('onExpand:', vals, context);
- const expanded = vals.filter((val) => val !== '2.3');
- console.info('节点 2.3 不允许展开', expanded);
+ console.info('onExpand expanded:', vals, 'context:', context);
+ const { node } = context;
+ console.info(node.value, 'context.node.expanded:', node.expanded);
+ const expanded = vals.filter((val) => {
+ if (val === '2.3') {
+ console.info('节点 2.3 不允许展开');
+ return false;
+ }
+ return true;
+ });
if (this.syncProps) {
this.expanded = expanded;
}
- const { node } = context;
- console.info(node.value, 'expanded:', node.expanded);
},
},
};
diff --git a/src/tree/hooks/useTreeAction.ts b/src/tree/hooks/useTreeAction.ts
index a0a2135423..f8be96cb9f 100644
--- a/src/tree/hooks/useTreeAction.ts
+++ b/src/tree/hooks/useTreeAction.ts
@@ -15,13 +15,12 @@ export default function useTreeAction(state: TypeTreeState) {
const { store } = treeState;
const componentName = usePrefixClass('tree').value;
- const [, setTValue] = state.vmValue;
- const [, setTActived] = state.vmActived;
- const [, setTExpanded] = state.vmExpanded;
+ const [tValue, setTValue] = state.vmValue;
+ const [tActived, setTActived] = state.vmActived;
+ const [tExpanded, setTExpanded] = state.vmExpanded;
const setExpanded = (item: TypeTargetNode, isExpanded: boolean): TreeNodeValue[] => {
const node = getNode(store, item);
- const expanded = node.setExpanded(isExpanded);
const mouseEvent = treeState.mouseEvent as MouseEvent;
const evtCtx: TypeExpandEventContext = {
node: node.getModel(),
@@ -36,7 +35,13 @@ export default function useTreeAction(state: TypeTreeState) {
evtCtx.trigger = 'icon-click';
}
}
+ const expanded = node.setExpanded(isExpanded, {
+ directly: true,
+ });
setTExpanded(expanded, evtCtx);
+ if (evtCtx.trigger !== 'setItem') {
+ store.replaceExpanded(tExpanded.value as TreeNodeValue[]);
+ }
return expanded;
};
@@ -47,7 +52,6 @@ export default function useTreeAction(state: TypeTreeState) {
const setActived = (item: TypeTargetNode, isActived: boolean) => {
const node = getNode(store, item);
- const actived = node.setActived(isActived);
const mouseEvent = treeState.mouseEvent as MouseEvent;
const evtCtx: TypeActiveEventContext = {
node: node.getModel(),
@@ -57,7 +61,13 @@ export default function useTreeAction(state: TypeTreeState) {
if (mouseEvent) {
evtCtx.trigger = 'node-click';
}
+ const actived = node.setActived(isActived, {
+ directly: true,
+ });
setTActived(actived, evtCtx);
+ if (evtCtx.trigger !== 'setItem') {
+ store.replaceActived(tActived.value as TreeNodeValue[]);
+ }
return actived;
};
@@ -68,7 +78,6 @@ export default function useTreeAction(state: TypeTreeState) {
const setChecked = (item: TypeTargetNode, isChecked: boolean, ctx: { e: Event }): TreeNodeValue[] => {
const node = getNode(store, item);
- const checked = node.setChecked(isChecked);
const mouseEvent = ctx?.e as MouseEvent;
const evtCtx: TypeChangeEventContext = {
node: node.getModel(),
@@ -78,7 +87,14 @@ export default function useTreeAction(state: TypeTreeState) {
if (mouseEvent) {
evtCtx.trigger = 'node-click';
}
+ const checked = node.setChecked(isChecked, {
+ directly: true,
+ });
setTValue(checked, evtCtx);
+ // 这是针对受控执行的操作,如果 props.value 未变更,则执行还原操作
+ if (evtCtx.trigger !== 'setItem') {
+ store.replaceChecked(tValue.value as TreeNodeValue[]);
+ }
return checked;
};
diff --git a/src/tree/hooks/useTreeStore.ts b/src/tree/hooks/useTreeStore.ts
index d0447a3df4..64eaa6ffa2 100644
--- a/src/tree/hooks/useTreeStore.ts
+++ b/src/tree/hooks/useTreeStore.ts
@@ -186,25 +186,15 @@ export default function useTreeStore(state: TypeTreeState) {
state.setStore(store);
// 配置属性监听
- watch(refProps.value, (nVal: TreeNodeValue[]) => {
- const previousVal = store.getChecked();
- if (nVal.join() === previousVal?.join()) return;
- store.replaceChecked(nVal);
- });
- watch(refProps.expanded, (nVal: TreeNodeValue[]) => {
- store.replaceExpanded(nVal);
- });
- watch(refProps.actived, (nVal: TreeNodeValue[]) => {
- const previousVal = store.getActived();
- if (nVal.join() === previousVal?.join()) return;
- store.replaceActived(nVal);
- });
+ // tValue 就是 refProps.value
watch(tValue, (nVal: TreeNodeValue[]) => {
store.replaceChecked(nVal);
});
+ // tExpanded 就是 refProps.expanded
watch(tExpanded, (nVal: TreeNodeValue[]) => {
store.replaceExpanded(nVal);
});
+ // tActived 就是 refProps.actived
watch(tActived, (nVal: TreeNodeValue[]) => {
store.replaceActived(nVal);
});
diff --git a/src/tree/tree.tsx b/src/tree/tree.tsx
index 96af345f89..3ad0b7ca07 100644
--- a/src/tree/tree.tsx
+++ b/src/tree/tree.tsx
@@ -99,12 +99,9 @@ export default defineComponent({
const val = spec[name];
delete spec[name];
const methodName = `set${upperFirst(name)}`;
- const setupMethod = node[methodName];
+ const setupMethod = this[methodName];
if (isFunction(setupMethod)) {
- setupMethod.call(node, val, {
- directly: true,
- isAction: false,
- });
+ setupMethod.call(this, node, val);
}
}
});
diff --git a/test/unit/snap/__snapshots__/csr.test.js.snap b/test/unit/snap/__snapshots__/csr.test.js.snap
index 521e976fa2..39e1efb4b7 100644
--- a/test/unit/snap/__snapshots__/csr.test.js.snap
+++ b/test/unit/snap/__snapshots__/csr.test.js.snap
@@ -180935,7 +180935,7 @@ exports[`csr snapshot test > csr test ./src/tree/_example/checkable.vue 1`] = `