/
api.html
550 lines (427 loc) · 16.8 KB
/
api.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content=IE=edge>
<meta name=viewport content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>DIO – API</title>
<link rel=stylesheet href=css/stylesheet.css>
<link rel=icon type=image/png href=../imgs/favicon-large.png sizes=32x32>
<link rel=icon type=image/png href=../imgs/favicon-small.png sizes=16x16>
</head>
<body>
<a href=./ title=index class=logo>
<img src=imgs/logo.svg alt=logo>
</a>
<header>
<nav>
<a href=./>Index</a>
<a href=./introduction.html>Introduction</a>
<a href=./api.html>API</a>
<a href=./examples.html>Examples</a>
<a href=https://github.com/thysultan/dio.js>Github</a>
</nav>
</header>
<article>
<h1>API</h1>
<p>
DIO's API surface is small to a benefit, <br>with a close resemblance to React.
</p>
<h1 id=elements>Elements</h1>
<p>Elements are made up of three parts</p>
<ol>
<li>type — type of element that should be rendered.</li>
<li>props — properties associated the element.</li>
<li>children — children contained within the element</li>
</ol>
<pre><code>
const h1 = h('h1', {
class: 'heading'
}, 'Hello World')
</code></pre>
<h2 id=clone>cloneElement</h2>
<p>When the first argument of an element, its type, is a virtual element, the element is cloned with new children replacing old children and props shallow merged.</p>
<pre><code>
const h1 = h('h1', {
class: 'large'
}, 'Hello World')
const h2 = h(h1, {
class: 'small'
}, 'The Fox')
</code></pre>
<h2 id=portals>Portals</h2>
<p>A portal is created when the first argument of an element, its type, is a DOM node. This means that when the element is mounted the children of the portal will be placed in the DOM node specified regardless of what its virtual parent is in the tree of virtual elements.</p>
<p>Given the following:</p>
<pre><code>
const portal = h(document.body, 2, 3)
dio.render(h('div', 1, portal))
</code></pre>
<p>We would find 1 to be in its parent <code>`div`</code> while 2 & 3 would be placed into <code>`document.body`.</code></p>
<h1 id=components>Components</h1>
<p>
Components can be either a <code>class</code> or a <code>function</code>, and though the class syntax can extend <code>dio.Component</code> DIO accepts plain classes alike.
</p>
<h2>Classes</h2>
<pre><code>
class Welcome {
render() {
return 'Welcome';
}
}
</code></pre>
<pre><code>
class Welcome extends dio.Component {
render() {
return 'Welcome'
}
}
</code></pre>
<pre><code>
function Welcome {}
Welcome.prototype = {
render() {
return 'Welcome'
}
}
</code></pre>
<h2>Functions</h2>
<pre><code>
const Welcome = () => {
return 'Welcome'
}
function Welcome () {
return 'Welcome'
}
</code></pre>
<pre><code>
function* Welcome () {
return 'Welcome'
}
</code></pre>
<h1 id=lifecycle>Lifecycle</h1>
<p>
Both <code>function</code> and <code>class</code> components can make use of lifcycles <label for=1></label>.
<span class=note>
With the exception of <code>`getInitialState`</code> that is reserved soley for <code>`class`</code> components; Useful when setting the initial state of a component is a requirement.
</span>
</p>
<p>When required any lifecycle on a <code>`class`</code> based component can call <code>`this.setState`</code> or return a state update object to update the state.</p>
<h2>Data</h2>
<p>
The default return value that <code>`getInitialState`</code> expects is an object representing the initial state of the component; However <code>`getInitialState`</code> also accepts a <code>`Promise`</code> when the data the component requires is network bound <label for=2></label>.
<span class=note>That is to say you can asynchronous-ly retrieve a components initial state.</span>
</p>
<pre><code>
class Welcome {
getInitialState() {}
}
</code></pre>
<h2 id=refs>Refs</h2>
<p>
Refs are either functions of strings that provide access to the underlining DOM node; While <code>string</code>
<label for=3></label>
<span class=note>String refs are only usefull on <code>class</code> based components.</span>
refs can be dynamic, <code>function</code>
refs can also return or call <code>`this.setState`</code> to update the component.</p>
<pre><code>
class Welcome {
retrieveDOM(el) {
console.log(el.getBoundingClientRect())
}
render() {
return h('h1', {
ref: this.retrieveDOM
}, 'Welcome')
}
}
</code></pre>
<h2 id=mounting>Mounting</h2>
<p>Mounting happens in a bottom-up fashion in that the lifecycle method of child components will be invoked before the parent.</p>
<p>Both lifecycle methods <code>`componentWillMount`</code> & <code>`componentDidMount`</code> receive the mounting DOM node as an argument.</p>
<pre><code>
class Welcome {
componentWillMount() {}
}
</code></pre>
<pre><code>
class Welcome {
componentDidMount() {}
}
</code></pre>
<h2 id=updating>Updating</h2>
<p>Certain lifecycles are only invoked in the update phase; When an update is triggerd through <code>`this.setState`</code> or <code>`this.forceUpdate`</code> the <code>`componentWillReceiveProps`</code> lifecycle will not be invoked because new props are not received.</p>
<p>On the other hand if <code>`shouldComponentUpdate`</code> returns <code>false</code> then a component will skip that specific update phase.</p>
<pre><code>
class Welcome {
componentWillReceiveProps() {}
}
</code></pre>
<pre><code>
class Welcome {
shouldComponentUpdate() {}
}
</code></pre>
<pre><code>
class Welcome {
componentWillUpdate() {}
}
</code></pre>
<pre><code>
class Welcome {
componentDidUpdate() {}
}
</code></pre>
<h2 id=unmounting>Umounting</h2>
<p>Unlike mounting Unmounting happens in a top-down fashion, the lifecycle method <code>`componentWillUnmount`</code> is unique in that it is the only lifecycle method that cannot update state because at this phase the component is due to be removed from the DOM.</p>
<p>On the other hand <code>`componentWillUnmount`</code> can return a <code>Promise</code>; When used the unmounting DOM node will be removed once the <code>Promise</code> resolves.</p>
<p>Like <code>componentWillMount</code> & <code>componentDidMount</code>, <code>`componentWillUnmount`</code> also recieves the unmounting DOM node as an argument.</p>
<pre><code>
class Welcome {
componentWillUnmount() {}
}
</code></pre>
<h1 id=setstate>setState</h1>
<p>A component can update its state by invoking <code>`this.setState`</code> with an Object representing how to update the state or a <code>`Promise`</code> that resolves to a similar Object.</p>
<p>In addition you could optionaly update the state by returning an Object/Promise from a lifcycle method or event handler; </p>
<p>For example:</p>
<h2>Data</h2>
<pre><code>
class Welcome {
getInitialState() {
return {n: 0}
}
}
</code></pre>
<h2>Lifecycle</h2>
<pre><code>
class Welcome {
componentDidMount() {
return {n: 0}
}
}
</code></pre>
<h2>Event</h2>
<pre><code>
class Welcome {
handleClick() {
return {n: 0}
}
render() {
h('button', {
onclick: this.handleClick
}, 'Click Me')
}
}
</code></pre>
<h2>Async</h2>
<pre><code>
class Welcome {
async getInitialState() {
return {n: 0}
}
}
</code></pre>
<h1 id=setstate>forceUpdate</h1>
<p>Used when you want to invoke an update without a state update, <code>`this.forceUpdate`</code>.</p>
<h1 id=render>Render</h1>
<p>
The first argument is the virtual element <label for=4></label>
<span class=note>This includes strings, numbers, functions, null, and virtual elements.</span>
you want to render, the second is the root DOM node you want render to. There is an optional third argument that doubles as a callback function when a function is passed or a server-side rendered node for hydration.
</p>
<pre><code>
dio.render('Hello World', document.body)
</code></pre>
<p>When ommitted, the root DOM node defaults to the <code>`<body>`</code> or <code>`<html>`</code> DOM nodes.</p>
<pre><code>
dio.render('Hello World')
</code></pre>
<p>By default calling <code>`render`</code> when mounting preserves the children of the mount node you specifiy; This means that you can mount to <code>`document.body`</code> without any side-effects.</p>
<p></p>
<h2 id=server>Server</h2>
<p>The recommended way to render from the server is to use <code>`dio.render`</code> which can also render to a response object.</p>
<pre><code>
const http = require('http')
const dio = require('dio.js')
const webpage = (
h('!doctype',
h('head',
h('title', 'Title!'),
h('style', `h1 {color:red;}`),
h('script', `console.log("Hello")`)
),
h('body', 'Hello World')
)
)
const server = (request, response) => {
dio.render(webpage, response)
}
http.createServer(server).listen(2000)
</code></pre>
<p>By design using <code>`dio.render`</code> to render on the server uses a stream to send the response to the client in chunks, on the other hand the <code>`toString`</code> method on virtual elements returns the element as a string. Which allows us to render a string in the following way.</p>
<pre><code>
`${h('html')}` === `<html></html>`
</code></pre>
<h2 id=hydration>Hydration</h2>
<p>To hydrate server rendered content <code>`render`</code> accepts a third argument that is either a callback or a DOM node to hydrate.</p>
<pre><code>
dio.render(
h('h1', 'Hello World'),
document.body,
document.body.firstChild
)
</code></pre>
<p>In this case <code>`document.body`</code> is the containing node and <code>`document.body.firstChild`</code> is the DOM node to hydrate.</p>
<h2 id=shallow>Shallow</h2>
<p><code>`dio.shallow`</code> does not render to the DOM or a string but rather returns a shallow render of the element. This is useful when used for testing purposes.</p>
<pre><code>
dio.shallow(() => {
return 'Hello World';
})
</code></pre>
<h1 id=version>Version</h1>
<p>Retrieves the current version of DIO in use, ex. <code>`7.0.0`</code></p>
<pre><code>
console.log(dio.version)
</code></pre>
<h1 id=other>Other</h1>
<h2 id=statics>Static PropTypes/defaultProps</h2>
<p>The following would resemble attaching the returned object of the functions to the <code>`Welcome` class</code>.</p>
<pre><code>
class Welcome {
static propTypes () {
return {
id: PropTypes.func.isRequired
}
}
static defaultProps () {
return {id: 1}
}
render () {
}
}
</code></pre>
<h2 id=error-boundaries>Error Boundary</h2>
<p>All components are error safe; This means that if any error where to throw at any point in the components lifecycle DIO would catch it and pass it the <code>`componentDidThrow`</code> method if it exists which can in turn show an error view or try to manually recover from the error state.</p>
<pre><code>
class Welcome {
componentDidThrow({location, message}) {
if (location === 'render')
return h('h1', 'Error State')
}
render () {
throw ''
}
}
</code></pre>
<p>Pseudo Code:</p>
<ol>
<li>We create a components that intentionally throws in the render phase.</li>
<li>DIO catches this and passes it to the <code>`componentDidThrow`</code> method if it exists.</li>
<li>Our <code>`componentDidThrow`</code> method detects if the error comes from <code>`render`</code> and responds with an error state.</li>
</ol>
<p>On the other hand if the error happend in the update phase and we did not handle it, DIO would simply resolve to the last rendered state.</p>
<h2 id=non-deterministic>Non-Deterministic</h2>
<p>
By default rendering something is a <a href=https://en.wikipedia.org/wiki/Deterministic_system>deterministic</a> <label for=5></label><span class=note>A deterministic system is a system in which no randomness is involved in the development of future states of the system.</span> operation,
given the following children <code>`[A, B, C, D]`</code> the path the flow would take would be.
</p>
<p class=math>A → B → C → D</p>
<p>
But <a href=https://en.wikipedia.org/wiki/Nondeterministic_algorithm>non-deterministic</a> <label for=6></label>
<span class=note>A non-deterministic algorithm is an algorithm that, even for the same input, can exhibit different behaviors on different runs.</span>
introduces the aspect of non-linear updates, where instead of <code>`A → B → C → D`</code> you could get an array of different update sequences;
</p>
<p>For example:</p>
<p class=math>C → D → A → B</p>
<p>This is possible because DIO can pause and resume an update to a specific component depending on how long we tell it to wait before following through.</p>
<p>In the example that follows we wait for a random time before resolving the return value of a render.</p>
<pre><code>
class Welcome {
render() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(
h('h1',
'performance now - ',
performance.now()
)
)
}, Math.random()*1000)
})
}
}
</code></pre>
<p>Putting multiple instances of this component in a list will result in a random update order whenever an update is invoked.</p>
<h2 id=coroutine>Coroutine</h2>
<p>In other words JavaScript <code>`generators`</code><label for=7></label><span class=note>A generator is a special type of function that works as a factory for iterators. A function becomes a generator if it contains one or more yield expressions and if it uses the <code>`function*`</code> syntax.</span>; The example that follows will continue to return a button and increment the <code>`supply`</code> variable whenever an update is invoked through a <code>`click`</code> of the button element.</p>
<p>Though generators are not a well known/used feature in JavaScript they do introduce interesting ways to model behaviour beyond normal functons.</p>
<pre><code>
class Coroutine {
*render(props, state) {
let supply = 0
while (true) {
yield h('button', {
onclick: this.forceUpdate
}, 'Click Me - ', supply++)
}
}
}
</code></pre>
<h2 id=animations>Animations</h2>
<p>There is no specialized API for animations in DIO, however the mount lifecycles methods have been designed with animations in mind.</p>
<p>Making use of the <code>`componentWillUnmount`</code> lifecycle return signature and <code>`componentDidMount`</code> we can animate any component; For example, using the new <a href=https://developer.mozilla.org/en-US/docs/Web/API/Element/animate>Element.animate</a><label for=8></label><span class=note>The Web Animations API opens the browser’s animation engine to developers and manipulation by JavaScript. This API was designed to underlie implementations of both CSS Animations and CSS Transitions, and leaves the door open to future animation effects. It is one of the most performant ways to animate on the Web where supported, letting the browser make its own internal optimizations without hacks, coercion, or Window.requestAnimationFrame(). <a href=https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API>Using the Web Animations API.</a></span> API:</p>
<pre><code>
const animations = {
fadeOut: [
{transform: 'translateY(0)', opacity: 1},
{transform: 'translateY(-100%)', opacity: 0}
],
fadeIn: [
{transform: 'translateY(-100%)', opacity: 0},
{transform: 'translateY(0%)', opacity: 1}
]
}
class Item {
componentDidMount(el) {
el.animate(animations.fadeIn, {
duration: 200
});
}
componentWillUnmount(el) {
return new Promise((resolve) => {
el.animate(animations.fadeOut, {
duration: 200
}).onfinish = resolve
})
}
render() {
return h('li', this.props.children)
}
}
class List {
render() {
return h('ul', this.props.children.map(
v => h(List, v)
))
}
}
dio.render(h(List, 1, 2, 3))
setTimeout(dio.render, 400, h(List, 1, 2, 3, 4))
setTimeout(dio.render, 800, h(List, 1, 2, 3))
</code></pre>
<p>Pseudo Code:</p>
<ol>
<li>We create <code>`Item`</code> component that triggers a <code>fadeIn</code> animation when mounted and <code>fadeOut</code> when unmounted.</li>
<li>We create multiple instances of the <code>`Item`</code> component in our <code>`List`</code> component.</li>
<li>We then render our <code>`List`</code> component with children <code>[1, 2, 3]</code>.</li>
<li>Three <code>`List`</code> components mount and animate when <code>`componentDidMount`</code> is invoked.</li>
<li>After <code>`400ms`</code> we update our <code>`List`</code> components children — <br> <code>[1, 2, 3, 4]</code>.</li>
<li>This mounts a new instance of our <code>`Item`</code> component.</li>
<li>After <code>`800ms`</code> we remove one <code>`Item`</code> child from our <code>`List`</code> component, which in turn invokes our <code>fadeOut</code> animation in <code>`componentWillUnmount`</code>.</li>
</ol>
</article>
<script src=js/highlight.js></script>
<script src=js/main.js></script>
</body>
</html>