Skip to content
This repository was archived by the owner on Apr 11, 2025. It is now read-only.

Commit 337168e

Browse files
committed
♻️ refactor: 基于 StoreUpdater 重构受控模式代码
1 parent 89307f0 commit 337168e

File tree

10 files changed

+215
-139
lines changed

10 files changed

+215
-139
lines changed

packages/sortable-list/src/components/Action/index.less

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
@focused-outline-color: #4c9ffe;
2-
31
.Action {
42
display: flex;
53
width: 12px;
@@ -41,10 +39,4 @@
4139
fill: var(--fill, #788491);
4240
}
4341
}
44-
45-
&:focus-visible {
46-
outline: none;
47-
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0),
48-
0 0px 0px 2px @focused-outline-color;
49-
}
5042
}

packages/sortable-list/src/components/BaseItem/index.less

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
@import (reference) '~antd/es/style/themes/default.less';
22

3-
@border-color: #efefef;
4-
@handle-color: rgba(0, 0, 0, 0.25);
53
@box-shadow-border: 0 0 0 calc(1px / var(--scale-x, 1)) hsla(240, 0%, 26%, 0.05);
64
@box-shadow-common: 0 1px calc(3px / var(--scale-x, 1)) hsla(240, 0%, 22%, 0.15);
75
@box-shadow: @box-shadow-border, @box-shadow-common;

packages/sortable-list/src/components/SortableItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* istanbul ignore file */
12
import type { FC } from 'react';
23
import React from 'react';
34

packages/sortable-list/src/container/StoreUpdater.tsx

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
1-
import { useEffect } from 'react';
2-
3-
import { useStore } from '../store';
4-
import type {
5-
StoreUpdaterProps,
6-
SortableItemList,
7-
OnDataChange,
8-
} from '../types';
9-
10-
const useStoreUpdater = <T extends any>(
11-
value: T | undefined,
12-
setStoreState: (param: T) => void,
13-
) => {
14-
useEffect(() => {
15-
if (typeof value !== 'undefined') {
16-
setStoreState(value);
17-
}
18-
}, [value]);
19-
};
1+
import { useStoreUpdater } from '../hooks/useStoreUpdater';
2+
import type { StoreUpdaterProps } from '../types';
203

214
const StoreUpdater = (props: StoreUpdaterProps) => {
22-
const syncOutsideProps = useStore((s) => s.syncOutsideProps);
23-
24-
useStoreUpdater<SortableItemList>(props.data, (data) => {
25-
syncOutsideProps({ data });
26-
});
27-
28-
useStoreUpdater<OnDataChange>(props.onDataChange, (fn) => {
29-
syncOutsideProps({ onDataChange: fn });
30-
});
5+
useStoreUpdater(props);
316

327
return null;
338
};

packages/sortable-list/src/hooks/useSortableList.ts

