Skip to content

Commit 0488813

Browse files
committed
refactor: rewrite tree code again to reach the smallest bundle as possible
1 parent 6c7e005 commit 0488813

File tree

5 files changed

+221
-123
lines changed

5 files changed

+221
-123
lines changed

config/enzyme.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable @typescript-eslint/no-require-imports */
1+
/* eslint-disable @typescript-eslint/no-require-imports,@typescript-eslint/no-unsafe-call */
22
const Enzyme = require('enzyme');
33
const Adapter = require('enzyme-adapter-react-16');
44

src/FixedSizeTree.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
/* eslint-disable react/no-unused-state */
2-
import React from 'react';
1+
import React, {ReactNode} from 'react';
32
import {FixedSizeList, FixedSizeListProps} from 'react-window';
43
import Tree, {
4+
createTreeComputer,
55
NodeComponentProps,
66
NodeData,
77
NodeRecord,
88
TreeProps,
99
TreeState,
1010
UpdateOptions,
1111
} from './Tree';
12+
import {
13+
createRecord,
14+
shouldUpdateRecords,
15+
updateRecord,
16+
updateRecordOnWalk,
17+
} from './utils';
1218

1319
export type FixedSizeNodeData = NodeData;
1420

@@ -33,6 +39,20 @@ export type FixedSizeTreeState<T extends FixedSizeNodeData> = TreeState<
3339
T
3440
>;
3541

