A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

2 个月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. title: Coroutines and web components
  2. url: https://lorenzofox.dev/posts/component-as-infinite-loop/
  3. hash_url: 6f3cb3c0c6c580407b1cfaa2d7d9005b
  4. archive_date: 2024-03-09
  5. og_image: https://lorenzofox.dev/posts/component-as-infinite-loop/public/logo.webp
  6. description: Custom elements (web components) modelling with coroutines
  7. favicon: https://lorenzofox.dev/posts/component-as-infinite-loop/public/favicon.ico
  8. language: en_US
  9. <p>In the <a href="https://lorenzofox.dev/posts/coroutine">previous article</a> we learned what coroutines are and saw some patterns they can help implement.
  10. In this article, we will see how coroutines can be used to model web components in a different way, and why you might like it.</p>
  11. <h2>Rendering loop</h2>
  12. <p>Among other things, coroutines have a few properties that we will use in this short essay:</p>
  13. <ul>
  14. <li>They are primarily <strong>functions</strong> and can benefit from the whole functional arsenal of Javascript (composition, higher order function, delegation, etc.).</li>
  15. <li>They are <strong>stateful</strong>.</li>
  16. <li>You can inject pretty much any kind of data when they are paused. For example, an infinite loop within the body of the routine can be considered as a public API function.</li>
  17. <li>You cannot, by design, call the <code>next</code> function concurrently.</li>
  18. </ul>
  19. <h2>Introduction example</h2>
  20. <p>Consider the following generator:</p>
  21. <pre class="language-js"><code class="language-js"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">someComponent</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  22. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  23. <span class="token keyword">const</span> <span class="token punctuation">{</span>content <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">yield</span><span class="token punctuation">;</span>
  24. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> content<span class="token punctuation">;</span>
  25. <span class="token punctuation">}</span>
  26. <span class="token punctuation">}</span></code></pre>
  27. <p>It takes a <code>$host</code> DOM element and has a rendering loop. You can wrap this generator with a function that produces a <code>render</code> function:</p>
  28. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">createComponent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">generator</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  29. <span class="token keyword">const</span> gen <span class="token operator">=</span> <span class="token function">generator</span><span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  30. gen<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  31. <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> gen<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span>
  32. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  33. <span class="token keyword">const</span> HelloWorldComponent <span class="token operator">=</span> <span class="token function">createComponent</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  34. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  35. <span class="token keyword">const</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">yield</span><span class="token punctuation">;</span>
  36. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  37. <span class="token punctuation">}</span>
  38. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  39. <span class="token keyword">const</span> render <span class="token operator">=</span> <span class="token function">HelloWorldComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  40. <span class="token literal-property property">$host</span><span class="token operator">:</span> div
  41. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  42. <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Laurent'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  43. <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Bernadette'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  44. <h2>The power of functions</h2>
  45. <p>For now, the rendering loop is a piece of imperative code, but it can use any rendering library you want (react and so on).
  46. The first point above says that functions (and therefore coroutines) are very versatile in Javascript. We could easily go back to a known paradigm if we wanted to. For example, we use <a href="https://lorenzofox.dev/todo">lit-html</a> to have a declarative view instead of a bunch of imperative code:</p>
  47. <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>render<span class="token punctuation">,</span> html<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit-element'</span><span class="token punctuation">;</span>
  48. <span class="token keyword">const</span> HelloWorldComponent <span class="token operator">=</span> <span class="token function">createComponent</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  49. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  50. <span class="token keyword">const</span> <span class="token punctuation">{</span>name<span class="token operator">=</span><span class="token string">''</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">yield</span>
  51. <span class="token keyword">const</span> template <span class="token operator">=</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;p&gt;hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/p&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  52. <span class="token function">render</span><span class="token punctuation">(</span>$host<span class="token punctuation">,</span> template<span class="token punctuation">)</span><span class="token punctuation">;</span>
  53. <span class="token punctuation">}</span>
  54. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  55. <p>you can draw the template into a function:</p>
  56. <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>html<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit-element'</span><span class="token punctuation">;</span>
  57. <span class="token keyword">const</span> template <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;p&gt;hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/p&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
  58. <p>And compose with a new higher order function:</p>
  59. <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>render<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'lit-element'</span><span class="token punctuation">;</span>
  60. <span class="token keyword">const</span> <span class="token function-variable function">withView</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">templateFn</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">function</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  61. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  62. <span class="token function">render</span><span class="token punctuation">(</span>$host<span class="token punctuation">,</span> <span class="token function">templateFn</span><span class="token punctuation">(</span><span class="token keyword">yield</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  63. <span class="token punctuation">}</span>
  64. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  65. <span class="token keyword">const</span> HelloWorldComponent <span class="token operator">=</span> <span class="token function">createComponent</span><span class="token punctuation">(</span><span class="token function">withView</span><span class="token punctuation">(</span>template<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  66. <p>All right, we are on familiar ground: our component is now a simple function of the state</p>
  67. <pre class="language-js"><code class="language-js"><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;p&gt;hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/p&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> </code></pre>
  68. <h2>Maintaining a state</h2>
  69. <p>Having an infinite rendering loop to model our component can actually be more interesting than it seems at first: you can have a state in the closure of that loop.</p>
  70. <p>If we first modify the higher-level <code>createComponent</code> function a little to bind the <code>render</code> function to the host element:</p>
  71. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">createComponent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">generator</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  72. <span class="token keyword">const</span> gen <span class="token operator">=</span> <span class="token function">generator</span><span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  73. gen<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  74. $host<span class="token punctuation">.</span><span class="token function-variable function">render</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> gen<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span>
  75. <span class="token keyword">return</span> $host<span class="token punctuation">;</span>
  76. <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
  77. <p>We can now make the component trigger its own rendering:</p>
  78. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> CountClick <span class="token operator">=</span> <span class="token function">createComponent</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token operator">*</span><span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
  79. <span class="token keyword">let</span> clickCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  80. $host<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  81. clickCount<span class="token operator">+=</span><span class="token number">1</span><span class="token punctuation">;</span>
  82. $host<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  83. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  84. <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  85. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">I have been clicked </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clickCount<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> time(s)</span><span class="token template-punctuation string">`</span></span>
  86. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  87. <span class="token punctuation">}</span>
  88. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  89. <p>In frameworks like React, where you only have access to the equivalent of what is inside the loop, you rely on the framework extension points (the hooks in the case of React) to build this sort of mechanism, and have very little control over rendering scheduling.</p>
  90. <h2>More HOF function to reduce the coupling.</h2>
  91. <p>The component embeds its view and some logic at the same time. Again, we can easily decouple them so that we can reuse either the view or the logic:
  92. All we need to do is take advantage of the third property of coroutines mentioned in the introduction, and a simple delegation mechanism inherent to generators: <code>yield*</code>.</p>
  93. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">countClickable</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">view</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">function</span> <span class="token operator">*</span><span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  94. <span class="token keyword">let</span> clickCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  95. $host<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  96. clickCount<span class="token operator">+=</span><span class="token number">1</span><span class="token punctuation">;</span>
  97. $host<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">count</span><span class="token operator">:</span> clickCount<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  98. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  99. <span class="token keyword">yield</span><span class="token operator">*</span> <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  100. <span class="token punctuation">}</span></code></pre>
  101. <p>This type of mixin is responsible for holding the state and triggering the rendering of any <em>view</em>. Rendering is left to the view thanks to <strong>delegation</strong>, while the state is passed whenever the view coroutine is paused and requires a new render:</p>
  102. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> CountClick <span class="token operator">=</span> <span class="token function">createComponent</span><span class="token punctuation">(</span><span class="token function">countClickable</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  103. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  104. <span class="token keyword">const</span> <span class="token punctuation">{</span>count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">yield</span><span class="token punctuation">;</span>
  105. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">I have been clicked </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>count<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> time(s)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  106. <span class="token punctuation">}</span>
  107. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  108. <p>Neat ! You can now use the "clickable" behaviour independently, on different views. In the same way, you can plug the view into a different controller logic, as long as it passes the expected data interface (<code>{ count: number | string}</code>): note how the data comes from the <code>yield</code> assignation.</p>
  109. <p>We will see more patterns like this in future articles.</p>
  110. <h2>Web components and lifecycle mapping</h2>
  111. <p>So far we have designed our component to be a function of the host. We can go further and ensure that the rendering routine is actually private to the host, so that the rendering code is encapsulated inside along with any potential behaviour enhancements (the <code>countClickable</code> mixin for example), while both remain reusable.</p>
  112. <p>Let's look at another way of modelling <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">custom elements</a>. To enhance your HTML document, you can teach the browser new ones using its registry and the <code>define</code> method.</p>
  113. <pre class="language-js"><code class="language-js">customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">'hello-world'</span><span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token class-name">extends</span> HTMLElement <span class="token punctuation">{</span>
  114. <span class="token function">connectedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  115. <span class="token keyword">this</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
  116. <span class="token punctuation">}</span>
  117. <span class="token punctuation">}</span><span class="token punctuation">)</span> </code></pre>
  118. <p>And then use the <code>hello-world</code> tag in the markup like any other regular HTML tag.</p>
  119. <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>hello-world</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Laurent<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>hello-world</span><span class="token punctuation">&gt;</span></span></code></pre>
  120. <p>Instead of using a class that extends the <code>HTMLElement</code> class (or any other valid built-in element class), we want the second argument to be a generator function. This means our custom <code>define</code> would need to turn the generator into a class.</p>
  121. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">define</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">tag<span class="token punctuation">,</span> gen</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  122. customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token class-name">extends</span> HTMLElement <span class="token punctuation">{</span>
  123. #loop<span class="token punctuation">;</span>
  124. <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  125. <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  126. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop <span class="token operator">=</span> <span class="token function">gen</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  127. <span class="token literal-property property">$host</span><span class="token operator">:</span> <span class="token keyword">this</span>
  128. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  129. <span class="token keyword">this</span><span class="token punctuation">.</span>render <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  130. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  131. <span class="token punctuation">}</span>
  132. <span class="token function">connectedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  133. <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  134. <span class="token punctuation">}</span>
  135. <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  136. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span>
  137. <span class="token punctuation">}</span>
  138. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  139. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  140. <span class="token function">define</span><span class="token punctuation">(</span><span class="token string">'hello-world'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>$host<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  141. <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  142. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  143. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$host<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
  144. <span class="token punctuation">}</span>
  145. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
  146. <p>Using a class expression, we create the custom element class on the fly. The <code>#loop</code> rendering routine is instantiated inside the constructor and advanced to its first <code>yield</code> point. Note that we pass the host as a parameter to the routine, although the routine is specifically bound to the host so that we could just use <code>this</code> inside the generator to refer to the host. This is a personal preference as I find the use of <code>this</code> in Javascript very error-prone.</p>
  147. <p>When the <code>connectedCallback</code> is called (this happens when the component is mounted into the DOM). We call <code>next</code> again, which in our previous example corresponds to the first iteration of the loop. Then, whenever the component needs to be rendered (when <code>render</code> is called) again, we continue the loop.</p>
  148. <p>This is very interesting because we are able to match the different component lifecycles to a location within the generator function:</p>
  149. <pre class="language-js"><code class="language-js"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">comp</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  150. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'I am being instantiated'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  151. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  152. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Iam being mounted'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  153. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'I have just been mounted'</span><span class="token punctuation">;</span>
  154. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  155. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  156. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'I am being rendered'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  157. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$host<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  158. <span class="token keyword">yield</span> <span class="token string">'I have been rendered'</span><span class="token punctuation">;</span>
  159. <span class="token punctuation">}</span>
  160. <span class="token punctuation">}</span></code></pre>
  161. <p>Yet, one important lifecycle remains to be implemented. When the component is unmounted, the <code>disconnectedCallbak</code> of the class definition is normally called, allowing us to run cleanup code and avoid memory leaks for example.</p>
  162. <p>In the generator we can force the exit of the loop into a <code>finally</code> clause. This is as simple as calling the loop's <code>return</code> function instead of the usual <code>next</code>.</p>
  163. <p>Altogether:</p>
  164. <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">define</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">tag<span class="token punctuation">,</span> gen</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  165. customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token class-name">extends</span> HTMLElement <span class="token punctuation">{</span>
  166. #loop<span class="token punctuation">;</span>
  167. <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  168. <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  169. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop <span class="token operator">=</span> <span class="token function">gen</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  170. <span class="token literal-property property">$host</span><span class="token operator">:</span> <span class="token keyword">this</span>
  171. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  172. <span class="token keyword">this</span><span class="token punctuation">.</span>render <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  173. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  174. <span class="token punctuation">}</span>
  175. <span class="token function">connectedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  176. <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  177. <span class="token punctuation">}</span>
  178. <span class="token function">disconnectedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  179. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop<span class="token punctuation">.</span><span class="token function">return</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  180. <span class="token punctuation">}</span>
  181. <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  182. <span class="token keyword">this</span><span class="token punctuation">.</span>#loop<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span>
  183. <span class="token punctuation">}</span>
  184. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  185. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  186. <span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">comp</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  187. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'I am being instantiated'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  188. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  189. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Iam being mounted'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  190. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'I have just been mounted'</span>
  191. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  192. <span class="token keyword">try</span> <span class="token punctuation">{</span>
  193. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  194. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'I am being rendered'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  195. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$host<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  196. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  197. <span class="token punctuation">}</span>
  198. <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
  199. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'cleanup here !!!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  200. <span class="token punctuation">}</span>
  201. <span class="token punctuation">}</span></code></pre>
  202. <p>This "linear" representation of the component and its lifetime makes things easier to reason about: there are no surprises when a callback or a hook is called, everything is read from top to bottom!</p>
  203. <h2>Concurrent updates</h2>
  204. <p>Before we conclude, we can illustrate the fourth point mentioned in the introduction: if you try to advance a generator function while it is already advancing, you will get an error. In the component world, this means that concurrent rendering is impossible by design!</p>
  205. <p>This code:</p>
  206. <pre class="language-js"><code class="language-js"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">comp</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>$host<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  207. <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  208. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'I am being rendered'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  209. $host<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  210. $host<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$host<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  211. <span class="token keyword">yield</span><span class="token punctuation">;</span>
  212. <span class="token punctuation">}</span>
  213. <span class="token punctuation">}</span></code></pre>
  214. <p>will trigger an error <code>Uncaught TypeError: already executing generator</code>.</p>
  215. <h2>conclusion</h2>
  216. <p>We have seen throughout this article that the functional nature of a generator combined with its intrinsic properties can be useful to build a very flexible and simple abstraction of UI component, with the ability to split behaviour and view into reusable bits, to maintain internal state or to have at reach all component lifecycles in the same place.</p>
  217. <p>In the next article, we will see how we can further improve and optimise our generator-to-class conversion.</p>