diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 2ffae2ee2f..8ffed33173 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -74,10 +74,9 @@ export default function dom( }); const builder = new CodeBuilder(); + const computationBuilder = new CodeBuilder(); if (computations.length) { - const computationBuilder = new CodeBuilder(); - computations.forEach(({ key, deps }) => { if (generator.readonly.has(key)) { // <:Window> bindings @@ -88,48 +87,18 @@ export default function dom( generator.readonly.add(key); - const condition = `isInitial || ${deps - .map( - dep => - `( '${dep}' in newState && @differs( state.${dep}, oldState.${dep} ) )` - ) - .join(' || ')}`; - const statement = `state.${key} = newState.${key} = @template.computed.${key}( ${deps + const condition = `isInitial || ${deps.map(dep => + `( '${dep}' in changed )` + ).join(' || ')}`; + + const statement = `if ( @differs( ( state.${key} = @template.computed.${key}( ${deps .map(dep => `state.${dep}`) - .join(', ')} );`; + .join(', ')} ) ), oldState.${key} ) ) changed.${key} = true;`; computationBuilder.addConditionalLine(condition, statement); }); - - builder.addBlock(deindent` - function @recompute ( state, newState, oldState, isInitial ) { - ${computationBuilder} - } - `); } - const _set = deindent` - ${options.dev && - deindent` - if ( typeof newState !== 'object' ) { - throw new Error( 'Component .set was called without an object of data key-values to update.' ); - } - - ${Array.from(generator.readonly).map( - prop => - `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` - )} - `} - - var oldState = this._state; - this._state = @assign( {}, oldState, newState ); - ${computations.length && - `@recompute( this._state, newState, oldState, false )`} - @dispatchObservers( this, this._observers.pre, newState, oldState ); - ${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`} - @dispatchObservers( this, this._observers.post, newState, oldState ); - `; - if (hasJs) { builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`); } @@ -174,7 +143,7 @@ export default function dom( ? `@proto ` : deindent` { - ${['destroy', 'get', 'fire', 'observe', 'on', 'set', 'teardown'] + ${['destroy', 'get', 'fire', 'observe', 'on', 'set', '_set', 'teardown'] .map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`) .join(',\n')} }`; @@ -190,7 +159,7 @@ export default function dom( ? `@assign( @template.data(), options.data )` : `options.data || {}`}; ${generator.metaBindings} - ${computations.length && `@recompute( this._state, this._state, {}, true );`} + ${computations.length && `this._recompute( {}, this._state, {}, true );`} ${options.dev && Array.from(generator.expectedProperties).map( prop => @@ -259,9 +228,20 @@ export default function dom( @assign( ${prototypeBase}, ${proto}); - ${name}.prototype._set = function _set ( newState ) { - ${_set} - }; + ${options.dev && deindent` + ${name}.prototype._checkReadOnly = function _checkReadOnly ( newState ) { + ${Array.from(generator.readonly).map( + prop => + `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` + )} + }; + `} + + ${computations.length ? deindent` + ${name}.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { + ${computationBuilder} + } + ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} ${templateProperties.setup && `@template.setup( ${name} );`} `); diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 1811bc762f..af460756da 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -462,7 +462,7 @@ export default function preprocess( generator.blocks.push(block); preprocessChildren(generator, block, state, node, false, [], true, null); - block.hasUpdateMethod = block.dependencies.size > 0; + block.hasUpdateMethod = true; return { block, state }; } diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 3ef015a9fd..7be3a541d9 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -13,7 +13,7 @@ export default function visitMustacheTag( const name = node._state.name; const value = block.getUniqueName(`${name}_value`); - const { snippet } = block.contextualise(node.expression); + const { dependencies, snippet } = block.contextualise(node.expression); block.addVariable(value); block.addElement( @@ -26,9 +26,13 @@ export default function visitMustacheTag( true ); - block.builders.update.addBlock(deindent` - if ( ${value} !== ( ${value} = ${snippet} ) ) { - ${name}.data = ${value}; - } - `); + if (dependencies.length) { + const changedCheck = dependencies.map(dependency => `'${dependency}' in changed`).join(' || '); + + block.builders.update.addBlock(deindent` + if ( ( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} ) ) { + ${name}.data = ${value}; + } + `); + } } diff --git a/src/shared/index.js b/src/shared/index.js index f198a4ef95..67360fc025 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -26,25 +26,23 @@ export function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -export function dispatchObservers(component, group, newState, oldState) { +export function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -133,6 +131,34 @@ export function set(newState) { this._root._lock = false; } +export function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, newState, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, newState, oldState); +} + +export function _setDev(newState) { + if (typeof newState !== 'object') { + throw new Error( + 'Component .set was called without an object of data key-values to update.' + ); + } + + this._checkReadOnly(newState); + _set.call(this, newState); +} + export function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -144,7 +170,9 @@ export var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; export var protoDev = { @@ -154,5 +182,7 @@ export var protoDev = { observe: observeDev, on: onDev, set: set, - teardown: destroyDev + teardown: destroyDev, + _recompute: noop, + _set: _setDev }; diff --git a/test/runtime/samples/flush-before-bindings/Nested.html b/test/runtime/samples/flush-before-bindings/Nested.html index 9b5a838cb4..d4071beee9 100644 --- a/test/runtime/samples/flush-before-bindings/Nested.html +++ b/test/runtime/samples/flush-before-bindings/Nested.html @@ -1,7 +1,7 @@ {{#each things as thing}} {{thing}} ({{visibilityMap[thing]}}) - + {{/each}}
{{thing}} ({{visibilityMap[thing]}})