Updated docs, added best practices.
h3rald h3rald@h3rald.com
Sat, 01 Aug 2020 15:46:05 +0200
6 files changed,
91 insertions(+),
59 deletions(-)
M
docs/H3_DeveloperGuide.htm
→
docs/H3_DeveloperGuide.htm
@@ -7259,14 +7259,11 @@ </li>
<li><a href="#Key-Concepts">Key Concepts</a> <ul> <li><a href="#HyperScript">HyperScript</a></li> - <li><a href="#Components">Components</a></li> + <li><a href="#Component">Component</a></li> + <li><a href="#Router">Router</a></li> + <li><a href="#Screen">Screen</a></li> <li><a href="#Store">Store</a></li> - <li><a href="#Modules">Modules</a></li> - <li><a href="#Router">Router</a> - <ul> - <li><a href="#Screen">Screen</a></li> - </ul> - </li> + <li><a href="#Module">Module</a></li> <li><a href="#How-everything-works...">How everything works...</a></li> </ul> </li>@@ -7450,8 +7447,6 @@ <h3>HyperScript<a href="#document-top" title="Go to top"></a></h3>
<p>H3 uses a <a href="https://openbase.io/js/hyperscript">HyperScript</a>-like syntax to create HTML elements in pure JavaScript. No, you are actually creating Virtual DOM nodes with it but it can be easier to think about them as HTML elements, or better, something that <em>eventually</em> will be rendered as an HTML element.</p> -<p>The main difference between H3’s HyperScript implementation and others is that it uses <strong>h3</strong> as the main constructor to create nodes. HyperScript uses <strong>h</strong>, Mithril uses <strong>m</strong>, …kind of an obvious choice if you ask me. If you don’t like it, you can rename it to <em>piripicchio</em> if you want, and it will <em>still</em> be used in the same way.</p> - <p>How, you ask? Like this:</p> <pre><code class="js">h("div.test", [@@ -7476,8 +7471,8 @@ </code></pre>
<p>Simple enough. Yes there are some quirks to it, but check the API or Usage docs for those.</p> -<a name="Components"></a> -<h3>Components<a href="#document-top" title="Go to top"></a></h3> +<a name="Component"></a> +<h3>Component<a href="#document-top" title="Go to top"></a></h3> <p>In H3, a component is a function that returns a Virtual Node or a string (that will be treated as a textual DOM node).</p>@@ -7491,27 +7486,6 @@ }, `You clicked me ${count} times.`);
} </code></pre> -<a name="Store"></a> -<h3>Store<a href="#document-top" title="Go to top"></a></h3> - -<p>H3 essentially uses something very, <em>very</em> similar to <a href="https://github.com/storeon/storeon">Storeon</a> for state management <em>and</em> also as a very simple client-side event dispatcher/subscriber (seriously, it is virtually the same code as Storeon). Typically you’ll only use the default store created by H3 upon initialization, and you’ll use the <code>h3.dispatch()</code> and <code>h3.on()</code> methods to dispatch and subscribe to events.</p> - -<p>The current application state is accessible via the <code>h3.state</code> property.</p> - -<a name="Modules"></a> -<h3>Modules<a href="#document-top" title="Go to top"></a></h3> - -<p>The <code>h3.init()</code> method takes an array of <em>modules</em> that can be used to manipulate the application state when specific events are received. A simple module looks like this:</p> - -<pre><code class="js">const error = () => { - h3.on("$init", () => ({ displayEmptyTodoError: false })); - h3.on("error/clear", (state) => ({ displayEmptyTodoError: false })); - h3.on("error/set", (state) => ({ displayEmptyTodoError: true })); -}; -</code></pre> - -<p>Essentially a module is just a function that typically is meant to run only once to define one or more event subscriptions. Modules are the place where you should handle state changes in your application.</p> - <a name="Router"></a> <h3>Router<a href="#document-top" title="Go to top"></a></h3>@@ -7520,7 +7494,7 @@
<p>The current route is always accessible via the <code>h3.route</code> property.</p> <a name="Screen"></a> -<h4>Screen<a href="#document-top" title="Go to top"></a></h4> +<h3>Screen<a href="#document-top" title="Go to top"></a></h3> <p>A screen is a top-level component that handles a route. Unlike ordinary components, screens:</p>@@ -7536,6 +7510,27 @@ <p>Note that:
* Both the <strong>setup</strong> method take an object as a parameter, representing the component state. Such object will be empty the first time the <strong>setup</strong> method is called for a given component, but it may contain properties not removed during subsequent teardowns. * The <strong>teardown</strong> method can return an object, which will be retained as component state. If however nothing is returned, the component state object is emptied. * Both methods can be asynchronous, in which case H3 will wait for their completion before proceeding.</p> + +<a name="Store"></a> +<h3>Store<a href="#document-top" title="Go to top"></a></h3> + +<p>H3 essentially uses something very, <em>very</em> similar to <a href="https://github.com/storeon/storeon">Storeon</a> for state management <em>and</em> also as a very simple client-side event dispatcher/subscriber (seriously, it is virtually the same code as Storeon). Typically you’ll only use the default store created by H3 upon initialization, and you’ll use the <code>h3.dispatch()</code> and <code>h3.on()</code> methods to dispatch and subscribe to events.</p> + +<p>The current application state is accessible via the <code>h3.state</code> property.</p> + +<a name="Module"></a> +<h3>Module<a href="#document-top" title="Go to top"></a></h3> + +<p>The <code>h3.init()</code> method takes an array of <em>modules</em> that can be used to manipulate the application state when specific events are received. A simple module looks like this:</p> + +<pre><code class="js">const error = () => { + h3.on("$init", () => ({ displayEmptyTodoError: false })); + h3.on("error/clear", (state) => ({ displayEmptyTodoError: false })); + h3.on("error/set", (state) => ({ displayEmptyTodoError: true })); +}; +</code></pre> + +<p>Essentially a module is just a function that typically is meant to run only once to define one or more event subscriptions. Modules are the place where you should handle state changes in your application.</p> <a name="How-everything-works..."></a> <h3>How everything works…<a href="#document-top" title="Go to top"></a></h3>@@ -7650,6 +7645,7 @@ <pre><code class="js">const labels = {
overview: "Overview", "quick-start": "Quick Start", "key-concepts": "Key Concepts", + "best-practices": "Best Practices", tutorial: "Tutorial", api: "API", about: "About",@@ -8124,7 +8120,7 @@ <p>Special thanks to the following individuals, that made H3 possible:</p>
<ul> <li><strong>Leo Horie</strong>, author of the awesome <a href="https://mithril.js.org/">Mithril</a> framework that inspired me to write the H3 microframework in a moment of need.</li> -<li><strong>Andrey Sitnik</strong>, author of the beatiful <a href="https://evilmartians.com/chronicles/storeon-redux-in-173-bytes">Storeon</a> state management library, that is used (with minor modification) as the H3 store.</li> +<li><strong>Andrey Sitnik</strong>, author of the beatiful <a href="https://evilmartians.com/chronicles/storeon-redux-in-173-bytes">Storeon</a> state management library, that is used (with minor modifications) as the H3 store.</li> </ul> </div>
M
docs/js/app.js
→
docs/js/app.js
@@ -6,6 +6,7 @@ const labels = {
overview: "Overview", "quick-start": "Quick Start", "key-concepts": "Key Concepts", + "best-practices": "Best Practices", tutorial: "Tutorial", api: "API", about: "About",
M
docs/md/about.md
→
docs/md/about.md
@@ -39,4 +39,4 @@
Special thanks to the following individuals, that made H3 possible: * **Leo Horie**, author of the awesome [Mithril](https://mithril.js.org/) framework that inspired me to write the H3 microframework in a moment of need. -* **Andrey Sitnik**, author of the beatiful [Storeon](https://evilmartians.com/chronicles/storeon-redux-in-173-bytes) state management library, that is used (with minor modification) as the H3 store.+* **Andrey Sitnik**, author of the beatiful [Storeon](https://evilmartians.com/chronicles/storeon-redux-in-173-bytes) state management library, that is used (with minor modifications) as the H3 store.
A
docs/md/best-practices.md
@@ -0,0 +1,36 @@
+## Best Practices + +This page lists some common tips and best practices to get the most out of H3. Some of these may sound counter-intuitive (especially if you are using to frameworks advocating absolute data immutability), but they work well with H3 because of the way it is designed. + +### Embrace Mutability + +No, that's not a mistake. Although you should understand [why immutability is important](https://stackoverflow.com/questions/34385243/why-is-immutability-so-important-or-needed-in-javascript), you shouldn't force yourself to use it in all situations. Instead, you should go through [this article](https://desalasworks.com/article/immutability-in-javascript-a-contrarian-view/) and try to understand also a contrarian view of immutability. + +In H3, changes only occur when needed. Most notably, when re-rendering the Virtual DOM tree of the application will be *mutated in place*, but only where necessary. Functions as well are considered equal if their source code (i.e. string representation) is equal. While this can cause problems in some situations if you are not aware of it, it can be beneficial and actually simplify things most of the time. + +When managing state, if something is different you should typically _just change it_, unless it's shared across the whole application through the Store, in which case (but only in that case) you should try to manage change without side effects and following basic immutability rules. As a rule of thumb, Modules should manage shared application state in an immutable way. + +### Components + +* Avoid nesting component definitions, to avoid creating stale closures. Only define components within other components if you are not relying on changes affecting variables defined in the outer component within the inner component. +* Component should only mutate their own local state. +* Pay attention when relying on captured variables in event handlers, as they may become stale. In certain situations, you can pass data to event handlers through the DOM instead. + * Add identifiers to the real DOM and use `event.currentTarget` in event handlers. + * Use dataset to store identfiers in the real DOM. + + +### Screens + +* Use screens for complex, stateful components orchestrating nested "dumb" components. +* Store event subscription destructors in the screen state and call them in the `teardown` method. +* Return an object from the `teardown` method to preserve route state across screens, only if needed. +* Use the `setup` method to define local, screen-level state that will be accessible in the `display` method. + +### State Management + +* Application state should be stored in the H3 store and should not be mutated. +* Use mutable objects for non-stored local state. +* Use screen state to share data among complex components hierarchies. +* Define separate model classes for complex objects. +* Move complex data manipulation logic to model classes. +* Use url parts and params as an easy way to keep references to recreate application state.
M
docs/md/key-concepts.md
→
docs/md/key-concepts.md
@@ -8,8 +8,6 @@ ### HyperScript
H3 uses a [HyperScript](https://openbase.io/js/hyperscript)-like syntax to create HTML elements in pure JavaScript. No, you are actually creating Virtual DOM nodes with it but it can be easier to think about them as HTML elements, or better, something that *eventually* will be rendered as an HTML element. -The main difference between H3's HyperScript implementation and others is that it uses **h3** as the main constructor to create nodes. HyperScript uses **h**, Mithril uses **m**, ...kind of an obvious choice if you ask me. If you don't like it, you can rename it to *piripicchio* if you want, and it will *still* be used in the same way. - How, you ask? Like this: ```js@@ -36,7 +34,7 @@ ```
Simple enough. Yes there are some quirks to it, but check the API or Usage docs for those. -### Components +### Component In H3, a component is a function that returns a Virtual Node or a string (that will be treated as a textual DOM node).@@ -51,26 +49,6 @@ }, `You clicked me ${count} times.`);
} ``` -### Store - -H3 essentially uses something very, *very* similar to [Storeon](https://github.com/storeon/storeon) for state management *and* also as a very simple client-side event dispatcher/subscriber (seriously, it is virtually the same code as Storeon). Typically you'll only use the default store created by H3 upon initialization, and you'll use the `h3.dispatch()` and `h3.on()` methods to dispatch and subscribe to events. - -The current application state is accessible via the `h3.state` property. - -### Modules - -The `h3.init()` method takes an array of *modules* that can be used to manipulate the application state when specific events are received. A simple module looks like this: - -```js -const error = () => { - h3.on("$init", () => ({ displayEmptyTodoError: false })); - h3.on("error/clear", (state) => ({ displayEmptyTodoError: false })); - h3.on("error/set", (state) => ({ displayEmptyTodoError: true })); -}; -``` - -Essentially a module is just a function that typically is meant to run only once to define one or more event subscriptions. Modules are the place where you should handle state changes in your application. - ### Router H3 comes with a very minimal but fully functional URL fragment router. You create your application routes when initializing your application, and you can navigate to them using ordinary `href` links or programmatically using the `h3.navigateTo` method.@@ -78,7 +56,7 @@
The current route is always accessible via the `h3.route` property. -#### Screen +### Screen A screen is a top-level component that handles a route. Unlike ordinary components, screens:@@ -91,6 +69,26 @@ Note that:
* Both the **setup** method take an object as a parameter, representing the component state. Such object will be empty the first time the **setup** method is called for a given component, but it may contain properties not removed during subsequent teardowns. * The **teardown** method can return an object, which will be retained as component state. If however nothing is returned, the component state object is emptied. * Both methods can be asynchronous, in which case H3 will wait for their completion before proceeding. + +### Store + +H3 essentially uses something very, *very* similar to [Storeon](https://github.com/storeon/storeon) for state management *and* also as a very simple client-side event dispatcher/subscriber (seriously, it is virtually the same code as Storeon). Typically you'll only use the default store created by H3 upon initialization, and you'll use the `h3.dispatch()` and `h3.on()` methods to dispatch and subscribe to events. + +The current application state is accessible via the `h3.state` property. + +### Module + +The `h3.init()` method takes an array of *modules* that can be used to manipulate the application state when specific events are received. A simple module looks like this: + +```js +const error = () => { + h3.on("$init", () => ({ displayEmptyTodoError: false })); + h3.on("error/clear", (state) => ({ displayEmptyTodoError: false })); + h3.on("error/set", (state) => ({ displayEmptyTodoError: true })); +}; +``` + +Essentially a module is just a function that typically is meant to run only once to define one or more event subscriptions. Modules are the place where you should handle state changes in your application. ### How everything works...
M
docs/md/tutorial.md
→
docs/md/tutorial.md
@@ -62,6 +62,7 @@ const labels = {
overview: "Overview", "quick-start": "Quick Start", "key-concepts": "Key Concepts", + "best-practices": "Best Practices", tutorial: "Tutorial", api: "API", about: "About",