42+
const computeTree = createTreeComputer<
43+
FixedSizeNodeComponentProps<FixedSizeNodeData>,
44+
FixedSizeNodeRecord<FixedSizeNodeData>,
45+
FixedSizeUpdateOptions,
46+
FixedSizeNodeData,
47+
FixedSizeTreeProps<FixedSizeNodeData>,
48+
FixedSizeTreeState<FixedSizeNodeData>
49+
>({
50+
createRecord,
51+
shouldUpdateRecords,
52+
updateRecord,
53+
updateRecordOnWalk,
54+
});
55+
3656
export class FixedSizeTree<T extends FixedSizeNodeData = NodeData> extends Tree<
3757
FixedSizeNodeComponentProps<T>,
3858
FixedSizeNodeRecord<T>,
@@ -42,7 +62,16 @@ export class FixedSizeTree<T extends FixedSizeNodeData = NodeData> extends Tree<
4262
FixedSizeTreeState<T>,
4363
FixedSizeList
4464
> {
45-
public render(): React.ReactNode {
65+
public constructor(props: FixedSizeTreeProps<T>, context: any) {
66+
super(props, context);
67+
68+
this.state = {
69+
...this.state,
70+
computeTree,
71+
};
72+
}
73+
74+
public render(): ReactNode {
4675
const {children, treeWalker, rowComponent, ...rest} = this.props;
4776

4877
return (
@@ -56,11 +85,4 @@ export class FixedSizeTree<T extends FixedSizeNodeData = NodeData> extends Tree<
5685
</FixedSizeList>
5786
);
5887
}
59-
60-
// eslint-disable-next-line class-methods-use-this
61-
protected constructState(
62-
state: FixedSizeTreeState<T>,
63-
): FixedSizeTreeState<T> {
64-
return state;
65-
}
6688
}

src/Tree.tsx

Lines changed: 91 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable react/no-unused-state,@typescript-eslint/consistent-type-assertions */
12
import React, {
23
ComponentType,
34
PropsWithChildren,
@@ -87,8 +88,15 @@ export type TreeState<
8788
TData extends NodeData
8889
> = Readonly<{
8990
component: ComponentType<TNodeComponentProps>;
90-
methods: OverridableMethods;
9191
order?: ReadonlyArray<string | symbol>;
92+
computeTree: TreeComputer<
93+
TNodeComponentProps,
94+
TNodeRecord,
95+
TUpdateOptions,
96+
TData,
97+
any,
98+
any
99+
>;
92100
records: Readonly<Record<string, TNodeRecord | undefined>>;
93101
treeData?: any;
94102
recomputeTree: (options?: TUpdateOptions) => Promise<void>;
@@ -126,17 +134,81 @@ export const Row = <TData extends NodeData>({
126134
/>
127135
);
128136

129-
const computeTree = (
130-
{treeWalker}: TreeProps<any, any>,
131-
state: TreeState<any, NodeRecord<any>, any, any>,
132-
options: UpdateOptions = {},
137+
export type TreeCreatorOptions<
138+
TNodeComponentProps extends NodeComponentProps<TData>,
139+
TNodeRecord extends NodeRecord<TData>,
140+
TUpdateOptions extends UpdateOptions,
141+
TData extends NodeData,
142+
TState extends TreeState<
143+
TNodeComponentProps,
144+
TNodeRecord,
145+
TUpdateOptions,
146+
TData
147+
>
148+
> = Readonly<{
149+
createRecord: (data: TData, state: TState) => TNodeRecord;
150+
shouldUpdateRecords: (options: TUpdateOptions) => boolean;
151+
updateRecord: (
152+
record: TNodeRecord,
153+
recordId: string | symbol,
154+
options: TUpdateOptions,
155+
) => void;
156+
updateRecordOnWalk: (record: TNodeRecord, options: TUpdateOptions) => void;
157+
}>;
158+
159+
export type TreeComputer<
160+
TNodeComponentProps extends NodeComponentProps<TData>,
161+
TNodeRecord extends NodeRecord<TData>,
162+
TUpdateOptions extends UpdateOptions,
163+
TData extends NodeData,
164+
TProps extends TreeProps<TNodeComponentProps, TData>,
165+
TState extends TreeState<
166+
TNodeComponentProps,
167+
TNodeRecord,
168+
TUpdateOptions,
169+
TData
170+
>
171+
> = (
172+
props: TProps,
173+
state: TState,
174+
options?: TUpdateOptions,
175+
) => Pick<TState, 'order' | 'records'>;
176+
177+
export const createTreeComputer = <
178+
TNodeComponentProps extends NodeComponentProps<TData>,
179+
TNodeRecord extends NodeRecord<TData>,
180+
TUpdateOptions extends UpdateOptions,
181+
TData extends NodeData,
182+
TProps extends TreeProps<TNodeComponentProps, TData>,
183+
TState extends TreeState<
184+
TNodeComponentProps,
185+
TNodeRecord,
186+
TUpdateOptions,
187+
TData
188+
>
189+
>({
190+
createRecord,
191+
shouldUpdateRecords,
192+
updateRecord,
193+
updateRecordOnWalk,
194+
}: TreeCreatorOptions<
195+
TNodeComponentProps,
196+
TNodeRecord,
197+
TUpdateOptions,
198+
TData,
199+
TState
200+
>): TreeComputer<
201+
TNodeComponentProps,
202+
TNodeRecord,
203+
TUpdateOptions,
204+
TData,
205+
TProps,
206+
TState
207+
> => (
208+
{treeWalker},
209+
state,
210+
options = {} as TUpdateOptions,
133211
): Pick<TreeState<any, any, any, any>, 'order' | 'records'> => {
134-
const {
135-
constructRecord,
136-
shouldUpdateRecords,
137-
updateRecord,
138-
updateRecordDuringTreeWalk,
139-
} = state.methods;
140212
const order: Array<string | symbol> = [];
141213
const records = {...state.records};
142214
const iter = treeWalker(options.refreshNodes ?? false);
@@ -170,10 +242,10 @@ const computeTree = (
170242
const record = records[id as string];
171243

172244
if (!record) {
173-
records[id as string] = constructRecord(value, state);
245+
records[id as string] = createRecord(value, state);
174246
} else {
175247
record.data = value;
176-
updateRecordDuringTreeWalk(record, options);
248+
updateRecordOnWalk(record, options);
177249
}
178250
}
179251

@@ -192,7 +264,7 @@ const computeTree = (
192264
};
193265
};
194266

195-
abstract class Tree<
267+
class Tree<
196268
TNodeComponentProps extends NodeComponentProps<TData>,
197269
TNodeRecord extends NodeRecord<TData>,
198270
TUpdateOptions extends UpdateOptions,
@@ -215,7 +287,7 @@ abstract class Tree<
215287
state: TreeState<any, any, any, any>,
216288
): Partial<TreeState<any, any, any, any>> {
217289
const {children: component, itemData: treeData, treeWalker} = props;
218-
const {treeWalker: oldTreeWalker, order} = state;
290+
const {computeTree, order, treeWalker: oldTreeWalker} = state;
219291

220292
return {
221293
component,
@@ -226,68 +298,23 @@ abstract class Tree<
226298
};
227299
}
228300

229-
protected static constructRecord(
230-
data: NodeData,
231-
{recomputeTree}: TreeState<any, any, any, any>,
232-
): NodeRecord<NodeData> {
233-
const record = {
234-
data,
235-
isOpen: data.isOpenByDefault,
236-
async toggle(): Promise<void> {
237-
record.isOpen = !record.isOpen;
238-
await recomputeTree({refreshNodes: record.isOpen});
239-
},
240-
};
241-
242-
return record;
243-
}
244-
245-
protected static shouldUpdateRecords({
246-
opennessState,
247-
useDefaultOpenness = false,
248-
}: UpdateOptions): boolean {
249-
return !!opennessState || useDefaultOpenness;
250-
}
251-
252-
protected static updateRecord(
253-
record: NodeRecord<NodeData>,
254-
recordId: string,
255-
{opennessState, useDefaultOpenness = false}: UpdateOptions,
256-
): void {
257-
record.isOpen = useDefaultOpenness
258-
? record.data.isOpenByDefault
259-
: opennessState?.[recordId] ?? record.isOpen;
260-
}
261-
262-
protected static updateRecordDuringTreeWalk(
263-
record: NodeRecord<NodeData>,
264-
{useDefaultOpenness = false}: UpdateOptions,
265-
): void {
266-
if (useDefaultOpenness) {
267-
record.isOpen = record.data.isOpenByDefault;
268-
}
269-
}
270-
271301
protected readonly list: React.RefObject<TListComponent> = React.createRef();
272302

273-
protected constructor(props: TProps, context: any) {
303+
public constructor(props: TProps, context: any) {
274304
super(props, context);
275305

276-
this.state = this.constructState({
306+
this.state = {
277307
component: props.children,
278-
// Remembering the current constructor to use overridable static methods
279-
// in computeTree function
280-
methods: (this.constructor as unknown) as OverridableMethods,
281308
recomputeTree: this.recomputeTree.bind(this),
282309
records: {},
283310
treeWalker: props.treeWalker,
284-
});
311+
} as TState;
285312
}
286313

287314
public async recomputeTree(options?: TUpdateOptions): Promise<void> {
288315
return new Promise((resolve) => {
289316
this.setState<never>(
290-
(prevState) => computeTree(this.props, prevState, options),
317+
(prevState) => prevState.computeTree(this.props, prevState, options),
291318
resolve,
292319
);
293320
});
@@ -301,12 +328,6 @@ abstract class Tree<
301328
// eslint-disable-next-line react/destructuring-assignment
302329
this.list.current?.scrollToItem(this.state.order!.indexOf(id), align);
303330
}
304-
305-
public abstract render(): React.ReactNode;
306-
307-
protected abstract constructState(
308-
state: TreeState<any, any, any, any>,
309-
): TState;
310331
}
311332

312333
export default Tree;

0 commit comments

Comments
 (0)