You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Docs - prioritize closure components for state (MithrilJS#2292)
* Emphasize closure components in components.md
* Use closure components for all stateful component examples
* Add change-log entry
* Edits and separate sections for closure, class & POJO state
Copy file name to clipboardExpand all lines: docs/change-log.md
+1
Original file line number
Diff line number
Diff line change
@@ -44,6 +44,7 @@
44
44
- render/core: remove the DOM nodes recycling pool ([#2122](https://github.com/MithrilJS/mithril.js/pull/2122))
45
45
- render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes.
46
46
- API: Introduction of `m.prop()` ([#2268](https://github.com/MithrilJS/mithril.js/pull/2268))
47
+
- docs: Emphasize Closure Components for stateful components, use them for all stateful component examples.
Copy file name to clipboardExpand all lines: docs/components.md
+138-39
Original file line number
Diff line number
Diff line change
@@ -103,14 +103,63 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl
103
103
104
104
### Syntactic variants
105
105
106
+
#### Closure components
107
+
108
+
One of the easiest ways to manage state in a component is with a closure. A "closure component" is one that returns an object with a view function and optionally other lifecycle hooks. It has the ability to manage instance state within the body of the outer function.
109
+
110
+
```javascript
111
+
functionClosureComponent(initialVnode) {
112
+
// Each instance of this component has its own instance of `kind`
113
+
var kind ="closure component"
114
+
115
+
return {
116
+
view:function(vnode) {
117
+
returnm("div", "Hello from a "+ kind)
118
+
},
119
+
oncreate:function(vnode) {
120
+
console.log("We've created a "+ kind)
121
+
}
122
+
}
123
+
}
124
+
```
125
+
126
+
The returned object must hold a `view` function, used to get the tree to render.
127
+
128
+
They can be consumed in the same way regular components can.
129
+
130
+
```javascript
131
+
// EXAMPLE: via m.render
132
+
m.render(document.body, m(ClosureComponent))
133
+
134
+
// EXAMPLE: via m.mount
135
+
m.mount(document.body, ClosureComponent)
136
+
137
+
// EXAMPLE: via m.route
138
+
m.route(document.body, "/", {
139
+
"/": ClosureComponent
140
+
})
141
+
142
+
// EXAMPLE: component composition
143
+
functionAnotherClosureComponent() {
144
+
return {
145
+
view:function() {
146
+
returnm("main",
147
+
m(ClosureComponent)
148
+
)
149
+
}
150
+
}
151
+
}
152
+
```
153
+
154
+
If a component does *not* have state then you should opt for the simpler POJO component to avoid the additional overhead and boilerplate of the closure.
155
+
106
156
#### ES6 classes
107
157
108
158
Components can also be written using ES6 class syntax:
109
159
110
160
```javascript
111
161
classES6ClassComponent {
112
162
constructor(vnode) {
113
-
// vnode.state is undefined at this point
114
163
this.kind="ES6 class"
115
164
}
116
165
view() {
@@ -148,65 +197,110 @@ class AnotherES6ClassComponent {
148
197
}
149
198
```
150
199
151
-
#### Closure components
200
+
#### Mixing component kinds
201
+
202
+
Components can be freely mixed. A class component can have closure or POJO components as children, etc...
203
+
204
+
---
152
205
153
-
Functionally minded developers may prefer using the "closure component" syntax:
206
+
### State
207
+
208
+
Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns.
209
+
210
+
Note that unlike many other frameworks, component state does *not* trigger [redraws](autoredraw.md) or DOM updates. Instead, redraws are performed when event handlers fire, when HTTP requests made by [m.request](request.md) complete or when the browser navigates to different routes. Mithril's component state mechanisms simply exist as a convenience for applications.
211
+
212
+
If a state change occurs that is not as a result of any of the above conditions (e.g. after a `setTimeout`), then you can use `m.redraw()` to trigger a redraw manually.
213
+
214
+
#### Closure Component State
215
+
216
+
With a closure component state can simply be maintained by variables that are declared within the outer function. For example:
154
217
155
218
```javascript
156
-
functionclosureComponent(vnode) {
157
-
//vnode.state is undefined at this point
158
-
varkind="closure component"
219
+
functionComponentWithState() {
220
+
//Variables that hold component state
221
+
varcount=0
159
222
160
223
return {
161
224
view:function() {
162
-
returnm("div", "Hello from a "+ kind)
163
-
},
164
-
oncreate:function() {
165
-
console.log("We've created a "+ kind)
225
+
returnm("div",
226
+
m("p", "Count: "+ count),
227
+
m("button", {
228
+
onclick:function() {
229
+
count +=1
230
+
}
231
+
}, "Increment count")
232
+
)
166
233
}
167
234
}
168
235
}
169
236
```
170
237
171
-
The returned object must hold a `view` function, used to get the tree to render.
172
-
173
-
They can be consumed in the same way regular components can.
238
+
Any functions declared within the closure also have access to its state variables.
174
239
175
240
```javascript
176
-
// EXAMPLE: via m.render
177
-
m.render(document.body, m(closureComponent))
241
+
functionComponentWithState() {
242
+
var count =0
178
243
179
-
// EXAMPLE: via m.mount
180
-
m.mount(document.body, closureComponent)
244
+
functionincrement() {
245
+
count +=1
246
+
}
181
247
182
-
// EXAMPLE: via m.route
183
-
m.route(document.body, "/", {
184
-
"/": closureComponent
185
-
})
248
+
functiondecrement() {
249
+
count -=1
250
+
}
186
251
187
-
// EXAMPLE: component composition
188
-
functionanotherClosureComponent() {
189
252
return {
190
253
view:function() {
191
-
returnm("main", [
192
-
m(closureComponent)
193
-
])
254
+
returnm("div",
255
+
m("p", "Count: "+ count),
256
+
m("button", {
257
+
onclick: increment
258
+
}, "Increment"),
259
+
m("button", {
260
+
onclick: decrement
261
+
}, "Decrement")
262
+
)
194
263
}
195
264
}
196
265
}
197
266
```
198
267
199
-
#### Mixing component kinds
268
+
A big advantage of closure components is that we don't need to worry about binding `this` when attaching event handler callbacks. In fact `this` is never used at all and we never have to think about `this` context ambiguities.
200
269
201
-
Components can be freely mixed. A Class component can have closure or POJO components as children, etc...
270
+
#### Class Component State
202
271
203
-
---
272
+
With classes, state can be managed by class instance properties and methods. For example:
204
273
205
-
### State
274
+
```javascript
275
+
classComponentWithState() {
276
+
constructor() {
277
+
this.count=0
278
+
}
279
+
increment() {
280
+
this.count+=1
281
+
}
282
+
decrement() {
283
+
this.count-=1
284
+
}
285
+
view() {
286
+
returnm("div",
287
+
m("p", "Count: "+ count),
288
+
m("button", {
289
+
onclick: () => {this.increment()}
290
+
}, "Increment"),
291
+
m("button", {
292
+
onclick: () => {this.decrement()}
293
+
}, "Decrement")
294
+
)
295
+
}
296
+
}
297
+
```
206
298
207
-
Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns.
299
+
Note that we must wrap the event callbacks in arrow functions so that the `this` context is preserved correctly.
300
+
301
+
#### POJO Component State
208
302
209
-
The state of a component can be accessed three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods.
303
+
For POJO components the state of a component can be accessed three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods.
210
304
211
305
#### At initialization
212
306
@@ -228,10 +322,6 @@ m(ComponentWithInitialState)
228
322
// <div>Initial content</div>
229
323
```
230
324
231
-
For class components, the state is an instance of the class, set right after the constructor is called.
232
-
233
-
For closure components, the state is the object returned by the closure, set right after the closure returns. The state object is mostly redundant for closure components (since variables defined in the closure scope can be used instead).
234
-
235
325
#### Via vnode.state
236
326
237
327
State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component.
@@ -351,9 +441,18 @@ var Auth = require("../models/Auth")
Copy file name to clipboardExpand all lines: docs/lifecycle-methods.md
+34-26
Original file line number
Diff line number
Diff line change
@@ -60,19 +60,24 @@ Like in other hooks, the `this` keyword in the `oninit` callback points to `vnod
60
60
The `oninit` hook is useful for initializing component state based on arguments passed via `vnode.attrs` or `vnode.children`.
61
61
62
62
```javascript
63
-
var ComponentWithState = {
64
-
oninit:function(vnode) {
65
-
this.data=vnode.attrs.data
66
-
},
67
-
view:function() {
68
-
returnm("div", this.data) // displays data from initialization time
63
+
functionComponentWithState() {
64
+
var initialData
65
+
return {
66
+
oninit:function(vnode) {
67
+
initialData =vnode.attrs.data
68
+
},
69
+
view:function(vnode) {
70
+
return [
71
+
// displays data from initialization time:
72
+
m("div", "Initial: "+ initialData),
73
+
// displays current data:
74
+
m("div", "Current: "+vnode.attrs.data)
75
+
]
76
+
}
69
77
}
70
78
}
71
79
72
80
m(ComponentWithState, {data:"Hello"})
73
-
74
-
// Equivalent HTML
75
-
// <div>Hello</div>
76
81
```
77
82
78
83
You should not modify model data synchronously from this method. Since `oninit` makes no guarantees regarding the status of other elements, model changes created from this method may not be reflected in all parts of the UI until the next render cycle.
@@ -110,17 +115,19 @@ The `onupdate(vnode)` hook is called after a DOM element is updated, while attac
110
115
111
116
This hook is only called if the element existed in the previous render cycle. It is not called when an element is created or when it is recycled.
112
117
113
-
Like in other hooks, the `this` keyword in the `onupdate` callback points to `vnode.state`. DOM elements whose vnodes have an `onupdate` hook do not get recycled.
118
+
DOM elements whose vnodes have an `onupdate` hook do not get recycled.
114
119
115
120
The `onupdate` hook is useful for reading layout values that may trigger a repaint, and for dynamically updating UI-affecting state in third party libraries after model data has been changed.
116
121
117
122
```javascript
118
-
var RedrawReporter = {
119
-
count:0,
120
-
onupdate:function(vnode) {
121
-
console.log("Redraws so far: ", ++vnode.state.count)
122
-
},
123
-
view:function() {}
123
+
functionRedrawReporter() {
124
+
var count =0
125
+
return {
126
+
onupdate:function() {
127
+
console.log("Redraws so far: ", ++count)
128
+
},
129
+
view:function() {}
130
+
}
124
131
}
125
132
126
133
m(RedrawReporter, {data:"Hello"})
@@ -163,16 +170,17 @@ Like in other hooks, the `this` keyword in the `onremove` callback points to `vn
163
170
The `onremove` hook is useful for running clean up tasks.
0 commit comments