Lines changed: 0 additions & 97 deletions
This file was deleted.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React, { useCallback, useState } from 'react';
2+
import { act, renderHook } from '@testing-library/react-hooks';
3+
4+
import { useStoreUpdater } from './useStoreUpdater';
5+
import type { SortableItemList } from '../types';
6+
import { createStore, Provider, useStore } from '../store';
7+
8+
const renderOptions = {
9+
wrapper: ({ children }) => (
10+
<Provider createStore={createStore}>{children}</Provider>
11+
),
12+
};
13+
describe('useStoreUpdater', () => {
14+
it('外部控制初始值', () => {
15+
const { result } = renderHook(() => {
16+
useStoreUpdater({ defaultData: [{ id: '123' }] });
17+
18+
return useStore((s) => s.data);
19+
}, renderOptions);
20+
expect(result.current).toEqual([{ id: '123' }]);
21+
});
22+
23+
it('外部设置值', () => {
24+
const { result } = renderHook(() => {
25+
useStoreUpdater({ data: [{ id: '123' }] });
26+
return useStore((s) => s.data);
27+
}, renderOptions);
28+
29+
expect(result.current).toEqual([{ id: '123' }]);
30+
});
31+
32+
it('外部 setValue', () => {
33+
const controlledValue: SortableItemList = [
34+
{ id: '1' },
35+
{ id: '2' },
36+
{ id: '3' },
37+
];
38+
39+
const { result } = renderHook(() => {
40+
const [value, setConfig] = useState<SortableItemList>();
41+
useStoreUpdater({
42+
data: value,
43+
});
44+
45+
const data = useStore((s) => s.data);
46+
return { data, setConfig };
47+
}, renderOptions);
48+
49+
act(() => {
50+
result.current.setConfig(controlledValue);
51+
});
52+
53+
expect(result.current.data).toEqual([
54+
{ id: '1' },
55+
{ id: '2' },
56+
{ id: '3' },
57+
]);
58+
});
59+
60+
it('没有 onChange 时不更改', () => {
61+
const useNoOnChange = () => {
62+
const [data] = useState([{ id: '1' }]);
63+
useStoreUpdater({ data: data });
64+
65+
const store = useStore();
66+
return { store, data };
67+
};
68+
const { result } = renderHook(() => useNoOnChange(), renderOptions);
69+
//
70+
//
71+
act(() => {
72+
result.current.store.addItem({ id: '333' });
73+
});
74+
75+
expect(result.current.data).toEqual([{ id: '1' }]);
76+
});
77+
78+
it('内部修改值 外部 value 更新', () => {
79+
const { result } = renderHook(() => {
80+
const [value, setConfig] = useState<SortableItemList>([
81+
{ id: '1' },
82+
{ id: '2' },
83+
{ id: '3' },
84+
]);
85+
86+
const func = useCallback((data) => {
87+
console.log('xuga', data);
88+
setConfig(data);
89+
}, []);
90+
91+
useStoreUpdater({
92+
data: value,
93+
onDataChange: func,
94+
});
95+
96+
const reorder = useStore((s) => s.reorder);
97+
98+
return { value, reorder };
99+
}, renderOptions);
100+
101+
expect(result.current.value).toEqual([
102+
{ id: '1' },
103+
{ id: '2' },
104+
{ id: '3' },
105+
]);
106+
107+
act(() => {
108+
result.current.reorder(2, 0);
109+
});
110+
111+
expect(result.current.value).toEqual([
112+
{ id: '3' },
113+
{ id: '1' },
114+
{ id: '2' },
115+
]);
116+
117+
act(() => {
118+
result.current.reorder(1, 0);
119+
});
120+
121+
expect(result.current.value).toEqual([
122+
{ id: '1' },
123+
{ id: '3' },
124+
{ id: '2' },
125+
]);
126+
127+
act(() => {
128+
result.current.reorder(1, 1);
129+
});
130+
131+
expect(result.current.value).toEqual([
132+
{ id: '1' },
133+
{ id: '3' },
134+
{ id: '2' },
135+
]);
136+
});
137+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useEffect } from 'react';
2+
import type { StoreUpdaterProps } from '../types';
3+
import { useStore } from '../store';
4+
import shallow from 'zustand/shallow';
5+
6+
export const useStoreUpdater = ({
7+
data,
8+
defaultData,
9+
onDataChange,
10+
}: StoreUpdaterProps) => {
11+
const { syncOnDataChange, syncOutsideData } = useStore(
12+
(s) => ({
13+
syncOutsideData: s.syncOutsideData,
14+
syncOnDataChange: s.syncOnDataChange,
15+
}),
16+
shallow,
17+
);
18+
19+
useEffect(() => {
20+
if (defaultData) {
21+
syncOutsideData(defaultData);
22+
}
23+
}, []);
24+
25+
useEffect(() => {
26+
if (!data) return;
27+
syncOutsideData(data);
28+
}, [data]);
29+
30+
useEffect(() => {
31+
if (!onDataChange) return;
32+
33+
syncOnDataChange(onDataChange);
34+
}, [onDataChange]);
35+
};

packages/sortable-list/src/store/index.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,31 @@ describe('useStore', () => {
7474
{ id: '2' },
7575
]);
7676
});
77+
78+
it('激活/取消激活 item', () => {
79+
const { result } = renderHook(() => useStore());
80+
81+
expect(result.current.activeId).toBeNull();
82+
83+
// 设置
84+
act(() => {
85+
result.current.internalUpdateData([
86+
{ id: '1' },
87+
{ id: '2' },
88+
{ id: '3' },
89+
]);
90+
});
91+
// 激活
92+
act(() => {
93+
result.current.activateItem('2');
94+
});
95+
expect(result.current.activeId).toEqual('2');
96+
97+
// 取消激活
98+
99+
act(() => {
100+
result.current.deactivateItem();
101+
});
102+
expect(result.current.activeId).toEqual(null);
103+
});
77104
});

packages/sortable-list/src/store/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import create from 'zustand';
22
import createContext from 'zustand/context';
33

44
import { arrayMove } from '@dnd-kit/sortable';
5+
import isEqual from 'lodash.isequal';
56
import produce from 'immer';
67
import initialState from './initialState';
78
import type { SortableListStore } from '../types';
8-
99
const createStore = () =>
1010
create<SortableListStore>((set, get) => ({
1111
// 内部值
@@ -26,8 +26,14 @@ const createStore = () =>
2626
}
2727
},
2828

29-
syncOutsideProps: (props) => {
30-
set({ onDataChange: props.onDataChange, data: props.data ?? get().data });
29+
syncOnDataChange: (onDataChange) => {
30+
set({ onDataChange });
31+
},
32+
33+
syncOutsideData: (data) => {
34+
if (isEqual(get().data, data)) return;
35+
36+
set({ data });
3137
},
3238

3339
// 重新排序

0 commit comments

Comments
 (0)