Skip to content

Commit f1e5c1a

Browse files
authored
Merge pull request #1344 from sveltejs/gh-1197
Implement onstate and onupdate hooks
2 parents 8a99ce9 + 3d8c768 commit f1e5c1a

File tree

95 files changed

+1284
-2756
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1284
-2756
lines changed

src/generators/Generator.ts

+8
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,14 @@ export default class Generator {
646646
addDeclaration('ondestroy', templateProperties.ondestroy.value);
647647
}
648648

649+
if (templateProperties.onstate && dom) {
650+
addDeclaration('onstate', templateProperties.onstate.value);
651+
}
652+
653+
if (templateProperties.onupdate && dom) {
654+
addDeclaration('onupdate', templateProperties.onupdate.value);
655+
}
656+
649657
if (templateProperties.preload) {
650658
addDeclaration('preload', templateProperties.preload.value);
651659
}

src/generators/dom/index.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ export default function dom(
171171

172172
initialState.push(`options.data`);
173173

174+
const hasInitHooks = !!(templateProperties.oncreate || templateProperties.onstate || templateProperties.onupdate);
175+
174176
const constructorBody = deindent`
175177
${options.dev && `this._debugName = '${debugName}';`}
176178
${options.dev && !generator.customElement &&
@@ -199,6 +201,9 @@ export default function dom(
199201
${generator.bindingGroups.length &&
200202
`this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`}
201203
204+
${templateProperties.onstate && `this._handlers.state = [%onstate];`}
205+
${templateProperties.onupdate && `this._handlers.update = [%onupdate];`}
206+
202207
${(templateProperties.ondestroy || storeProps.length) && (
203208
`this._handlers.destroy = [${
204209
[templateProperties.ondestroy && `%ondestroy`, storeProps.length && `@removeFromStore`].filter(Boolean).join(', ')
@@ -216,9 +221,17 @@ export default function dom(
216221
`if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`)
217222
}
218223
219-
${templateProperties.oncreate && `var _oncreate = %oncreate.bind(this);`}
224+
${hasInitHooks && deindent`
225+
var self = this;
226+
var _oncreate = function() {
227+
var changed = { ${expectedProperties.map(p => `${p}: 1`).join(', ')} };
228+
${templateProperties.onstate && `%onstate.call(self, { changed: changed, current: self._state });`}
229+
${templateProperties.oncreate && `%oncreate.call(self);`}
230+
self.fire("update", { changed: changed, current: self._state });
231+
};
232+
`}
220233
221-
${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
234+
${(hasInitHooks || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
222235
if (!options.root) {
223236
this._oncreate = [];
224237
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
@@ -230,7 +243,7 @@ export default function dom(
230243
231244
this._fragment = @create_main_fragment(this, this._state);
232245
233-
${(templateProperties.oncreate) && deindent`
246+
${hasInitHooks && deindent`
234247
this.root._oncreate.push(_oncreate);
235248
`}
236249
@@ -253,10 +266,10 @@ export default function dom(
253266
`}
254267
this._mount(options.target, options.anchor);
255268
256-
${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent`
269+
${(generator.hasComponents || generator.hasComplexBindings || hasInitHooks || generator.hasIntroTransitions) && deindent`
257270
${generator.hasComponents && `this._lock = true;`}
258271
${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
259-
${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`}
272+
${(generator.hasComponents || hasInitHooks) && `@callAll(this._oncreate);`}
260273
${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
261274
${generator.hasComponents && `this._lock = false;`}
262275
`}

src/generators/nodes/Component.ts

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ export default class Component extends Node {
275275
hasStoreBindings && 'newStoreState = {}',
276276
].filter(Boolean).join(', ');
277277

278+
// TODO use component.on('state', ...) instead of _bind
278279
componentInitProperties.push(deindent`
279280
_bind: function(changed, childState) {
280281
var ${initialisers};

src/shared/index.js

+16-39
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,19 @@ export function _differsImmutable(a, b) {
3535
return a != a ? b == b : a !== b;
3636
}
3737

38-
export function dispatchObservers(component, group, changed, newState, oldState) {
39-
for (var key in group) {
40-
if (!changed[key]) continue;
41-
42-
var newValue = newState[key];
43-
var oldValue = oldState[key];
44-
45-
var callbacks = group[key];
46-
if (!callbacks) continue;
47-
48-
for (var i = 0; i < callbacks.length; i += 1) {
49-
var callback = callbacks[i];
50-
if (callback.__calling) continue;
51-
52-
callback.__calling = true;
53-
callback.call(component, newValue, oldValue);
54-
callback.__calling = false;
55-
}
56-
}
57-
}
58-
5938
export function fire(eventName, data) {
6039
var handlers =
6140
eventName in this._handlers && this._handlers[eventName].slice();
6241
if (!handlers) return;
6342

6443
for (var i = 0; i < handlers.length; i += 1) {
65-
handlers[i].call(this, data);
44+
var handler = handlers[i];
45+
46+
if (!handler.__calling) {
47+
handler.__calling = true;
48+
handler.call(this, data);
49+
handler.__calling = false;
50+
}
6651
}
6752
}
6853

@@ -71,7 +56,6 @@ export function get(key) {
7156
}
7257

7358
export function init(component, options) {
74-
component._observers = { pre: blankObject(), post: blankObject() };
7559
component._handlers = blankObject();
7660
component._bind = options._bind;
7761

@@ -81,27 +65,20 @@ export function init(component, options) {
8165
}
8266

8367
export function observe(key, callback, options) {
84-
var group = options && options.defer
85-
? this._observers.post
86-
: this._observers.pre;
87-
88-
(group[key] || (group[key] = [])).push(callback);
68+
var fn = callback.bind(this);
8969

9070
if (!options || options.init !== false) {
91-
callback.__calling = true;
92-
callback.call(this, this._state[key]);
93-
callback.__calling = false;
71+
fn(this.get()[key], undefined);
9472
}
9573

96-
return {
97-
cancel: function() {
98-
var index = group[key].indexOf(callback);
99-
if (~index) group[key].splice(index, 1);
100-
}
101-
};
74+
return this.on(options && options.defer ? 'update' : 'state', function(event) {
75+
if (event.changed[key]) fn(event.current[key], event.previous && event.previous[key]);
76+
});
10277
}
10378

10479
export function observeDev(key, callback, options) {
80+
console.warn("this.observe(key, (newValue, oldValue) => {...}) is deprecated. Use\n\n // runs before DOM updates\n this.on('state', ({ changed, current, previous }) => {...});\n\n // runs after DOM updates\n this.on('update', ...);\n\n...or add the observe method from the svelte-extras package");
81+
10582
var c = (key = '' + key).search(/[.[]/);
10683
if (c > -1) {
10784
var message =
@@ -169,9 +146,9 @@ export function _set(newState) {
169146
if (this._bind) this._bind(changed, this._state);
170147

171148
if (this._fragment) {
172-
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
149+
this.fire("state", { changed: changed, current: this._state, previous: oldState });
173150
this._fragment.p(changed, this._state);
174-
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
151+
this.fire("update", { changed: changed, current: this._state, previous: oldState });
175152
}
176153
}
177154

src/validate/js/propValidators/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import actions from './actions';
33
import computed from './computed';
44
import oncreate from './oncreate';
55
import ondestroy from './ondestroy';
6+
import onstate from './onstate';
7+
import onupdate from './onupdate';
68
import onrender from './onrender';
79
import onteardown from './onteardown';
810
import helpers from './helpers';
@@ -24,6 +26,8 @@ export default {
2426
computed,
2527
oncreate,
2628
ondestroy,
29+
onstate,
30+
onupdate,
2731
onrender,
2832
onteardown,
2933
helpers,
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import usesThisOrArguments from '../utils/usesThisOrArguments';
2+
import { Validator } from '../../index';
3+
import { Node } from '../../../interfaces';
4+
5+
export default function onstate(validator: Validator, prop: Node) {
6+
if (prop.value.type === 'ArrowFunctionExpression') {
7+
if (usesThisOrArguments(prop.value.body)) {
8+
validator.error(prop, {
9+
code: `invalid-onstate-property`,
10+
message: `'onstate' should be a function expression, not an arrow function expression`
11+
});
12+
}
13+
}
14+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import usesThisOrArguments from '../utils/usesThisOrArguments';
2+
import { Validator } from '../../index';
3+
import { Node } from '../../../interfaces';
4+
5+
export default function onupdate(validator: Validator, prop: Node) {
6+
if (prop.value.type === 'ArrowFunctionExpression') {
7+
if (usesThisOrArguments(prop.value.body)) {
8+
validator.error(prop, {
9+
code: `invalid-onupdate-property`,
10+
message: `'onupdate' should be a function expression, not an arrow function expression`
11+
});
12+
}
13+
}
14+
}

store.js

+24-17
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
_differsImmutable,
66
dispatchObservers,
77
get,
8-
observe
8+
observe,
9+
on,
10+
fire
911
} from './shared.js';
1012

1113
function Store(state, options) {
1214
this._observers = { pre: blankObject(), post: blankObject() };
13-
this._changeHandlers = [];
15+
this._handlers = {};
1416
this._dependents = [];
1517

1618
this._computed = blankObject();
@@ -105,21 +107,22 @@ assign(Store.prototype, {
105107
this._sortComputedProperties();
106108
},
107109

110+
fire: fire,
111+
108112
get: get,
109113

114+
// TODO remove this method
110115
observe: observe,
111116

112-
onchange: function(callback) {
113-
this._changeHandlers.push(callback);
117+
on: on,
114118

115-
var store = this;
119+
onchange: function(callback) {
120+
// TODO remove this method
121+
console.warn("store.onchange is deprecated in favour of store.on('state', event => {...})");
116122

117-
return {
118-
cancel: function() {
119-
var index = store._changeHandlers.indexOf(callback);
120-
if (~index) store._changeHandlers.splice(index, 1);
121-
}
122-
};
123+
return this.on('state', function(event) {
124+
callback(event.current, event.changed);
125+
});
123126
},
124127

125128
set: function(newState) {
@@ -139,11 +142,11 @@ assign(Store.prototype, {
139142
this._sortedComputedProperties[i].update(this._state, changed);
140143
}
141144

142-
for (var i = 0; i < this._changeHandlers.length; i += 1) {
143-
this._changeHandlers[i](this._state, changed);
144-
}
145-
146-
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
145+
this.fire('state', {
146+
changed: changed,
147+
current: this._state,
148+
previous: oldState
149+
});
147150

148151
var dependents = this._dependents.slice(); // guard against mutations
149152
for (var i = 0; i < dependents.length; i += 1) {
@@ -162,7 +165,11 @@ assign(Store.prototype, {
162165
if (dirty) dependent.component.set(componentState);
163166
}
164167

165-
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
168+
this.fire('update', {
169+
changed: changed,
170+
current: this._state,
171+
previous: oldState
172+
});
166173
}
167174
});
168175

test/js/samples/action/expected-bundle.js

+14-39
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,19 @@ function _differs(a, b) {
3535
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
3636
}
3737

38-
function dispatchObservers(component, group, changed, newState, oldState) {
39-
for (var key in group) {
40-
if (!changed[key]) continue;
41-
42-
var newValue = newState[key];
43-
var oldValue = oldState[key];
44-
45-
var callbacks = group[key];
46-
if (!callbacks) continue;
47-
48-
for (var i = 0; i < callbacks.length; i += 1) {
49-
var callback = callbacks[i];
50-
if (callback.__calling) continue;
51-
52-
callback.__calling = true;
53-
callback.call(component, newValue, oldValue);
54-
callback.__calling = false;
55-
}
56-
}
57-
}
58-
5938
function fire(eventName, data) {
6039
var handlers =
6140
eventName in this._handlers && this._handlers[eventName].slice();
6241
if (!handlers) return;
6342

6443
for (var i = 0; i < handlers.length; i += 1) {
65-
handlers[i].call(this, data);
44+
var handler = handlers[i];
45+
46+
if (!handler.__calling) {
47+
handler.__calling = true;
48+
handler.call(this, data);
49+
handler.__calling = false;
50+
}
6651
}
6752
}
6853

@@ -71,7 +56,6 @@ function get(key) {
7156
}
7257

7358
function init(component, options) {
74-
component._observers = { pre: blankObject(), post: blankObject() };
7559
component._handlers = blankObject();
7660
component._bind = options._bind;
7761

@@ -81,24 +65,15 @@ function init(component, options) {
8165
}
8266

8367
function observe(key, callback, options) {
84-
var group = options && options.defer
85-
? this._observers.post
86-
: this._observers.pre;
87-
88-
(group[key] || (group[key] = [])).push(callback);
68+
var fn = callback.bind(this);
8969

9070
if (!options || options.init !== false) {
91-
callback.__calling = true;
92-
callback.call(this, this._state[key]);
93-
callback.__calling = false;
71+
fn(this.get()[key], undefined);
9472
}
9573

96-
return {
97-
cancel: function() {
98-
var index = group[key].indexOf(callback);
99-
if (~index) group[key].splice(index, 1);
100-
}
101-
};
74+
return this.on(options && options.defer ? 'update' : 'state', function(event) {
75+
if (event.changed[key]) fn(event.current[key], event.previous && event.previous[key]);
76+
});
10277
}
10378

10479
function on(eventName, handler) {
@@ -140,9 +115,9 @@ function _set(newState) {
140115
if (this._bind) this._bind(changed, this._state);
141116

142117
if (this._fragment) {
143-
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
118+
this.fire("state", { changed: changed, current: this._state, previous: oldState });
144119
this._fragment.p(changed, this._state);
145-
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
120+
this.fire("update", { changed: changed, current: this._state, previous: oldState });
146121
}
147122
}
148123

0 commit comments

Comments
 (0)