Skip to content

Commit

Permalink
feat(): add componentShouldUpdate (#1876)
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat authored Sep 20, 2019
1 parent 8df8bf9 commit 457203f
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/compiler/app-core/build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export function getBuildFeatures(cmps: d.ComponentCompilerMeta[]) {
const f: d.BuildFeatures = {
allRenderFn: cmps.every(c => c.hasRenderFn),
cmpDidLoad: cmps.some(c => c.hasComponentDidLoadFn),
cmpShouldUpdate: cmps.some(c => c.hasComponentShouldUpdateFn),
cmpDidUnload: cmps.some(c => c.hasComponentDidUnloadFn),
cmpDidUpdate: cmps.some(c => c.hasComponentDidUpdateFn),
cmpDidRender: cmps.some(c => c.hasComponentDidRenderFn),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ function parseComponentDeprecated(config: d.Config, compilerCtx: d.CompilerCtx,
hasAttributeChangedCallbackFn: false,
hasComponentWillLoadFn: true,
hasComponentDidLoadFn: true,
hasComponentShouldUpdateFn: true,
hasComponentWillUpdateFn: true,
hasComponentDidUpdateFn: true,
hasComponentWillRenderFn: false,
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/component-build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ export const setComponentBuildConditionals = (cmpMeta: d.ComponentCompilerMeta)
cmpMeta.hasStyle = true;
cmpMeta.hasMode = cmpMeta.styles.some(s => s.modeName !== DEFAULT_STYLE_MODE);
}
cmpMeta.hasLifecycle = (cmpMeta.hasComponentWillLoadFn || cmpMeta.hasComponentDidLoadFn || cmpMeta.hasComponentWillUpdateFn || cmpMeta.hasComponentDidUpdateFn || cmpMeta.hasComponentWillRenderFn || cmpMeta.hasComponentDidRenderFn);
cmpMeta.hasLifecycle = (cmpMeta.hasComponentWillLoadFn || cmpMeta.hasComponentDidLoadFn || cmpMeta.hasComponentShouldUpdateFn || cmpMeta.hasComponentWillUpdateFn || cmpMeta.hasComponentDidUpdateFn || cmpMeta.hasComponentWillRenderFn || cmpMeta.hasComponentDidRenderFn);
cmpMeta.isPlain = !cmpMeta.hasMember && !cmpMeta.hasStyle && !cmpMeta.hasLifecycle && !cmpMeta.hasListener && !cmpMeta.hasVdomRender;
};
1 change: 1 addition & 0 deletions src/compiler/transformers/static-to-meta/class-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const parseClassMethods = (cmpNode: ts.ClassDeclaration, cmpMeta: d.Compo
cmpMeta.hasComponentWillRenderFn = classMethods.some(m => isMethod(m, 'componentWillRender'));
cmpMeta.hasComponentDidRenderFn = classMethods.some(m => isMethod(m, 'componentDidRender'));
cmpMeta.hasComponentDidLoadFn = classMethods.some(m => isMethod(m, 'componentDidLoad'));
cmpMeta.hasComponentShouldUpdateFn = classMethods.some(m => isMethod(m, 'componentShouldUpdate'));
cmpMeta.hasComponentDidUpdateFn = classMethods.some(m => isMethod(m, 'componentDidUpdate'));
cmpMeta.hasComponentDidUnloadFn = classMethods.some(m => isMethod(m, 'componentDidUnload'));
cmpMeta.hasLifecycle = (cmpMeta.hasComponentWillLoadFn || cmpMeta.hasComponentDidLoadFn || cmpMeta.hasComponentWillUpdateFn || cmpMeta.hasComponentDidUpdateFn);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/transformers/static-to-meta/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const parseStaticComponentMeta = (config: d.Config, compilerCtx: d.Compil
hasAttributeChangedCallbackFn: false,
hasComponentWillLoadFn: false,
hasComponentDidLoadFn: false,
hasComponentShouldUpdateFn: false,
hasComponentWillUpdateFn: false,
hasComponentDidUpdateFn: false,
hasComponentWillRenderFn: false,
Expand Down
1 change: 1 addition & 0 deletions src/declarations/build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface BuildFeatures {
// lifecycle events
lifecycle: boolean;
cmpDidLoad: boolean;
cmpShouldUpdate: boolean;
cmpWillLoad: boolean;
cmpDidUpdate: boolean;
cmpWillUpdate: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/declarations/component-compiler-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ComponentCompilerFeatures {
hasAttributeChangedCallbackFn: boolean;
hasComponentWillLoadFn: boolean;
hasComponentDidLoadFn: boolean;
hasComponentShouldUpdateFn: boolean;
hasComponentWillUpdateFn: boolean;
hasComponentDidUpdateFn: boolean;
hasComponentWillRenderFn: boolean;
Expand Down
11 changes: 8 additions & 3 deletions src/runtime/set-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe
const elm = BUILD.lazyLoad ? hostRef.$hostElement$ : ref as d.HostElement;
const oldVal = hostRef.$instanceValues$.get(propName);
const flags = hostRef.$flags$;
const instance = BUILD.lazyLoad ? hostRef.$lazyInstance$ : elm as any;
newVal = parsePropertyValue(newVal, cmpMeta.$members$[propName][0]);

if (newVal !== oldVal && (!BUILD.lazyLoad || !(flags & HOST_FLAGS.isConstructingInstance) || oldVal === undefined)) {
// gadzooks! the property's value has changed!!
// set our new value!
hostRef.$instanceValues$.set(propName, newVal);

if (!BUILD.lazyLoad || hostRef.$lazyInstance$) {
if (!BUILD.lazyLoad || instance) {
// get an array of method names of watch functions to call
if (BUILD.watchCallback && cmpMeta.$watchers$ && flags & HOST_FLAGS.isWatchReady) {
const watchMethods = cmpMeta.$watchers$[propName];
Expand All @@ -32,8 +33,7 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe
watchMethods.forEach(watchMethodName => {
try {
// fire off each of the watch methods that are watching this property
(BUILD.lazyLoad ? hostRef.$lazyInstance$ : elm as any)[watchMethodName].call(
(BUILD.lazyLoad ? hostRef.$lazyInstance$ : elm as any),
instance[watchMethodName](
newVal,
oldVal,
propName
Expand All @@ -47,6 +47,11 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe
}

if (BUILD.updatable && (flags & (HOST_FLAGS.isActiveRender | HOST_FLAGS.hasRendered | HOST_FLAGS.isQueuedForUpdate)) === HOST_FLAGS.hasRendered) {
if (BUILD.cmpShouldUpdate && instance.componentShouldUpdate) {
if (instance.componentShouldUpdate(newVal, oldVal, propName) === false) {
return;
}
}
// looks like this value actually changed, so we've got work to do!
// but only if we've already rendered, otherwise just chill out
// queue that we need to do an update, but don't worry about queuing
Expand Down
37 changes: 37 additions & 0 deletions src/runtime/test/lifecycle-sync.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,41 @@ describe('lifecycle sync', () => {
);
});

it('implement deep equality', async () => {
@Component({ tag: 'cmp-a'})
class CmpA {
renders = 0;
@Prop() complex: any;

componentShouldUpdate(newValue: any, oldValue: any) {
try {
return JSON.stringify(newValue) !== JSON.stringify(oldValue);
} catch {}
return true;
}

render() {
this.renders++;
}
}

const { root, rootInstance, waitForChanges } = await newSpecPage({
components: [CmpA],
template: () => (
<cmp-a complexObject={[1, 2, 3]}></cmp-a>
)
});

expect(rootInstance.renders).toBe(1);

root.complex = [3, 2, 1];
await waitForChanges();
expect(rootInstance.renders).toBe(2);

// Second does not trigger re-render
root.complex = [3, 2, 1];
await waitForChanges();
expect(rootInstance.renders).toBe(2);
});

});
41 changes: 41 additions & 0 deletions src/runtime/test/prop.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,45 @@ describe('prop', () => {
expect(root.num).toBe(88);
});

it('only update on even numbers', async () => {
@Component({ tag: 'cmp-a'})
class CmpA {
@Prop() num = 1;

componentShouldUpdate(newValue: number, _: number, propName: string) {
if (propName === 'num') {
return newValue % 2 === 0;
}
return true;
}
render() {
return `${this.num}`;
}
}

const { root, waitForChanges } = await newSpecPage({
components: [CmpA],
html: `<cmp-a></cmp-a>`,
});

expect(root).toEqualHtml(`
<cmp-a>1</cmp-a>
`);

root.num++;
await waitForChanges();
expect(root).toEqualHtml(`
<cmp-a>2</cmp-a>
`);
root.num++;
await waitForChanges();
expect(root).toEqualHtml(`
<cmp-a>2</cmp-a>
`);
root.num++;
await waitForChanges();
expect(root).toEqualHtml(`
<cmp-a>4</cmp-a>
`);
});
});

0 comments on commit 457203f

Please sign in to comment.