Skip to content

Commit 656518c

Browse files
committed
feat: immutable support customize trigger update logic
1 parent 3d3391d commit 656518c

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

src/Immutable.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import * as React from 'react';
33

44
const ImmutableContext = React.createContext<number>(0);
55

6+
export type CompareProps<T extends React.ComponentType<any>> = (
7+
prevProps: Readonly<React.ComponentProps<T>>,
8+
nextProps: Readonly<React.ComponentProps<T>>,
9+
) => boolean;
10+
611
/**
712
* Get render update mark by `makeImmutable` root.
813
* Do not deps on the return value as render times
@@ -16,14 +21,30 @@ export function useImmutableMark() {
1621
* Wrapped Component will be marked as Immutable.
1722
* When Component parent trigger render,
1823
* it will notice children component (use with `responseImmutable`) node that parent has updated.
24+
25+
* @param Component Passed Component
26+
* @param triggerRender Customize trigger `responseImmutable` children re-render logic. Default will always trigger re-render when this component re-render.
1927
*/
20-
export function makeImmutable<T extends React.ComponentType<any>>(Component: T): T {
28+
export function makeImmutable<T extends React.ComponentType<any>>(
29+
Component: T,
30+
shouldTriggerRender?: CompareProps<T>,
31+
): T {
2132
const refAble = supportRef(Component);
2233

2334
const ImmutableComponent = function (props: any, ref: any) {
2435
const refProps = refAble ? { ref } : {};
2536
const renderTimesRef = React.useRef(0);
26-
renderTimesRef.current += 1;
37+
const prevProps = React.useRef(props);
38+
39+
if (
40+
// Always trigger re-render if not provide `notTriggerRender`
41+
!shouldTriggerRender ||
42+
shouldTriggerRender(prevProps.current, props)
43+
) {
44+
renderTimesRef.current += 1;
45+
}
46+
47+
prevProps.current = props;
2748

2849
return (
2950
<ImmutableContext.Provider value={renderTimesRef.current}>
@@ -45,10 +66,7 @@ export function makeImmutable<T extends React.ComponentType<any>>(Component: T):
4566
*/
4667
export function responseImmutable<T extends React.ComponentType<any>>(
4768
Component: T,
48-
propsAreEqual?: (
49-
prevProps: Readonly<React.ComponentProps<T>>,
50-
nextProps: Readonly<React.ComponentProps<T>>,
51-
) => boolean,
69+
propsAreEqual?: CompareProps<T>,
5270
): T {
5371
const refAble = supportRef(Component);
5472

tests/common.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function Value({ id, value }: { id?: string; value: any }) {
2222

2323
return (
2424
<div id={id} className="value">
25-
{str.replace(/^"/, '').replace(/"$/, '')}
25+
{(str || '').replace(/^"/, '').replace(/"$/, '')}
2626
</div>
2727
);
2828
}

tests/immutable.test.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,53 @@ describe('Immutable', () => {
7373
expect(container.querySelector('#raw')!.textContent).toEqual('3');
7474
expect(container.querySelector('#value')!.textContent).toEqual('1');
7575
});
76+
77+
it('customize re-render logic', () => {
78+
const MyRoot = ({
79+
children,
80+
}: {
81+
children?: React.ReactNode;
82+
trigger?: string;
83+
notTrigger?: string;
84+
}) => {
85+
return (
86+
<>
87+
<RenderTimer id="root" />
88+
{children}
89+
</>
90+
);
91+
};
92+
93+
const ImmutableMyRoot = makeImmutable(MyRoot, (prev, next) => {
94+
return prev.trigger !== next.trigger;
95+
});
96+
97+
const { container, rerender } = render(
98+
<ImmutableMyRoot>
99+
<ImmutableRaw />
100+
</ImmutableMyRoot>,
101+
);
102+
expect(container.querySelector('#root').textContent).toEqual('1');
103+
expect(container.querySelector('#raw').textContent).toEqual('1');
104+
105+
// Update `notTrigger`: No Update
106+
rerender(
107+
<ImmutableMyRoot notTrigger="bamboo">
108+
<ImmutableRaw />
109+
</ImmutableMyRoot>,
110+
);
111+
expect(container.querySelector('#root').textContent).toEqual('2');
112+
expect(container.querySelector('#raw').textContent).toEqual('1');
113+
114+
// Update `trigger`: Full Update
115+
rerender(
116+
<ImmutableMyRoot trigger="little" notTrigger="bamboo">
117+
<ImmutableRaw />
118+
</ImmutableMyRoot>,
119+
);
120+
expect(container.querySelector('#root').textContent).toEqual('3');
121+
expect(container.querySelector('#raw').textContent).toEqual('2');
122+
});
76123
});
77124

78125
it('ref-able', () => {

0 commit comments

Comments
 (0)