From b399af1073282f7f89b2dc990aeed4dec7e705eb Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Sun, 27 Nov 2016 15:04:03 +0000 Subject: [PATCH 1/3] Add vnode mutation warning under components documentation --- docs/components.md | 66 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/components.md b/docs/components.md index bf61a6ee..524575f3 100644 --- a/docs/components.md +++ b/docs/components.md @@ -261,7 +261,9 @@ m(BetterHeader, { }) ``` -#### Avoid component factories +#### Define components statically, call them dynamically + +##### Avoid creating component definitions inside views If you create a component from within a `view` method (either directly inline or by calling a function that does so), each redraw will have a different clone of the component. When diffing component vnodes, if the component referenced by the new vnode is not strictly equal to the one referenced by the old component, the two are assumed to be different components even if they ultimately run equivalent code. This means components created dynamically via a factory will always be re-created from scratch. @@ -291,3 +293,65 @@ m.render(document.body, m(Component, {greeting: "hello"})) // calling a second time does not modify DOM m.render(document.body, m(Component, {greeting: "hello"})) ``` + +##### Avoid creating component instances outside views + +Conversely, for similar reasons, if a component instance is created outside of a view, future redraws will perform an equality check on the node and skip it. Therefore component instances should always be created inside views: + +``` +// AVOID +var Counter = { + count: 0, + view: function(vnode) { + return m("div", + m("p", "Count: " + vnode.state.count ), + + m("button", { + onclick: function() { + vnode.state.count++ + } + }, "Increase count") + ) + } +} + +var counter = m(Counter) + +m.mount(document.body, { + view: function(vnode) { + return [ + m("h1", "My app"), + counter + ] + } +}) +``` + +In the example above, clicking the counter component button will increase its state count, but its view will not be triggered because the vnode representing the component shares the same reference, and therefore the render process doesn't diff them. You should always call components in the view to ensure a new vnode is created: + +``` +// Prefer +var Counter = { + count: 0, + view: function(vnode) { + return m("div", + m("p", "Count: " + vnode.state.count ), + + m("button", { + onclick: function() { + vnode.state.count++ + } + }, "Increase count") + ) + } +} + +m.mount(document.body, { + view: function(vnode) { + return [ + m("h1", "My app"), + m(Counter) + ] + } +}) +``` From 2e43bf4f7b81551d00256edc5ccbfd3fa1567894 Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Sun, 27 Nov 2016 15:16:55 +0000 Subject: [PATCH 2/3] Add vnode caching warning under hyperscript documentation --- docs/hyperscript.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/hyperscript.md b/docs/hyperscript.md index af1067e9..e28781d1 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -408,3 +408,9 @@ var BetterListComponent = { } } ``` + +#### Avoid creating vnodes outside views + +When a redraw encounters a vnode which is strictly equal to the one in the previous render, it will be skipped and its contents will not be updated. While this may seem like an opportunity for performance optimisation, it should be avoided because it prevents dynamic changes in that node's tree - this leads to side-effects such as downstream lifecycle methods failing to trigger on redraw. In this sense, Mithril vnodes are immutable: new vnodes are compared to old ones; mutations to vnodes are not persisted. + +The component documentation contains [more detail and an example of this anti-pattern](components.md#avoid-creating-component-instances-outside-views). From f040bfe47023a4fbdc98ea23e8cd8540aeba529a Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Sun, 27 Nov 2016 15:24:34 +0000 Subject: [PATCH 3/3] Typos --- docs/components.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/components.md b/docs/components.md index 524575f3..116cd38e 100644 --- a/docs/components.md +++ b/docs/components.md @@ -298,7 +298,7 @@ m.render(document.body, m(Component, {greeting: "hello"})) Conversely, for similar reasons, if a component instance is created outside of a view, future redraws will perform an equality check on the node and skip it. Therefore component instances should always be created inside views: -``` +```javascript // AVOID var Counter = { count: 0, @@ -329,8 +329,8 @@ m.mount(document.body, { In the example above, clicking the counter component button will increase its state count, but its view will not be triggered because the vnode representing the component shares the same reference, and therefore the render process doesn't diff them. You should always call components in the view to ensure a new vnode is created: -``` -// Prefer +```javascript +// PREFER var Counter = { count: 0, view: function(vnode) {