<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Robby - 全端的 Front-End Engineer]]></title><description><![CDATA[這是 Robby Wu 的 Blog]]></description><link>https://blog.robby570.tw</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 00:52:51 GMT</lastBuildDate><atom:link href="https://blog.robby570.tw/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[React.js - 離開 vue 的我，和從前的 react 夥伴往 useState Lazy Initialization 邁進]]></title><description><![CDATA[在有需求時發現的新知識，總是特別難忘！
在 react 專案效能調整上，發現了最基礎的玩意。

前言
因爲工作使用 React 開發，對於許久沒碰的我來說，難免錯過了許多好料。
這篇的原由是有一次「質疑」現有專案的寫法：
const [list, setList] = useState(() => getList());

閱讀他人的代碼，先質疑，再理解。這也是一種成長的方式。
仔細查了，才發現原來這叫做「useState Lazy Initialization」。
回到最基礎的文件上，可以找到...]]></description><link>https://blog.robby570.tw/reactjs-vue-react-usestate-lazy-initialization</link><guid isPermaLink="true">https://blog.robby570.tw/reactjs-vue-react-usestate-lazy-initialization</guid><category><![CDATA[hooks]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2025-02-04_React.js%20-%20%E9%9B%A2%E9%96%8B%20vue%20%E7%9A%84%E6%88%91%EF%BC%8C%E5%92%8C%E5%BE%9E%E5%89%8D%E7%9A%84%20react%20%E5%A4%A5%E4%BC%B4%E5%BE%80%20useState%20Lazy%20Initialization%20%E9%82%81%E9%80%B2/banner/1738683611.png.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在有需求時發現的新知識，總是特別難忘！</p>
<p>在 react 專案效能調整上，發現了最基礎的玩意。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/7fac3f0f-753e-4a36-a85e-53a74599e651/1738683611.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2025-02-04_React.js%20-%20%E9%9B%A2%E9%96%8B%20vue%20%E7%9A%84%E6%88%91%EF%BC%8C%E5%92%8C%E5%BE%9E%E5%89%8D%E7%9A%84%20react%20%E5%A4%A5%E4%BC%B4%E5%BE%80%20useState%20Lazy%20Initialization%20%E9%82%81%E9%80%B2/1738683611.png.png" alt="1738683611.png.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>因爲工作使用 React 開發，對於許久沒碰的我來說，難免錯過了許多好料。</p>
<p>這篇的原由是有一次「<strong>質疑</strong>」現有專案的寫法：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [list, setList] = useState(<span class="hljs-function">() =&gt;</span> getList());
</code></pre>
<h6 id="heading-6zax6k6a5luw5lq655qe5luj56k877ym5ywi6loq55ar77ym5yan55cg6kej44cc6ycz5lmf5piv5lia56iu5oiq6zw355qe5pa55byp44cc">閱讀他人的代碼，先質疑，再理解。這也是一種成長的方式。</h6>
<p>仔細查了，才發現原來這叫做「<strong>useState Lazy Initialization</strong>」。</p>
<p>回到最基礎的文件上，可以找到這篇：<a target="_blank" href="https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state">Avoiding recreating the initial state</a> 。</p>
<p>它就是在講述使用<code>useState</code>，傳入<code>function</code>可以使狀態僅在初始時 render。</p>
<p>如果 <code>function</code>需要花費較高的運算時間，甚至引起畫面卡頓，那麼這是一個有效提升效能的做法。</p>
<h6 id="heading-state">中文應該叫做：惰性初始 <strong>state</strong>。</h6>
<p>這篇記錄學習內容，閱讀後你應該會知道 useState lazy initialization：</p>
<ol>
<li>如何正確起手式。</li>
<li>概念與實踐方式。</li>
<li>有無使用的差異。  </li>
</ol>
<h2 id="heading-5lia44cb5yid5ael5yyw5pa55byp55qe5ywp56iu5qih5byp">一、初始化方式的兩種模式</h2>
<h3 id="heading-1">1. 直接傳遞初始值</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos());
</code></pre>
<ul>
<li>初始值會在 <strong>組件每次渲染時</strong> 計算。</li>
<li>如果<code>createInitialTodos()</code>運算較重，將會影響效能。</li>
</ul>
<h3 id="heading-2-lazy-initialization">2. 傳遞函數進行 lazy initialization</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [todos, setTodos] = useState(<span class="hljs-function">() =&gt;</span> createInitialTodos());
<span class="hljs-comment">// or </span>
<span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos);
</code></pre>
<ul>
<li>React 只會在第一次渲染時執行該函數，並將結果作為初始狀態。</li>
<li>避免在每次渲染時執行昂貴的計算。</li>
</ul>
<h3 id="heading-3">3. 建立昂貴計算</h3>
<p>嘗試將<code>createInitialTodos</code>弄得複雜一點，你會發現開始延遲了。</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">depth</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">size</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createInitialTodos</span>(<span class="hljs-params">depth = <span class="hljs-number">3</span>, size = <span class="hljs-number">100</span></span>) </span>{
  <span class="hljs-keyword">if</span> (depth === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>(size)
      .fill(<span class="hljs-number">0</span>)
      .map(<span class="hljs-function">(<span class="hljs-params">_, i</span>) =&gt;</span> i);
  }
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>(size)
    .fill(<span class="hljs-number">0</span>)
    .map(<span class="hljs-function">() =&gt;</span> createInitialTodos(depth - <span class="hljs-number">1</span>, size));
}
</code></pre>
<h2 id="heading-5lqm44cb5l255so56e5l6l5l6g5qu6lyd5pwi6io9">二、使用範例來比較效能</h2>
<p>完整範例放在：<a target="_blank" href="https://github.com/explooosion/react-lazy-initialization">https://github.com/explooosion/react-lazy-initialization</a></p>
<h3 id="heading-1-cra">1. 初始 CRA 專案</h3>
<p>使用一個乾淨的專案來測試。</p>
<pre><code class="lang-powershell">npx create<span class="hljs-literal">-react</span><span class="hljs-literal">-app</span> my<span class="hljs-literal">-app</span>
</code></pre>
<p>[ App.css ]</p>
<p>記得先移除所有樣式。</p>
<h3 id="heading-2-components">2. 建立兩種 Components 比較</h3>
<p>沿用剛剛昂貴計算的<code>function createInitialTodos()</code>。</p>
<p>建立一個直接傳遞初始值的 Component<code>AppWithDirectValue</code>。</p>
<p>[ App.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AppWithDirectValue</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"Direct initializer"</span>);
  <span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos());
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">"Direct initializer"</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count + 1)}&gt;
        AppWithDirectValue ({count})
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<ul>
<li>在<code>useState</code>上下使用<code>console.time</code>, <code>console.timeEnd</code>記錄時間。</li>
<li>在<code>useState</code>直接傳入值<code>createInitialTodos</code>。</li>
<li>使用一個<code>button</code>來觸發<code>re-render</code>。</li>
</ul>
<p>以及建立一個傳遞函數進行 lazy initialization 的 Component <code>AppWithLazyInitializer</code>。</p>
<p>[ App.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AppWithLazyInitializer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"Lazy initializer"</span>);
  <span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos);
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">"Lazy initializer"</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count + 1)}&gt;
        AppWithLazyInitializer ({count})
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<ul>
<li>同樣在<code>useState</code>上下使用<code>console.time</code>, <code>console.timeEnd</code>記錄時間。</li>
<li>在<code>useState</code>傳入函數<code>createInitialTodos</code>，你也可以傳入<code>() =&gt; createInitialTodos()</code>。</li>
<li>使用一個<code>button</code>來觸發<code>re-render</code>。</li>
</ul>
<p>接著在 <code>App</code> 使用，就可以來比較囉！</p>
<p>[ App.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AppWithDirectValue</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AppWithLazyInitializer</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<p>完整代碼：</p>
<p>[ App.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// @ts-check</span>

<span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-comment">/**
 * @param {number} depth
 * @param {number} size
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createInitialTodos</span>(<span class="hljs-params">depth = <span class="hljs-number">3</span>, size = <span class="hljs-number">100</span></span>) </span>{
  <span class="hljs-keyword">if</span> (depth === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>(size)
      .fill(<span class="hljs-number">0</span>)
      .map(<span class="hljs-function">(<span class="hljs-params">_, i</span>) =&gt;</span> i);
  }
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>(size)
    .fill(<span class="hljs-number">0</span>)
    .map(<span class="hljs-function">() =&gt;</span> createInitialTodos(depth - <span class="hljs-number">1</span>, size));
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AppWithDirectValue</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"Direct initializer"</span>);
  <span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos());
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">"Direct initializer"</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count + 1)}&gt;
        AppWithDirectValue ({count})
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AppWithLazyInitializer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"Lazy initializer"</span>);
  <span class="hljs-keyword">const</span> [todos, setTodos] = useState(createInitialTodos);
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">"Lazy initializer"</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count + 1)}&gt;
        AppWithLazyInitializer ({count})
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AppWithDirectValue</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AppWithLazyInitializer</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-lazy-initialization">三、<strong>Lazy Initialization 合適的場景</strong></h2>
<p>✅ 適用於初始值計算量大，例如：</p>
<ul>
<li>具有龐大的值或深度的資料。</li>
<li>複雜計算，例如來源是解析 JSON、深拷貝物件。</li>
<li>只需要在 第一次渲染 設定狀態的情境。  </li>
</ul>
<p>❌ 不適用於：</p>
<ul>
<li>簡單的靜態值，如 <code>useState(0)</code>、<code>useState(false)</code>。</li>
<li>不影響效能的初始化（過度使用 Lazy Initialization 反而讓代碼變得難讀）。</li>
</ul>
<h2 id="heading-reference"><strong>reference</strong></h2>
<ul>
<li><a target="_blank" href="https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state">react dev useState - avoiding-recreating-the-initial-state</a></li>
<li><a target="_blank" href="https://gcdeng.com/blog/react-hooks#%E6%83%B0%E6%80%A7%E5%88%9D%E5%A7%8B-state">React Hooks 總整理筆記</a></li>
<li><a target="_blank" href="https://kentcdodds.com/blog/use-state-lazy-initialization-and-function-updates">useState lazy initialization and function updates</a>  </li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[JavaScript - 我獨自升級 substr]]></title><description><![CDATA[在加入了陳年專案維護中獲得了新知識。


前言
最近工作派發了一些 Legacy Code 專案翻修，難免會遇到一些 deprecated 的方法。
面對這些陳年代碼，最大的敵人往往都是疏忽了商業邏輯以及代碼可讀性。

本篇重點不在於介紹 substr, substring, slice ，想必大家都略懂這方法。

如果使用冷門特性，你一輩子都會記得寫註解對吧！？


這一篇的角度在於筆者對 Legacy Code 進行 String.substr() 重構的思路。
從「維護性」與「效能」的之間...]]></description><link>https://blog.robby570.tw/javascript-substr</link><guid isPermaLink="true">https://blog.robby570.tw/javascript-substr</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[string]]></category><category><![CDATA[substring]]></category><category><![CDATA[slice]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 31 Jan 2025 17:11:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738387081479/0fd9beb5-4344-4d08-bcb6-6b95bf9586d0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>在加入了陳年專案維護中獲得了新知識。</p>
<p><a target="_blank" href="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2025-01-31_JavaScript%20-%20%E6%88%91%E7%8D%A8%E8%87%AA%E5%8D%87%E7%B4%9A%20substr/banner/javascript_substr.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2025-01-31_JavaScript%20-%20%E6%88%91%E7%8D%A8%E8%87%AA%E5%8D%87%E7%B4%9A%20substr/banner/javascript_substr.png" alt="javascript_substr.png" /></a></p>
<p><br /><br /></p>
<h1 id="heading-5ymn6kia">前言</h1>
<p>最近工作派發了一些 Legacy Code 專案翻修，難免會遇到一些 <code>deprecated</code> 的方法。</p>
<p>面對這些陳年代碼，最大的敵人往往都是疏忽了商業邏輯以及代碼可讀性。</p>
<p><br /></p>
<p>本篇重點不在於介紹 <code>substr</code>, <code>substring</code>, <code>slice</code> ，想必大家都略懂這方法。</p>
<blockquote>
<p>如果使用冷門特性，你一輩子都會記得寫註解對吧！？</p>
</blockquote>
<p><br /></p>
<p>這一篇的角度在於筆者對 Legacy Code 進行 <code>String.substr()</code> 重構的思路。</p>
<p>從「<em>維護性</em>」與「<em>效能</em>」的之間找到合適的平衡。</p>
<p>這份筆記也是為了日後仍遇到時，可以快速拿自己的文章參考。</p>
<p>希望能對大家有所幫助。</p>
<p><br /><br /><br /></p>
<h1 id="heading-5lia44cb5a2x5liy5yih5ymy5lul57s5">一、字串切割介紹</h1>
<p>簡單說明一下他們「<strong>主要</strong>」的用法。</p>
<p>最大的區別在於第二個參數「<strong>索引 Index</strong>」的意義。</p>
<p><strong>String.prototype.substr()</strong> - <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr">MDN Web Docs</a></p>
<pre><code class="lang-javascript">substr(start)
substr(start, length)
</code></pre>
<p><strong>String.prototype.substring()</strong> - <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring">MDN Web Docs</a></p>
<pre><code class="lang-javascript">substring(indexStart)
substring(indexStart, indexEnd)
</code></pre>
<p><strong>String.prototype.slice()</strong> - <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice">MDN Web Docs</a></p>
<pre><code class="lang-javascript">slice(indexStart)
slice(indexStart, indexEnd)
</code></pre>
<p><br /></p>
<p>這些方法中，最大的差異在於：</p>
<ul>
<li><p>參數使用幾個？</p>
</li>
<li><p>傳入的參數值是否為 ≥ 0？</p>
</li>
<li><p>傳入的參數值是否為 負數？</p>
</li>
</ul>
<p><br /><br /><br /></p>
<h1 id="heading-5lqm44cb6kmv5lyw5oq95ob5pa55byp">二、評估抽換方式</h1>
<p>這篇是解決 Legacy Code 的場景，因此可以思考你面對的 <code>substr()</code> ，是為了解決什麼問題？</p>
<p>接著來探索這些可能的條件，找出最適的解決方案！</p>
<blockquote>
<p>極端的例子我相信是鮮少的，不太需要執著於極端案例。</p>
<p>難以長期傳承商業邏輯的代碼，最需要的是直觀的可讀性。</p>
</blockquote>
<p><br /><br /></p>
<p>以下提供 7 種的例子，作為重構的選擇：</p>
<ol>
<li><p>基本使用 <code>substr(start, length)</code>)</p>
</li>
<li><p>起始索引為負數 <code>substr(-start, length)</code> → 使用 <code>slice()</code></p>
</li>
<li><p>長度超過字串長度 <code>substr(start, length)</code> → 使用 <code>substring()</code></p>
</li>
<li><p>起始索引超過字串長度 <code>substr(start, length)</code> → 使用 <code>slice()</code></p>
</li>
<li><p>單參數 <code>substr(start)</code> → 使用 <code>substring()</code></p>
</li>
<li><p>起始索引為負數且超過字串長度 <code>substr(-start, length)</code> → 使用 <code>slice()</code></p>
</li>
<li><p>無法使用 <code>substring(start, end)</code> 替代 <code>substr()</code> 的情境 → 使用 <code>slice()</code></p>
</li>
</ol>
<p><br /><br />    </p>
<h3 id="heading-1-substrstart-length">1 . 基本使用 <code>substr(start, length)</code></h3>
<p>適用於簡單的範圍擷取，用於單純的商業邏輯。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"JavaScript"</span>;
<span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">4</span>, <span class="hljs-number">10</span>)); <span class="hljs-comment">// "Script"</span>
</code></pre>
<p>使用 <code>substring()</code> 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">4</span>, <span class="hljs-number">10</span>)); <span class="hljs-comment">// "Script"</span>
</code></pre>
<p>✅ <code>substring(start, start + length)</code> 直觀表達了「從 start 位置擷取 length 個字元」，可讀性極高。</p>
<p><br /><br /></p>
<h3 id="heading-2-substr-start-length-slice">2. 起始索引為負數 <code>substr(-start, length)</code> → 使用 <code>slice()</code></h3>
<p><code>substring()</code> 不支援負數索引，因此需要 <code>slice()</code> 來替代。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"Hello World"</span>;
<span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-5</span>)); <span class="hljs-comment">// "World"</span>
</code></pre>
<p>使用 <code>slice()</code> 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-5</span>)); <span class="hljs-comment">// "World"</span>
</code></pre>
<p>✅ <code>slice(-start)</code> 直接從倒數第 start 個字元開始擷取，行為與 <code>substr()</code> 相同。</p>
<p><br /><br /></p>
<h3 id="heading-3-substrstart-length-substring">3. 長度超過字串長度 <code>substr(start, length)</code> → 使用 <code>substring()</code></h3>
<p>在一些未知長度的條件下，如果允許 length 超出範圍，<code>substr()</code> 會自動調整，<code>substring()</code> 也有類似行為。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"abcdef"</span>;
<span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">2</span>)); <span class="hljs-comment">// "cdef"</span>
</code></pre>
<p>使用 <code>substring()</code> 替換相對直觀，<code>slice()</code> 你喜歡也可以使用。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">2</span>, str.length)); <span class="hljs-comment">// "cdef"</span>
</code></pre>
<p>✅ <code>substring(start, end)</code> 自動處理 end 超過範圍的情況。</p>
<p><br /><br /></p>
<h3 id="heading-4-substrstart-length-slice">4. 起始索引超過字串長度 <code>substr(start, length)</code> → 使用 <code>slice()</code></h3>
<p>如果場景具有未知的起始索引，<code>substring()</code> 會自動交換 start 和 end，而有<strong>不同</strong>結果，這種條件建議使用 <code>slice()</code>。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"Hello"</span>;
<span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">10</span>, <span class="hljs-number">13</span>)); <span class="hljs-comment">// "" (空字串)</span>
</code></pre>
<p>使用 slice() 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">10</span>, <span class="hljs-number">13</span>)); <span class="hljs-comment">// "" (空字串)</span>
</code></pre>
<p>✅ <code>slice(start, end)</code> 行為與 <code>substr()</code> 一致，會返回空字串。</p>
<p><br /><br /></p>
<h3 id="heading-5-substrstart-substring">5. 單參數 <code>substr(start)</code> → 使用 <code>substring()</code></h3>
<p>當 length 省略時，<code>substr(start)</code> 會提取到字串結尾，這與 <code>substring(start)</code> 行為相同。這應該是最簡單的場境，單純且單一參數。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"Hello JavaScript"</span>;
<span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">6</span>)); <span class="hljs-comment">// "JavaScript"</span>
</code></pre>
<p>使用 <code>substring()</code> 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">6</span>)); <span class="hljs-comment">// "JavaScript"</span>
</code></pre>
<p>✅ <code>substring(start)</code> 直覺地表示「從 start 開始到字串結尾」，可讀性高。</p>
<p><br /><br /></p>
<h3 id="heading-6-substr-start-length-slice">6. 起始索引為負數且超過字串長度 <code>substr(-start, length)</code> → 使用 <code>slice()</code></h3>
<p>通常在於更多的演算方法。<code>substring()</code> 不支援負數索引，應該使用 <code>slice()</code>。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"Hello"</span>;
<span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-10</span>, <span class="hljs-number">-7</span>)); <span class="hljs-comment">// "Hel"</span>
</code></pre>
<p>使用 <code>slice()</code> 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-10</span>, <span class="hljs-number">-7</span>)); <span class="hljs-comment">// "Hel"</span>
</code></pre>
<p>✅ <code>slice(start, end)</code> 在負索引時表現一致，適用於此場景。</p>
<p><br /><br /></p>
<h3 id="heading-7-substringstart-end-substr-slice">7. 無法使用 <code>substring(start, end)</code> 替代 <code>substr()</code> 的情境 → 使用 <code>slice()</code></h3>
<p><code>substring()</code> 無法處理負數索引，也無法擷取固定長度，因此 <code>slice()</code> 是更好的選擇。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> str = <span class="hljs-string">"Hello World"</span>;
<span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-5</span>, <span class="hljs-number">-2</span>)); <span class="hljs-comment">// "Wor"</span>
</code></pre>
<p>錯誤的 <code>substring()</code> 替換。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.substring(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// "" (錯誤，因為 `substring()` 會將負數視為 0)</span>
</code></pre>
<p>正確的做法應該用 <code>slice()</code>。</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(str.slice(<span class="hljs-number">-5</span>, <span class="hljs-number">-2</span>)); <span class="hljs-comment">// "Wor"</span>
</code></pre>
<p>✅ <code>slice()</code> 是唯一能夠完全取代 <code>substr()</code> 在負索引情境下的選擇。</p>
<p><br /><br /><br /></p>
<h1 id="heading-5lij44cb57wq6kuw">三、結論</h1>
<p>整體而言，應該根據重構的<strong>目的</strong>，以及代碼的<strong>職責</strong>來決定適合哪一種。</p>
<ul>
<li><p>基於可讀性優先：如果今天你在業務邏輯單純，且索引範圍固定時，<code>substring()</code> 可讀性更高。</p>
</li>
<li><p>基於效率且容錯率高的場景：如果面對特定的演算或者複雜的計算，甚至索引可能是負數等等不穩定的條件，這時候使用 <code>slice()</code> 相對安全。</p>
</li>
</ul>
<p><br /><br /></p>
<p><strong>適合</strong> <code>substring()</code> <strong>的場景：</strong></p>
<ul>
<li><p>start 與 end 你能肯定商業邏輯為正數。</p>
</li>
<li><p>需要擷取 固定範圍，不考慮負索引。</p>
</li>
<li><p>可讀性佳，適用於基本的字串擷取。</p>
</li>
</ul>
<p><strong>適合</strong> <code>slice()</code> <strong>的場景：</strong></p>
<ul>
<li><p>需要支援負數索引 <code>substr(-start, length)</code>。</p>
</li>
<li><p>起始索引超出字串長度時，應返回空字串。</p>
</li>
<li><p>避免 start &gt; end 情境時自動交換。</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Git - 在工作與私人專案中來回切換 SSH]]></title><description><![CDATA[將 SSH 分不同的帳號配置！
在悠閒的假日，最快樂的便是探索新技能～

前言
筆者 MacBook 拿來混雜工作與私人開發用，
因此經常有不同的 Repository 須對應不同的 Github Account。
如果沒設定好 SSH，就會被終端機嗆：
ERROR: Permission to explooosion/nuxt3-boilerplate.git denied to robbywu-work.
fatal: Could not read from remote repositor...]]></description><link>https://blog.robby570.tw/git-ssh</link><guid isPermaLink="true">https://blog.robby570.tw/git-ssh</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[ssh]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 08 Jul 2023 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/banner/1688807175.png.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>將 SSH 分不同的帳號配置！</p>
<p>在悠閒的假日，最快樂的便是探索新技能～</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/580f0763-acec-488f-9d67-cb7c7cfeb04d/1688807175.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/1688807175.png.png" alt="1688807175.png.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>筆者 MacBook 拿來混雜工作與私人開發用，</p>
<p>因此經常有不同的 Repository 須對應不同的 Github Account。</p>
<p>如果沒設定好 SSH，就會被終端機嗆：</p>
<pre><code class="lang-powershell">ERROR: Permission to explooosion/nuxt3<span class="hljs-literal">-boilerplate</span>.git denied to robbywu<span class="hljs-literal">-work</span>.
fatal: Could not read from remote repository.
</code></pre>
<p>每次在新環境都需要重新配置，</p>
<p>也…時常遺忘作法，所以這次將流程紀錄起來！</p>
<h3 id="heading-5roo5osp5lql6acf">注意事項</h3>
<ol>
<li>文章筆者分為 Personal 與 Work 的情境建置</li>
<li>筆者開發環境使用 macOS</li>
</ol>
<h2 id="heading-0"><strong>0.環境檢查</strong></h2>
<p>可以先檢查自己電腦是否已經存在一些 git 使用的 SSH，</p>
<p>在 <code>~/.ssh/</code>目錄下檢查是否存在以下兩個檔案（類似）：</p>
<ul>
<li><code>id_rsa</code>（私鑰）</li>
<li><code>id_rsa.pub</code>（公鑰）</li>
</ul>
<p>如果已經存在，可以選擇刪除重新來過，</p>
<p>或者跳過 <code>1.建置金鑰</code>，直接到 <code>2.SSH 設定檔</code>。</p>
<h2 id="heading-1"><strong>1.建置金鑰</strong></h2>
<h3 id="heading-11">1.1 私人用</h3>
<p>新增 SSH，輸入指令。</p>
<pre><code class="lang-powershell">ssh<span class="hljs-literal">-keygen</span> <span class="hljs-literal">-t</span> rsa <span class="hljs-literal">-b</span> <span class="hljs-number">4096</span> <span class="hljs-literal">-C</span> <span class="hljs-string">"your_email@example.com"</span>
</code></pre>
<ul>
<li>請將<code>your_email@example.com</code> 改為自己的私人電子郵件</li>
</ul>
<p>輸入上述指令建立檔案與名稱時，會提示你儲存檔案。</p>
<pre><code class="lang-powershell">Generating public/private rsa key pair.
Enter file <span class="hljs-keyword">in</span> which to save the key (/Users/robbymac/.ssh/id_rsa): id_personal
</code></pre>
<ul>
<li>可設定自己喜歡的名字。例如：<code>id_personal</code></li>
</ul>
<p>添加 SSH 金鑰到 SSH 代理，輸入指令。</p>
<pre><code class="lang-powershell">ssh<span class="hljs-literal">-add</span> ~/.ssh/id_personal
</code></pre>
<ul>
<li>請指向自己的檔案路徑與名稱</li>
</ul>
<p>複製公鑰，輸入指令。</p>
<p>稍後我們便可以貼到 <a target="_blank" href="https://github.com/settings/keys">Github - Settings - SSH keys</a> 上。</p>
<pre><code class="lang-powershell">pbcopy &lt; ~/.ssh/id_personal.pub
</code></pre>
<ul>
<li>請記得要複製公鑰<code>.pub</code></li>
<li>建立 SSH 時會自動產生私鑰匙與公鑰，公鑰預設檔名為<code>[私鑰].pub</code></li>
</ul>
<p>在 GitHub 上添加 SSH 金鑰，選擇 New SSH key。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/6298370b-3532-4c06-ada5-677501cbfb78/1688804448.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/1688804448.png.png" alt="1688804448.png.png" /></a></p>
<p>將剛剛透過 pbcopy 指令複製的公鑰，貼到 key，並適當取一個你好識別裝置的金鑰。</p>
<ul>
<li>Key type不需要變更，預設Authentication Key</li>
</ul>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/6298370b-3532-4c06-ada5-677501cbfb78/1688804478.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/1688804478.png.png" alt="1688804478.png.png" /></a></p>
<p>測試 SSH 連接，輸入指令。</p>
<pre><code class="lang-powershell">ssh <span class="hljs-literal">-T</span> git@github.com
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/6298370b-3532-4c06-ada5-677501cbfb78/1688804743.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/1688804743.png.png" alt="1688804743.png.png" /></a></p>
<h3 id="heading-12">1.2 工作用</h3>
<p>參照 「1.1 私人專案金鑰」，建立一組金鑰 id_work、id_work.pub</p>
<p>步驟很簡單，不是筆者懶了不寫文章。</p>
<h2 id="heading-2-ssh"><strong>2. SSH 設定檔</strong></h2>
<p>接著我們要在這個檔案，配置 SSH 用於連接遠端主機的相關設定。</p>
<p>請到 ~/.ssh 目錄建立新檔案，內容如下：</p>
<p>可使用 vi 或者 nano 編輯，</p>
<p>或者用 Finder 打開，直接建立檔案，輸入指令。</p>
<pre><code class="lang-powershell">open ~/.ssh
</code></pre>
<p>新增 SSH 設定檔案，輸入指令。</p>
<pre><code class="lang-powershell">touch config
</code></pre>
<p>SSH 設定檔案內容，輸入內容。</p>
<pre><code class="lang-code">Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_person

Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_work
</code></pre>
<ul>
<li>Host：可以自己設定方便識別的名稱</li>
<li>HostName：根據你的主機配置，由於 Repository 建立在 Github，因此設定 github.com 即可！</li>
<li>IdentityFile：設定你的 SSH 來源，請依據你的私人與工作用指定 私鑰</li>
</ul>
<p>以上流程就完成建置囉！後續不再需要改動。</p>
<h2 id="heading-3-repository"><strong>3. 設定 Repository</strong></h2>
<p>以下有兩種情境，請根據所需擇一配置。</p>
<h3 id="heading-31-clone-ssh-config-host"><strong>3.1 新專案 Clone 指定 SSH Config Host</strong></h3>
<p>在 Git 的預設設置中，當使用 SSH 協議與遠端版本控制倉庫進行通信時，它會自動讀取 ~/.ssh/config 文件中的主機配置。</p>
<p>這些主機配置包含了與遠端主機的連接參數，例如主機名稱、用戶名和身份證書文件等。</p>
<p>由於我們環境存在多個遠端主機，因此每次 Clone 需要指派對應的 Host。</p>
<p>使用 SSH 協議進行操作時忽略 ~/.ssh/config 文件中的任何主機配置。</p>
<p>設定環境變數，輸入指令。</p>
<pre><code class="lang-powershell">export GIT_SSH_COMMAND=<span class="hljs-string">"ssh -F /dev/null"</span>
</code></pre>
<p>接著 Clone 你的新專案。</p>
<pre><code class="lang-powershell">git clone ssh://github<span class="hljs-literal">-personal</span>:&lt;使用者名稱&gt;/&lt;儲存庫名稱&gt;.git
</code></pre>
<h3 id="heading-32-ssh-config">3.2 將既有專案添加指定 SSH Config</h3>
<p>每次你有新專案，都需要設定所使用的 SSH 帳號。</p>
<p>請先移動到你的專案目錄，輸入指令。</p>
<pre><code class="lang-powershell">git remote <span class="hljs-built_in">set-url</span> origin github<span class="hljs-literal">-personal</span>:&lt;使用者名稱&gt;/&lt;儲存庫名稱&gt;.git
</code></pre>
<ul>
<li>github-personal：根據你的專案用途，輸入 SSH Config 配置的 Host 名稱。</li>
</ul>
<p>檢查專案的 Remote，輸入指令。可以發現 remote-url 增加了 Host 名稱。</p>
<pre><code class="lang-powershell">git remote <span class="hljs-literal">-v</span>
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/6298370b-3532-4c06-ada5-677501cbfb78/1688805918.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-07-08_Git%20-%20%E5%9C%A8%E5%B7%A5%E4%BD%9C%E8%88%87%E7%A7%81%E4%BA%BA%E5%B0%88%E6%A1%88%E4%B8%AD%E4%BE%86%E5%9B%9E%E5%88%87%E6%8F%9B%20SSH/1688805918.png.png" alt="1688805918.png.png" /></a></p>
<p>好耶！恭喜你設置完畢。</p>
<p>只要要記得，這兩種的專案配置就行囉！</p>
<h6 id="heading-5oiw6icf6lef562g6icf5lia5qij77ym6yen5paw5zue6agn6ycz56h5pah56ug77yb77yb">或者跟筆者一樣，重新回顧這篇文章！！</h6>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[TypeScript - 在 interface 中理解 Pick, Omit, Partial, Intersection Types]]></title><description><![CDATA[關於 interface 的特性應用！
又是值得探索 typescript 的日子 ~

前言
平常專案的開發上使用都 TypeScript，但其實用的範圍很淺，沒有太多的琢磨。
而近期在重構撰寫上心中冒出了一些問題，這些問題其實蠻新手向的。
這篇文章最感謝 ChatGPT，在指引下找到關鍵字，進而翻閱到官方文件。
好耶！
本篇非常淺薄筆記用途，如果讀者有更多的興趣，可以直接參見官方文件：
https://www.typescriptlang.org/docs/handbook/utility-...]]></description><link>https://blog.robby570.tw/typescript-interface-pick-omit-partial-intersection-types</link><guid isPermaLink="true">https://blog.robby570.tw/typescript-interface-pick-omit-partial-intersection-types</guid><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 24 Jun 2023 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-06-24_TypeScript%20-%20%E5%9C%A8%20interface%20%E4%B8%AD%E7%90%86%E8%A7%A3%20Pick%2C%20Omit%2C%20Partial%2C%20Intersection%20Types/banner/1687544088.png.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>關於 interface 的特性應用！</p>
<p>又是值得探索 typescript 的日子 ~</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/70750be1-3b97-4661-8cec-abe36ff9a52a/1687544088.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-06-24_TypeScript%20-%20%E5%9C%A8%20interface%20%E4%B8%AD%E7%90%86%E8%A7%A3%20Pick%2C%20Omit%2C%20Partial%2C%20Intersection%20Types/1687544088.png.png" alt="1687544088.png.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>平常專案的開發上使用都 TypeScript，但其實用的範圍很淺，沒有太多的琢磨。</p>
<p>而近期在重構撰寫上心中冒出了一些問題，這些問題其實蠻新手向的。</p>
<p>這篇文章最感謝 <a target="_blank" href="https://chat.openai.com/">ChatGPT</a>，在指引下找到關鍵字，進而翻閱到官方文件。</p>
<p>好耶！</p>
<p>本篇非常淺薄筆記用途，如果讀者有更多的興趣，可以直接參見官方文件：</p>
<p>https://www.typescriptlang.org/docs/handbook/utility-types.html</p>
<h2 id="heading-5paw5oml5zcr5ymn572u5l2c5qwt">新手向前置作業</h2>
<p>在開始前可以先初始開發環境，讓環境能夠編譯 TypeScript。</p>
<pre><code class="lang-powershell">npm init <span class="hljs-literal">-y</span>
</code></pre>
<p>安裝 TypeScript 的定義管理工具。</p>
<pre><code class="lang-powershell">npm install -<span class="hljs-literal">-save</span><span class="hljs-literal">-dev</span> typescript ts<span class="hljs-literal">-node</span>
</code></pre>
<p>初始 tsconfig.json，預設配置不修改，如果有額外需求再自行設置。</p>
<pre><code class="lang-powershell">npx tsc -<span class="hljs-literal">-init</span>
</code></pre>
<p>如果要編譯時，執行指令即可：</p>
<pre><code class="lang-powershell">npx tsc
</code></pre>
<p>下方是一段關於用戶的介面定義，文章的範例都會沿用此介面。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Person {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  age: <span class="hljs-built_in">number</span>;
  email: <span class="hljs-built_in">string</span>;
  address: <span class="hljs-built_in">string</span>;
  phoneNumbner: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h2 id="heading-pick">一、Pick 應用</h2>
<p>Pick</p>
<p>如果今天想基於這個介面，取出需要的屬性來定義新的介面，可以使用 <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys">Pick</a>。</p>
<p>範例為定義一個介面，包含 name、age、email、address 4 種屬性。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Person {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  age: <span class="hljs-built_in">number</span>;
  email: <span class="hljs-built_in">string</span>;
  address: <span class="hljs-built_in">string</span>;
  phoneNumbner: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> PersonPreview = Pick&lt;Person, <span class="hljs-string">"name"</span> | <span class="hljs-string">"age"</span> | <span class="hljs-string">"email"</span> | <span class="hljs-string">"address"</span>&gt;;

<span class="hljs-keyword">const</span> person: PersonPreview = {
  name: <span class="hljs-string">"Robby"</span>,
  age: <span class="hljs-number">18</span>,
  email: <span class="hljs-string">"robby@email.com"</span>,
  address: <span class="hljs-string">"my address"</span>,
};
</code></pre>
<h2 id="heading-omit">二、Omit 應用</h2>
<p>Omit</p>
<p>如果今天想基於這個介面，剔除不需要的屬性來定義新的介面，可以使用 <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys">Omit</a>。</p>
<p>範例為定義一個介面，從 Person 介面中去除了 id、phoneNumber 後，剩餘的 4 種屬性。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Person {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  age: <span class="hljs-built_in">number</span>;
  email: <span class="hljs-built_in">string</span>;
  address: <span class="hljs-built_in">string</span>;
  phoneNumbner: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> PersonPreview = Omit&lt;Person, <span class="hljs-string">"id"</span> | <span class="hljs-string">"phoneNumbner"</span>&gt;;

<span class="hljs-keyword">const</span> person: PersonPreview = {
  name: <span class="hljs-string">"Robby"</span>,
  age: <span class="hljs-number">18</span>,
  email: <span class="hljs-string">"robby@email.com"</span>,
  address: <span class="hljs-string">"my address"</span>,
};
</code></pre>
<h2 id="heading-partial">三、Partial 應用</h2>
<p>Partial</p>
<p><a target="_blank" href="https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype">Partial</a> 用於定義介面是允許非必填的，這裡所指的是將原本屬性賦予 undefined 型別，不包含 null！</p>
<p>今天情境為定義一個介面用於新增用戶，可以延續使用 Person 來完成這個新介面定義。</p>
<p>在下方例子中：</p>
<ol>
<li>若 name、email 為必填，可使用 Pick 來抽出所需屬性。</li>
<li>若除了 name、email，其餘屬性皆可必填，我們可使用 Partial。</li>
<li>由於 id 不應該被視為 request body 提交，因此使用 Omit 去除 id。</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Person {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  age: <span class="hljs-built_in">number</span>;
  email: <span class="hljs-built_in">string</span>;
  address: <span class="hljs-built_in">string</span>;
  phoneNumbner: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> CreatePersonRequest = Pick&lt;Person, <span class="hljs-string">"name"</span> | <span class="hljs-string">"email"</span>&gt; &amp;
  Partial&lt;Omit&lt;Person, <span class="hljs-string">"id"</span>&gt;&gt;;

<span class="hljs-keyword">const</span> person: CreatePersonRequest = {
  name: <span class="hljs-string">"Robby"</span>,
  email: <span class="hljs-string">"robby@email.com"</span>,
};
</code></pre>
<h2 id="heading-5zub44cb5bu25ly45ocd6icd">四、延伸思考</h2>
<p>到目前為止其實範例都很簡單，在官方文件 <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/utility-types.html">Utility Types</a> 都能找到！</p>
<p>而筆者在 Partial 的例子中有了新疑惑！？</p>
<p>對於 CreatePersonRequest 賦予了必填：</p>
<pre><code class="lang-typescript">Pick&lt;Person, <span class="hljs-string">"name"</span> | <span class="hljs-string">"email"</span>&gt;
</code></pre>
<p>而我們卻在 Partial 只替除了 id，意味著 name、email 是非必填：</p>
<pre><code class="lang-typescript">Partial&lt;Omit&lt;Person, <span class="hljs-string">"id"</span>&gt;&gt;
</code></pre>
<p>關於非必填的設定，嚴格上做法是這三個屬性去除：</p>
<pre><code class="lang-typescript">Partial&lt;Omit&lt;Person, <span class="hljs-string">"id"</span> | <span class="hljs-string">"name"</span> | <span class="hljs-string">"email"</span>&gt;&gt;
</code></pre>
<p>那為何我們最初的例子結果卻是必填？？？</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> CreatePersonRequest = Pick&lt;Person, <span class="hljs-string">"name"</span> | <span class="hljs-string">"email"</span>&gt; &amp;
  Partial&lt;Omit&lt;Person, <span class="hljs-string">"id"</span>&gt;&gt;;
</code></pre>
<h3 id="heading-5lqk5yj5z6l5yil">交叉型別</h3>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Intersection_type#Intersection_of_a_type_family">交叉型別，Intersection Types</a>：</p>
<blockquote>
<p>在交叉型別中，如果有相同的屬性，則該屬性必須符合所有相關型別的規則。</p>
</blockquote>
<p>這是因為在這裡使用了「&amp;」作為結合，名詞稱之為「<a target="_blank" href="https://en.wikipedia.org/wiki/Intersection_type#Intersection_of_a_type_family">交叉型別，Intersection Types</a>」。</p>
<p>在這個情況下，即使 name 和 email 在 Partial&gt; 這個型別中是可選的，</p>
<p>但是由於他們在 Pick 型別中是必填的，所以在 CreatePersonRequest 中也是必填的。</p>
<p>換句話說：</p>
<p>可以把它看作是 "更嚴格" 的規則（也就是必填）優先於 "更寬鬆" 的規則（也就是可選）。</p>
<p>這是 TypeScript 的 Intersection Types 的行為。</p>
<h3 id="heading-5bu25ly45l6l5a2q">延伸例子</h3>
<p>一些關於 Intersection Types 的例子：</p>
<p>在這個範例中，型別 C 是型別 A 和 B 的交叉型別，因此它將有兩個屬性：一個數字型別的 a 屬性，和一個字串型別的 b 屬性。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> A = {
  a: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">type</span> B = {
  b: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">type</span> C = A &amp; B;

<span class="hljs-keyword">let</span> c: C = {
  a: <span class="hljs-number">1</span>,
  b: <span class="hljs-string">"hello"</span>
};
</code></pre>
<p>如果在兩個型別中都有相同的屬性，而其中一個是必須的，另一個是可選的，則結果型別將會是必須的。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> A = {
  a: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">type</span> B = {
  a?: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">type</span> C = A &amp; B;

<span class="hljs-keyword">let</span> c: C = { a: <span class="hljs-number">1</span> }; <span class="hljs-comment">// Correct</span>
<span class="hljs-keyword">let</span> c2: C = {}; <span class="hljs-comment">// Error, property 'a' is missing in type '{}'</span>
</code></pre>
<p>在交叉型別中，如果有相同的屬性但是型別不一致，則該屬性必須符合所有相關型別的規則。</p>
<p>這個例子中，型別 C 會導致一個錯誤，因為 a 在 A 中是 number 型別，而在 B 中是 string 型別。</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> A = {
  a: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">type</span> B = {
  a: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">type</span> C = A &amp; B;
</code></pre>
<h3 id="heading-57at5z655m56er">維基百科</h3>
<p>筆者在前端領域上接觸較深，對於後端或強型別語言的一些名詞或特性相對生疏。</p>
<p>關於 <a target="_blank" href="https://en.wikipedia.org/wiki/Intersection_type#Intersection_of_a_type_family">Intersection Types</a>  可以在維基百科看到更多擁有此特性的語言：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/70750be1-3b97-4661-8cec-abe36ff9a52a/1687542661.png.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2023-06-24_TypeScript%20-%20%E5%9C%A8%20interface%20%E4%B8%AD%E7%90%86%E8%A7%A3%20Pick%2C%20Omit%2C%20Partial%2C%20Intersection%20Types/1687542661.png.png" alt="1687542661.png.png" /></a></p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Nginx - 405 not allowed ft. Cloudflare]]></title><description><![CDATA[Nginx 405 not allowed 解決方式
修正 nginx 出現 405 方法拒絕

前言
本篇的錯誤發生於 cloudflare 設置 DDoS Protection 機制後發生
在 cloudflare 經過此機制後會向 server 發送 POST 事件
由於 nginx 預設禁止靜態資源配置 POST 請求
因此發生禁止的 Response
設置
將 nginx 設置關於 error_page 的捕捉
[ default.conf ]
server {

    listen...]]></description><link>https://blog.robby570.tw/nginx-405-not-allowed-ft-cloudflare</link><guid isPermaLink="true">https://blog.robby570.tw/nginx-405-not-allowed-ft-cloudflare</guid><category><![CDATA[nginx]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 08 Oct 2021 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-10-08_Nginx%20-%20405%20not%20allowed%20ft.%20Cloudflare/banner/1633700292.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nginx 405 not allowed 解決方式</p>
<p>修正 nginx 出現 405 方法拒絕</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-10-08_Nginx%20-%20405%20not%20allowed%20ft.%20Cloudflare/1633700292.png" alt="1633700292.png" /></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>本篇的錯誤發生於 <a target="_blank" href="https://www.cloudflare.com/">cloudflare</a> 設置 <a target="_blank" href="https://www.cloudflare.com/en-au/ddos-de/">DDoS Protection</a> 機制後發生</p>
<p>在 cloudflare 經過此機制後會向 server 發送 POST 事件</p>
<p>由於 nginx 預設禁止靜態資源配置 POST 請求</p>
<p>因此發生禁止的 Response</p>
<h2 id="heading-6kit572u">設置</h2>
<p>將 nginx 設置關於 <a target="_blank" href="http://nginx.org/en/docs/beginners_guide.html">error_page</a> 的捕捉</p>
<p>[ default.conf ]</p>
<pre><code class="lang-code">server {

    listen 80;
    listen [::]:80;

    return 301 https://$host$request_uri;
}

server {

    # ...

    error_page 405 =200 https://$host$request_uri;
}
</code></pre>
<h2 id="heading-5yd6icd">參考</h2>
<ul>
<li><a target="_blank" href="https://stackoverflow.com/questions/24415376/post-request-not-allowed-405-not-allowed-nginx-even-with-headers-included">https://stackoverflow.com/questions/24415376/post-request-not-allowed-405-not-allowed-nginx-even-with-headers-included</a></li>
<li><a target="_blank" href="https://www.izhangheng.com/nginx-405-not-allowed">https://www.izhangheng.com/nginx-405-not-allowed</a></li>
<li><a target="_blank" href="https://cloud.tencent.com/developer/article/1680056">https://cloud.tencent.com/developer/article/1680056</a></li>
<li><a target="_blank" href="https://github.com/denysvitali/nginx-error-pages">https://github.com/denysvitali/nginx-error-pages</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[GCP - Verify Google SSL certificates]]></title><description><![CDATA[紀錄 Google 自行管理 SSL 憑證時的驗證方式
在架設 GCP HTTPS 時遇到的小坑

先看看你現在有哪些憑證，如果沒有就先手動建立好吧～
gcloud compute target-https-proxies list

找到 Name 之後進行驗證
gcloud compute target-https-proxies describe TARGET_HTTPS_PROXY_NAME --global --format="get(sslCertificates)"

過一陣子查詢...]]></description><link>https://blog.robby570.tw/gcp-verify-google-ssl-certificates</link><guid isPermaLink="true">https://blog.robby570.tw/gcp-verify-google-ssl-certificates</guid><category><![CDATA[GCP]]></category><category><![CDATA[https]]></category><category><![CDATA[SSL]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Wed, 08 Sep 2021 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-09-08_GCP%20-%20Verify%20Google%20SSL%20certificates/banner/1631111893.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>紀錄 Google 自行管理 SSL 憑證時的驗證方式</p>
<p>在架設 GCP HTTPS 時遇到的小坑</p>
<p><a target="_blank" href="https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs?authuser=1#console"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-09-08_GCP%20-%20Verify%20Google%20SSL%20certificates/1631111893.png" alt="1631111893.png" /></a></p>
<p>先看看你現在有哪些憑證，如果沒有就先手動建立好吧～</p>
<pre><code class="lang-powershell">gcloud compute tar<span class="hljs-built_in">get-https</span><span class="hljs-literal">-proxies</span> list
</code></pre>
<p>找到 Name 之後進行驗證</p>
<pre><code class="lang-powershell">gcloud compute tar<span class="hljs-built_in">get-https</span><span class="hljs-literal">-proxies</span> describe TARGET_HTTPS_PROXY_NAME -<span class="hljs-literal">-global</span> -<span class="hljs-literal">-format</span>=<span class="hljs-string">"get(sslCertificates)"</span>
</code></pre>
<p>過一陣子查詢一下憑證就會亮綠燈囉</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/8a70c8f8-cce8-4e9d-bf58-99f3c61863aa/1631112266.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-09-08_GCP%20-%20Verify%20Google%20SSL%20certificates/1631112266.png" alt="1631112266.png" /></a></p>
<h3 id="heading-ref">REF</h3>
<p><a target="_blank" href="https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs">https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs</a></p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Kubectl - 利用 kubectl-graph 視覺化資源與分布吧！( Windows )]]></title><description><![CDATA[自己的星團珠寶自己拚！
將 Kubernetes 資源分布視覺化

steveteuber/kubectl-graph
前言
最近工作操作 kubernetes (K8s) 的時候，想說來視覺化一下資源分布使用情形
於是逛了 awesome-kubectl-plugins，找到了 kubectl-graph 一個視覺化的工具。
能以星型網的圖形介面呈現資源使用的狀況與節點分布，
是一個輕量簡易的星型拓撲視覺化工具。
由於  kubectl-graph  教學範例以 mac 說明，
因此筆者嘗試使...]]></description><link>https://blog.robby570.tw/kubectl-kubectl-graph-windows</link><guid isPermaLink="true">https://blog.robby570.tw/kubectl-kubectl-graph-windows</guid><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>自己的星團珠寶自己拚！</p>
<p>將 Kubernetes 資源分布視覺化</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/cypher-loki.png" alt="cypher-loki.png" /></p>
<p>steveteuber/kubectl-graph</p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>最近工作操作 <a target="_blank" href="https://kubernetes.io/">kubernetes</a> (K8s) 的時候，想說來視覺化一下資源分布使用情形</p>
<p>於是逛了 <a target="_blank" href="https://github.com/ishantanu/awesome-kubectl-plugins">awesome-kubectl-plugins</a>，找到了 <a target="_blank" href="https://github.com/steveteuber/kubectl-graph">kubectl-graph</a> 一個視覺化的工具。</p>
<p>能以星型網的圖形介面呈現資源使用的狀況與節點分布，</p>
<p>是一個輕量簡易的星型拓撲視覺化工具。</p>
<p>由於  <a target="_blank" href="https://github.com/steveteuber/kubectl-graph">kubectl-graph</a>  教學範例以 mac 說明，</p>
<p>因此筆者嘗試使用 Windows 進行操作展示。</p>
<h2 id="heading-5ymn572u5l2c5qwt">前置作業</h2>
<p>在一切作業開始前，希望您有以下基礎知識或環境設置：</p>
<ul>
<li>具有節點運作中的 Kubernetes 服務</li>
<li>熟悉且已安裝 kubetcl ( v1.12 or later )</li>
<li>已安裝 krew ( kubectl krew )</li>
<li>熟悉且已安裝 docker</li>
<li>具有 Java 環境，且最低版本要求 55 ( Java 11 )</li>
</ul>
<h2 id="heading-5a6j6kod5pwz5a24">安裝教學</h2>
<p>以下安裝步驟與 <a target="_blank" href="https://github.com/steveteuber/kubectl-graph">kubectl-graph</a> 相同，</p>
<p>也可以直接看著 repo 的 README 步驟做。</p>
<h3 id="heading-1-krew">1. 安裝 krew</h3>
<h6 id="heading-5aac5p6c5bey57at5pyj5q2k566h55cg5awx5lu25ymh5yv5lul55u05o6l6lez6ygo44cc">如果已經有此管理套件則可以直接跳過。</h6>
<p>到 <a target="_blank" href="https://krew.sigs.k8s.io/">Krew 官方網站</a> 安裝，在 <a target="_blank" href="https://krew.sigs.k8s.io/docs/user-guide/setup/install/">Install 頁面</a> 最下方可以找到 Windows 安裝步驟 。</p>
<p>確認是否安裝成功：</p>
<pre><code class="lang-powershell">kubectl krew version
</code></pre>
<h3 id="heading-2-graphviz">2. 安裝 graphviz</h3>
<p>在 kubectl-graph 有提到使用了 <a target="_blank" href="https://graphviz.org/">graphviz</a>，能夠使用 dot 命令產出靜態圖檔，</p>
<p>由於我們是 Windows 環境，乖乖的去官方網站下載安裝吧！</p>
<p>在<a target="_blank" href="https://graphviz.org/download/">下載頁面</a>中，選擇 Stable Windows install packages 下載就可以了：</p>
<ul>
<li>2.47.3 EXE installer for Windows 10 (64-bit): <a target="_blank" href="https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.47.3/stable_windows_10_cmake_Release_x64_graphviz-install-2.47.3-win64.exe">stable_windows_10_cmake_Release_x64_graphviz-install-2.47.3-win64.exe</a> (not all tools and libraries are included)</li>
</ul>
<p>環境變數建議在選擇的時候直接勾選好。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/3c7eacc9-1ef9-42d5-8f56-9f95cb9f8d66/1625673759.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/1625673759.png" alt="1625673759.png" /></a></p>
<p>確認是否安裝成功 ( -V 為大寫 )：</p>
<pre><code class="lang-powershell">dot <span class="hljs-literal">-V</span>
</code></pre>
<h3 id="heading-3-cypher-shell">3. 安裝 cypher-shell</h3>
<p>作者在圖形資料上使用了 <a target="_blank" href="https://neo4j.com/">Neo4j database</a>，為了與資料庫溝通，</p>
<p>在這裡你可以選擇安裝 <a target="_blank" href="https://neo4j.com/try-neo4j/">Neo4j</a>，或者選擇輕量地安裝 <a target="_blank" href="https://neo4j.com/download-center/#cyphershell">cypher-shell</a>。</p>
<p>筆者不建議安裝 <a target="_blank" href="https://neo4j.com/try-neo4j/">Neo4j</a>，因為步驟繁瑣麻煩…</p>
<p>直接用 docker pull image 就可以了！</p>
<p><a target="_blank" href="https://neo4j.com/download-center/#cyphershell">cypher-shell</a> 頁面選擇 <a target="_blank" href="https://dist.neo4j.org/cypher-shell/cypher-shell-4.3.0.zip">cypher-shell-4.3.0.zip</a>。</p>
<p>接著再將解壓縮後的 cypher-shell 資料夾路徑加入到環境變數。</p>
<h6 id="heading-java-11">請注意！！！！！Java 版本務必需要為 11以上。</h6>
<ul>
<li><a target="_blank" href="https://www.oracle.com/tw/java/technologies/javase-jdk11-downloads.html">Java SE Development Kit 11 Downloads</a></li>
</ul>
<p>確認是否安裝成功：</p>
<pre><code class="lang-powershell">cypher <span class="hljs-literal">-v</span>
</code></pre>
<h3 id="heading-4-kubectl-graph">4. 安裝 kubectl-graph</h3>
<p>終於可以安裝我們的主角了！</p>
<pre><code class="lang-powershell">kubectl krew install graph
</code></pre>
<p>確認是否安裝成功：</p>
<pre><code class="lang-powershell">kubectl graph <span class="hljs-literal">-h</span>
</code></pre>
<h2 id="heading-svg">使用教學 - SVG</h2>
<h3 id="heading-pods-dot-svg">將 pods 資訊輸出給 dot 建立 SVG 圖檔</h3>
<pre><code class="lang-powershell">kubectl graph pods -<span class="hljs-literal">-field</span><span class="hljs-literal">-selector</span> status.phase=Running <span class="hljs-literal">-n</span> kube<span class="hljs-literal">-system</span> | dot <span class="hljs-literal">-T</span> svg <span class="hljs-literal">-o</span> pods.svg
</code></pre>
<ul>
<li>kube-system：這是 Kubernetes 系統的 namesapce，你可以改成其他 namesapce</li>
</ul>
<h2 id="heading-visualize">使用教學 - Visualize</h2>
<h3 id="heading-6kaw6ka65yyw5pif54ua5zyw">視覺化星狀圖</h3>
<p>在這裡先利用 docker 安裝 <a target="_blank" href="https://neo4j.com/developer/docker-run-neo4j/">image</a> 並啟動：</p>
<pre><code class="lang-powershell">docker run -<span class="hljs-literal">-rm</span> <span class="hljs-literal">-p</span> <span class="hljs-number">7474</span>:<span class="hljs-number">7474</span> <span class="hljs-literal">-p</span> <span class="hljs-number">7687</span>:<span class="hljs-number">7687</span> <span class="hljs-literal">-e</span> NEO4J_AUTH=none neo4j
</code></pre>
<ul>
<li>NEO4J_AUTH：環境變數直接給 none 就可以了，或者你有自己的<a target="_blank" href="https://neo4j.com/docs/operations-manual/current/docker/introduction/#docker-auth">密碼設置</a></li>
</ul>
<p>完成後可以開啟網址檢視畫面：<a target="_blank" href="http://localhost:7474/">http://localhost:7474/</a></p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/3c7eacc9-1ef9-42d5-8f56-9f95cb9f8d66/1625674573.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/1625674573.png" alt="1625674573.png" /></a></p>
<p>由於我們沒設置密碼因此直接 connect 即可。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/3c7eacc9-1ef9-42d5-8f56-9f95cb9f8d66/1625676706.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/1625676706.png" alt="1625676706.png" /></a></p>
<p>登入後點選左上的 Database Information，確認 neo4j。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/1625674732.png" alt="1625674732.png" /></p>
<p>接著我們就可以利用指令將節點資訊匯入資料庫了：</p>
<pre><code class="lang-powershell">kubectl graph all <span class="hljs-literal">-n</span> kube<span class="hljs-literal">-system</span> <span class="hljs-literal">-o</span> cypher | cypher<span class="hljs-literal">-shell</span>
</code></pre>
<ul>
<li>kube-system：你可以改成其他 namesapce</li>
</ul>
<p>如果剛剛 neo4j 資料庫有設置帳號密碼，可以參考 <a target="_blank" href="https://neo4j.com/docs/operations-manual/current/tools/cypher-shell/">Syntax</a></p>
<pre><code class="lang-powershell">kubectl graph all <span class="hljs-literal">-n</span> loki <span class="hljs-literal">-o</span> cypher | cypher<span class="hljs-literal">-shell</span> <span class="hljs-literal">-u</span> neo4j <span class="hljs-literal">-p</span> secret
</code></pre>
<ul>
<li>cypher-shell [-u USERNAME, --username USERNAME]</li>
</ul>
<p>在網頁上就可以看到 Labels 有許多分類啦～～～～</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-07-08_Kubectl%20-%20%E5%88%A9%E7%94%A8%20kubectl-graph%20%E8%A6%96%E8%A6%BA%E5%8C%96%E8%B3%87%E6%BA%90%E8%88%87%E5%88%86%E5%B8%83%E5%90%A7%EF%BC%81(%20Windows%20)/1625676891.png" alt="1625676891.png" /></p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li><a target="_blank" href="https://github.com/steveteuber/kubectl-graph">kubectl-graph</a></li>
<li><a target="_blank" href="https://neo4j.com/developer/docker-run-neo4j/">docker-run-neo4j</a></li>
<li><a target="_blank" href="https://neo4j.com/docs/operations-manual/current/tools/cypher-shell/">cypher-shell</a></li>
<li><a target="_blank" href="https://github.com/neo4j/neo4j-javascript-driver/issues/660">The Client is unauthorized due to authentication failure</a></li>
<li><a target="_blank" href="https://www.oracle.com/tw/java/technologies/javase-jdk11-downloads.html">Java SE Development Kit 11 Downloads</a></li>
<li><a target="_blank" href="https://www.baeldung.com/java-lang-unsupportedclassversion">java.lang.UnsupportedClassVersionError</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Git - 被 Windows EOL 搞到血流成河]]></title><description><![CDATA[Git：我要看到血流成河！
關於 Windows 環境 EOL 的設定問題

前言
眼尖的你會發現 Banner 怎麼會是 git，沒錯，筆者就是被 git 搞到 血流成河 ！
本次經驗主要在 vscode 開發上遇到了 EOL 的問題，
雖然這類文章滿街跑，不過筆者也仍紀錄自己也被搞的經歷
原來大家都被搞過啊
如果你想知道怎麼處理，請直接跳到：處理方式
因為更換了新電腦，所有環境都要重設定，
想說把平常在 mac 開發的專案拿到 Windows 搭配新的螢幕寫。
因為也換了一台 27" 2K ...]]></description><link>https://blog.robby570.tw/git-windows-eol</link><guid isPermaLink="true">https://blog.robby570.tw/git-windows-eol</guid><category><![CDATA[Git]]></category><category><![CDATA[Visual Studio Code]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Thu, 11 Mar 2021 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-03-11_Git%20-%20%E8%A2%AB%20Windows%20EOL%20%E6%90%9E%E5%88%B0%E8%A1%80%E6%B5%81%E6%88%90%E6%B2%B3/banner/1615452125.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Git：我要看到血流成河！</p>
<p>關於 Windows 環境 EOL 的設定問題</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/8aae20a4-239b-4ced-8818-fd143afca27d/1615452125.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-03-11_Git%20-%20%E8%A2%AB%20Windows%20EOL%20%E6%90%9E%E5%88%B0%E8%A1%80%E6%B5%81%E6%88%90%E6%B2%B3/1615452125.png" alt="1615452125.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>眼尖的你會發現 Banner 怎麼會是 git，沒錯，筆者就是被 git 搞到 血流成河 ！</p>
<p>本次經驗主要在 <a target="_blank" href="https://code.visualstudio.com/">vscode</a> 開發上遇到了 <a target="_blank" href="https://zh.wikipedia.org/wiki/%E6%8F%9B%E8%A1%8C">EOL</a> 的問題，</p>
<p>雖然這類文章滿街跑，不過筆者也仍紀錄自己也被搞的經歷</p>
<p><em>原來大家都被搞過啊</em></p>
<p>如果你想知道怎麼處理，請直接跳到：<a class="post-section-overview" href="#1">處理方式</a></p>
<p>因為更換了新電腦，所有環境都要重設定，</p>
<p>想說把平常在 <a target="_blank" href="https://www.google.com/search?q=mac&amp;oq=mac&amp;aqs=chrome..69i57j0i433j69i65l3j69i61l3.196j0j4&amp;sourceid=chrome&amp;ie=UTF-8">mac</a> 開發的專案拿到 <a target="_blank" href="https://www.google.com/search?q=Windows&amp;oq=Windows&amp;aqs=chrome..69i57j69i59l2j69i65l3j69i60l2.226j0j9&amp;sourceid=chrome&amp;ie=UTF-8">Windows</a> 搭配新的螢幕寫。</p>
<p><em>因為也換了一台 27" 2K 曲面螢幕  :D</em></p>
<p>於是專案 clone 並開啟後，結果 <a target="_blank" href="https://prettier.io/">prettier</a> 和 <a target="_blank" href="https://eslint.org/">eslint</a> 噴了一堆 WARNING</p>
<p>：<a target="_blank" href="https://eslint.org/docs/rules/eol-last">require or disallow newline at the end of files (eol-last)</a></p>
<p>原本想說調整 <a target="_blank" href="https://code.visualstudio.com/">vscode</a> 的 Editor: Unfold On Click After End Of Line</p>
<p>結果即使檔案開啟使用 <a target="_blank" href="https://zh.wikipedia.org/wiki/%E6%8F%9B%E8%A1%8C">LF</a>，但問題仍會被 <a target="_blank" href="https://git-scm.com/">git</a> 的 <a target="_blank" href="https://medium.com/@gabrielschade/how-git-diff-works-a-sample-with-f-af3e3737963">diff algorithms</a> 判斷到</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/8aae20a4-239b-4ced-8818-fd143afca27d/1615450639.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-03-11_Git%20-%20%E8%A2%AB%20Windows%20EOL%20%E6%90%9E%E5%88%B0%E8%A1%80%E6%B5%81%E6%88%90%E6%B2%B3/1615450639.png" alt="1615450639.png" /></a></p>
<p><strong>Dear：</strong>　</p>
<p>　　　你是不是在安裝 git 的時候，一鍵完成？ </p>
<h2 id="heading-6kej5rg65pa55byp">解決方式</h2>
<p>筆者採取最快最乾淨最簡單的方式</p>
<p>FAST . EASY . CLEAN . ONCE</p>
<h3 id="heading-git">請移除 git，重新安裝</h3>
<p>並且在 Configuring the line ending conversions 步驟！</p>
<p>預設是第一個，他會把你符號轉成 CRLF </p>
<p>就算你提交時會幫你改回 LF...</p>
<p>但你在 develop 階段會被 lint 誤判啊！！！ </p>
<p>所以請改為第三個：</p>
<p>Checkout as-is, commit as-is | 你怎麼簽出就怎麼提交</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/8aae20a4-239b-4ced-8818-fd143afca27d/1615451193.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-03-11_Git%20-%20%E8%A2%AB%20Windows%20EOL%20%E6%90%9E%E5%88%B0%E8%A1%80%E6%B5%81%E6%88%90%E6%B2%B3/1615451193.png" alt="1615451193.png" /></a></p>
<p>這樣你的 repo git 就不會亂掉了 ~</p>
<p>還敢不 Step By Step 啊你</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/8aae20a4-239b-4ced-8818-fd143afca27d/1615451953.jpg"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2021-03-11_Git%20-%20%E8%A2%AB%20Windows%20EOL%20%E6%90%9E%E5%88%B0%E8%A1%80%E6%B5%81%E6%88%90%E6%B2%B3/1615451953.jpg" alt="1615451953.jpg" /></a></p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li><a target="_blank" href="https://blog.miniasp.com/post/2013/09/15/Git-for-Windows-Line-Ending-Conversion-Notes">Git 在 Windows 平台處理斷行字元 (CRLF) 的注意事項</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Vue - Extend Vue component with values for props and slots]]></title><description><![CDATA[談討關於繼承組件以及覆寫 props 與 slots！
關於重新包裝組件的方式，本篇圍繞著 Render Functions 與 Template。


banner: Persist Access Token with Vue.js

前言
在工作誤打誤撞下，開始接觸 Vue 一陣子，
在環境中使用了 Quasar framework。
在組件高複用下，經常為了統一樣式，增加不少行數，
因此筆者把常用的組件設定樣式後再封裝一次。
為了讓組件能夠持續使用原有的 props 以及 slot，
設定...]]></description><link>https://blog.robby570.tw/vue-extend-vue-component-with-values-for-props-and-slots</link><guid isPermaLink="true">https://blog.robby570.tw/vue-extend-vue-component-with-values-for-props-and-slots</guid><category><![CDATA[vue]]></category><category><![CDATA[template]]></category><category><![CDATA[components]]></category><category><![CDATA[functions]]></category><category><![CDATA[functional]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 12 Dec 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/banner/1607715557.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>談討關於繼承組件以及覆寫 props 與 slots！</p>
<p>關於重新包裝組件的方式，本篇圍繞著 Render Functions 與 Template。</p>
<p><a target="_blank" href="https://webdevchallenges.com/persist-access-token-with-vue-js/"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607715557.jpg" alt="1607715557.jpg" /></a></p>
<ul>
<li>banner: <a target="_blank" href="http://webdevchallenges.com/persist-access-token-with-vue-js/">Persist Access Token with Vue.js</a></li>
</ul>
<h2 id="heading-5ymn6kia">前言</h2>
<p>在工作誤打誤撞下，開始接觸 <a target="_blank" href="https://vuejs.org/">Vue</a> 一陣子，</p>
<p>在環境中使用了 <a target="_blank" href="https://quasar.dev/">Quasar framework</a>。</p>
<p>在組件高複用下，經常為了統一樣式，增加不少行數，</p>
<p>因此筆者把常用的組件設定樣式後再封裝一次。</p>
<p>為了讓組件能夠持續使用原有的 <a target="_blank" href="https://vuejs.org/v2/api/#props">props</a> 以及 <a target="_blank" href="https://vuejs.org/v2/api/#slot">slot</a>，</p>
<p>設定上遇到了不少困難。</p>
<p>本文從理解簡單的 component 建立，</p>
<p>接著實作 <a target="_blank" href="https://vuejs.org/v2/guide/components.html">components</a> 與 <a target="_blank" href="https://vuejs.org/v2/api/#extends">extends</a>，</p>
<p>以及 <a target="_blank" href="https://vuejs.org/v2/api/#template">template</a> 與 <a target="_blank" href="https://vuejs.org/v2/guide/render-function.html">render functoins</a> 的方式。</p>
<p>由於筆者屬於新手入門，當然本篇也以初學者的角度分享，</p>
<p>環境是使用 <a target="_blank" href="https://v3.vuejs.org/api/options-api.html">options api</a> 的方式，所以不探討 <a target="_blank" href="https://composition-api.vuejs.org/">composition api</a>。</p>
<p>專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/vue-extend-slot-example">https://github.com/explooosion/vue-extend-slot-example</a></p>
<hr />
<h2 id="heading-55uu6yye">目錄</h2>
<ul>
<li><a class="post-section-overview" href="#0">前置作業</a></li>
<li><a class="post-section-overview" href="#1">ㄧ、簡單的組件建立</a></li>
<li><a class="post-section-overview" href="#2">二、使用 components 包裝 Button</a></li>
<li><a class="post-section-overview" href="#3">三、使用 extends 繼承 Button</a></li>
</ul>
<hr />
<h2 id="heading-5ymn572u5l2c5qwt">前置作業</h2>
<p>本專案使用 <a target="_blank" href="https://cli.vuejs.org/guide/prototyping.html">@vue/cli</a> 建立</p>
<pre><code class="lang-bash">npm install -g @vue/cli @vue/cli-service-global
<span class="hljs-comment"># or</span>
yarn global add @vue/cli @vue/cli-service-global
</code></pre>
<pre><code class="lang-bash">vue create hello-world
</code></pre>
<p>為了簡單使用 <a target="_blank" href="https://fontawesome.com/">fontawesome</a>，在 index.html 新增 CDN</p>
<p>[ public / index.html ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://pro.fontawesome.com/releases/v5.10.0/css/all.css"</span>
    <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span> /&gt;</span>
</code></pre>
<hr />
<h2 id="heading-44sn44cb57ch5zau55qe57we5lu25bu656ul">ㄧ、簡單的組件建立</h2>
<p>簡單建立一個 Button 的 component。</p>
<p>利用 v-on, v-bind 方式將 listeners 與 attrs 綁給 button 。</p>
<p>在範例組件中，筆者提供了：</p>
<ul>
<li>3 個 props ( type, label, bold )</li>
<li>2 種 slots ( default, #after )</li>
<li>1 個 event ( greet )</li>
</ul>
<p>[ Button.vue ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
    <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span>
    <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"$attrs"</span>
    <span class="hljs-attr">:type</span>=<span class="hljs-string">"type"</span>
    <span class="hljs-attr">:style</span>=<span class="hljs-string">"`font-weight: ${bold ? 'bold' : 'normal'}`"</span>
    @<span class="hljs-attr">click</span>=<span class="hljs-string">"$emit('greet', 'Hello Vue')"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
    {{ label }}
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"after"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Button"</span>,
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">type</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"button"</span>,
    },
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">""</span>,
    },
    <span class="hljs-attr">bold</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-literal">false</span>,
    },
  },
};
</code></pre>
<ul>
<li>type: buttoon type [ button, submit, reset ]</li>
<li>label: button 的文字</li>
<li>bold: 如果 true 則為粗體字</li>
</ul>
<p>使用範例：</p>
<p>[ App.vue ]</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- with props --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Button"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- with props, default slot --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Submit"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">:bold</span>=<span class="hljs-string">"true"</span>&gt;</span>#<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>

<span class="hljs-comment">&lt;!-- with props, after slot --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Reset"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"reset"</span> <span class="hljs-attr">:bold</span>=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span>@<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
</code></pre>
<p>畫面預覽：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607763821.png" alt="1607763821.png" /></p>
<hr />
<h2 id="heading-components-button">二、使用 components 包裝 Button</h2>
<p>使用 components 的方式可以想成你又把 Button 組件再包一層起來。</p>
<p>一個 wrapper 的概念，而 DOM 實際上只會有一層 element render 出來</p>
<p>假如想重新封裝一次 Button，並且預先指定好 props 與 slots，</p>
<p>以下範例筆者讓 label 為 Reset，type 為 reset，</p>
<p>並且 slot #after 塞入了一個 redo icon。</p>
<p>[ ButtonTemplate.vue ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">:label</span>=<span class="hljs-string">"label"</span> <span class="hljs-attr">:type</span>=<span class="hljs-string">"type"</span> <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span> <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"$attrs"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-redo"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"ButtonTemplate"</span>,
  <span class="hljs-attr">components</span>: {
    Button,
  },
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">type</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"reset"</span>,
    },
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Reset"</span>,
    },
  },
};
</code></pre>
<p>三種使用範例：</p>
<p>[ App.vue ]</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- basic --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonTemplate</span> @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- with props --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonTemplate</span>
  <span class="hljs-attr">label</span>=<span class="hljs-string">"New Reset"</span>
  <span class="hljs-attr">type</span>=<span class="hljs-string">"reset"</span>
  <span class="hljs-attr">:bold</span>=<span class="hljs-string">"true"</span>
  @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span>
/&gt;</span>

<span class="hljs-comment">&lt;!-- with slots --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonTemplate</span> @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-sync-alt"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-sync-alt"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ButtonTemplate</span>&gt;</span>
</code></pre>
<p>畫面預覽：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607763861.png" alt="1607763861.png" /></p>
<p>關於上述 3 種使用情境：</p>
<h4 id="heading-1-basic">1. basic</h4>
<p>直接使用我們重新封裝過的，沒甚麼問題。</p>
<h4 id="heading-2-with-props">2. with props</h4>
<p>再次傳遞 Button 組件提供的 props，在外觀上似乎沒問題，</p>
<p>但是查看 DOM tree，bold 被誤當 attrs 加上去了。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/07b62e54-0c5b-42da-8a53-9a6a5fc56585/1607760109.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607760109.png" alt="1607760109.png" /></a></p>
<p>因為我們沒有在 ButtonTemplate.vue 告知他是 props</p>
<p>這時你可以把 Button 的 props 透過 spread 方式： ...Button.props 補進來：</p>
<p>[ ButtonTemplate.vue ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"ButtonTemplate"</span>,
  <span class="hljs-attr">components</span>: {
    Button,
  },
  <span class="hljs-attr">props</span>: {
    ...Button.props, <span class="hljs-comment">// add here</span>
    <span class="hljs-attr">type</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"reset"</span>,
    },
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Reset"</span>,
    },
  },
};
</code></pre>
<p>重新檢視原始碼，就不會被當屬性顯示！</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607761183.png" alt="1607761183.png" /></p>
<p>雖然沒被當屬性顯示，但我們傳遞的 bold 也失效了，</p>
<p>因為你接收了 props: bold 但沒做任何處理。</p>
<p>沒有成功接收到 props: bold 的畫面：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607763893.png" alt="1607763893.png" /></p>
<p>這時你可以在 v-bind 補上 $options.propsData 就沒問題囉！</p>
<p>[ ButtonTemplate.vue ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">:label</span>=<span class="hljs-string">"label"</span>
    <span class="hljs-attr">:type</span>=<span class="hljs-string">"type"</span>
    <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span>
    <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"[$attrs, $options.propsData]"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-redo"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>成功後的畫面：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607763930.png" alt="1607763930.png" /></p>
<h4 id="heading-3-with-slots">3. with slots</h4>
<p>這段我們將 default, 與 name 為 #after 的 slots 傳遞進去，但很明顯地，並沒有成功傳入。</p>
<p>可以嘗試在 mounted 印出 <a target="_blank" href="https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots-with-the-slot-scope-Attribute">this.$scopedSlots</a>，可以發現多了 after 與 default </p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/07b62e54-0c5b-42da-8a53-9a6a5fc56585/1607761811.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607761811.png" alt="1607761811.png" /></a></p>
<p>我們可以利用 v-for 將這段 slots 補進來：</p>
<p>[ ButtonTemplate.vue ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">:label</span>=<span class="hljs-string">"label"</span> <span class="hljs-attr">:type</span>=<span class="hljs-string">"type"</span> <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span> <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"$attrs"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-redo"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">"(_, slot) of $scopedSlots"</span> <span class="hljs-attr">v-slot:</span>[<span class="hljs-attr">slot</span>]=<span class="hljs-string">"scope"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">:name</span>=<span class="hljs-string">"slot"</span> <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"scope"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>畫面預覽：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607763954.png" alt="1607763954.png" /></p>
<h4 id="heading-components">完整的 components 方式：</h4>
<p>[ ButtonTemplate.vue ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">:label</span>=<span class="hljs-string">"label"</span>
    <span class="hljs-attr">:type</span>=<span class="hljs-string">"type"</span>
    <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span>
    <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"[$attrs, $options.propsData]"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-redo"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">"(_, slot) of $scopedSlots"</span> <span class="hljs-attr">v-slot:</span>[<span class="hljs-attr">slot</span>]=<span class="hljs-string">"scope"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">:name</span>=<span class="hljs-string">"slot"</span> <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"scope"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"ButtonTemplate"</span>,
  <span class="hljs-attr">components</span>: {
    Button,
  },
  <span class="hljs-attr">props</span>: {
    ...Button.props,
    <span class="hljs-attr">type</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"reset"</span>,
    },
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Reset"</span>,
    },
  },
};
</code></pre>
<p>好處是你可以設定 <a target="_blank" href="https://vuejs.org/v2/guide/components-registration.html">name</a>，透過 <a target="_blank" href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd">Vue.js devtools</a> 找到你的組件。</p>
<p>同時你也可以發現，ButtonTemplate 其實是個 wrapper 概念。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607768997.png" alt="1607768997.png" /></p>
<hr />
<h2 id="heading-extends-button">三、使用 extends 繼承 Button</h2>
<p>使用 extends 的方式，有別於 component，並不是又包了一層，</p>
<p>而是繼承原有的組件，並且再覆寫原本的 render 內容。</p>
<p>本篇暫不討論 <a target="_blank" href="https://vuejs.org/v2/guide/mixins.html">mixins</a>。</p>
<p>使用 extends 的方式，以下範例筆者讓 label 為 Search，</p>
<p>後續範例則在 slot 塞入了一個 search icon。</p>
<p>[ ButtonFunctional.vue ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">extends</span>: Button,
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Search"</span>,
    },
  },
};
</code></pre>
<ul>
<li>label: 在順序上，如果沒有傳遞 props，就會使用 default value</li>
</ul>
<p>三種使用範例：</p>
<p>[ App.vue ]</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- basic --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonFunctional</span> @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- with props --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonFunctional</span>
  <span class="hljs-attr">label</span>=<span class="hljs-string">"New Search"</span>
  <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
  <span class="hljs-attr">:bold</span>=<span class="hljs-string">"true"</span>
  @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span>
/&gt;</span>

<span class="hljs-comment">&lt;!-- with slots --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ButtonFunctional</span> @<span class="hljs-attr">greet</span>=<span class="hljs-string">"onGreet"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-search-plus"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">after</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-search-plus"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ButtonFunctional</span>&gt;</span>
</code></pre>
<p>畫面預覽：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607766194.png" alt="1607766194.png" /></p>
<p>關於上述 3 種使用情境：</p>
<p>似乎都是沒甚麼問題。</p>
<p>如果想讓特定 slot 為 search icon，該怎麼做？</p>
<p>由於我們使用 extends，因此改使用 <a target="_blank" href="https://vuejs.org/v2/guide/render-function.html">render functions</a> 方式。</p>
<p>相關 render functions 也可參考：<a target="_blank" href="https://mini-ghost.dev/blog/vue-render-function-dynamic-component/">簡單的 Vue Render Functions 與動態組件的綜合應用</a></p>
<p>在 render functions 中，筆者參考官方的文件 <a target="_blank" href="https://vuejs.org/v2/guide/render-function.html#Slots">Slots</a>，</p>
<p>使用 scopedSlots 方式去設定，其中：</p>
<ul>
<li>h: <code>CreateElement</code> </li>
<li>ctx: <code>RenderContext</code> </li>
</ul>
<p>[ ButtonFunctional.vue ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">extends</span>: Button,
  <span class="hljs-attr">functional</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Search"</span>,
    },
  },
  render(h, ctx) {
    <span class="hljs-keyword">return</span> h(
      <span class="hljs-string">"Button"</span>,
      {
        ...ctx.data,
        <span class="hljs-attr">props</span>: {
          ...ctx.props,
          <span class="hljs-comment">/** use props or extend here */</span>
        },
        <span class="hljs-attr">scopedSlots</span>: {
          ...ctx.scopedSlots,
          <span class="hljs-attr">default</span>: <span class="hljs-function">() =&gt;</span> h(<span class="hljs-string">"i"</span>, { <span class="hljs-attr">class</span>: [<span class="hljs-string">"fas"</span>, <span class="hljs-string">"fa-search"</span>] }),
          <span class="hljs-comment">/** use slots or extend here */</span>
        },
      },
      ...(ctx.children || [])
    );
  },
};
</code></pre>
<ul>
<li>functional: true。由於我們組件屬於無狀態，又需要接收 props，因此設置為 true，把組件寫成 functinal component。</li>
<li>props: 預設值可寫在最外層的 props，如果寫在 render functions 的 props，則代表強制蓋掉指定的 props</li>
<li>scopedSlots:  將原本的 scopedSlots 展開，並於 default 處設定我們的 search icon，你也可以把 default: 根據 slot name 改成 after: </li>
<li>ctx.children: 如果我們有在 ButtonFunctional 標籤內新增內容，那就是 child 囉！</li>
</ul>
<p>關於 functional：</p>
<p>由於該組件不處理 data，我們可以轉換成無狀態的寫法，</p>
<p>筆者使用 Functional Component + render function 方式，因此把 functional 設置為 true。</p>
<ul>
<li><a target="_blank" href="https://medium.com/js-dojo/vue-js-functional-components-what-why-and-when-439cfaa08713">https://vuejs.org/v2/api/#functional</a></li>
<li><a target="_blank" href="https://medium.com/js-dojo/vue-js-functional-components-what-why-and-when-439cfaa08713">Vue.js functional components: what, why, and when?</a></li>
</ul>
<p>當然你也可以改使用 Functional Component + Vue Template，改寫後跟前面組件 ButtonTemplate 結構類似，如下：</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">functional</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">:label</span>=<span class="hljs-string">"props.label"</span>
    <span class="hljs-attr">v-on</span>=<span class="hljs-string">"$listeners"</span>
    <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"[$attrs, $options.propsData]"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fas fa-search"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">"(_, slot) of $scopedSlots"</span> <span class="hljs-attr">v-slot:</span>[<span class="hljs-attr">slot</span>]=<span class="hljs-string">"scope"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">:name</span>=<span class="hljs-string">"slot"</span> <span class="hljs-attr">v-bind</span>=<span class="hljs-string">"scope"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">extends</span>: Button,
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">label</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">default</span>: <span class="hljs-string">"Search"</span>,
    },
  },
};
</code></pre>
<p>關於 children  範例：</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ButtonFunctional</span>&gt;</span>
  child1
  <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>child2<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ButtonFunctional</span>&gt;</span>
</code></pre>
<p>畫面預覽：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607768921.png" alt="1607768921.png" /></p>
<p>有個小缺點是你無法透過 <a target="_blank" href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd">Vue.js devtools</a> 找到你的組件。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607768971.png" alt="1607768971.png" /></p>
<hr />
<p>專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/vue-extend-slot-example">https://github.com/explooosion/vue-extend-slot-example</a></p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/07b62e54-0c5b-42da-8a53-9a6a5fc56585/1607769127.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-12-12_Vue%20-%20Extend%20Vue%20component%20with%20values%20for%20props%20and%20slots/1607769127.png" alt="1607769127.png" /></a></p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li><a target="_blank" href="https://vuejs.org/v2/guide/">https://vuejs.org/v2/guide/</a></li>
<li><a target="_blank" href="https://v3.vuejs.org/api/options-api.html">https://v3.vuejs.org/api/options-api.html</a></li>
<li><a target="_blank" href="https://vuejs.org/v2/guide/render-function.html#Slots">https://vuejs.org/v2/guide/render-function.html#Slots</a></li>
<li><a target="_blank" href="https://vuejs.org/v2/guide/render-function.html#Slots">https://composition-api.vuejs.org/</a></li>
<li><a target="_blank" href="https://mini-ghost.dev/blog/vue-render-function-dynamic-component/">https://mini-ghost.dev/blog/vue-render-function-dynamic-component/</a></li>
<li><a target="_blank" href="https://stackoverflow.com/questions/57493900/how-to-extend-vue-component-with-default-values-for-props-and-slots">https://stackoverflow.com/questions/57493900/how-to-extend-vue-component-with-default-values-for-props-and-slots</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[GCP - 使用 Github Actions 部署 React 到 GKE]]></title><description><![CDATA[丟給 github actions 跑就對啦！
利用 Github Actions 部署你的專案到 GKE Cluster 上吧！


GitHub Actions self-hosted runners on Google Cloud

前言
有關 GCP 與 Github Actons 的設定，大多於前一篇文章，說明詳細。

GCP - 使用 Github Actions 部署 React 到 Cloud Run

因此本篇略過大多設定，以重點設定的方式說明。
在 Github Action...]]></description><link>https://blog.robby570.tw/gcp-github-actions-react-gke</link><guid isPermaLink="true">https://blog.robby570.tw/gcp-github-actions-react-gke</guid><category><![CDATA[Deploy ]]></category><category><![CDATA[Docker]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Google]]></category><category><![CDATA[k8s]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 10 Oct 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/banner/1602267554.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>丟給 github actions 跑就對啦！</p>
<p>利用 Github Actions 部署你的專案到 GKE Cluster 上吧！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602267554.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602267554.png" alt="1602267554.png" /></a></p>
<ul>
<li><a target="_blank" href="https://github.blog/2020-08-04-github-actions-self-hosted-runners-on-google-cloud/">GitHub Actions self-hosted runners on Google Cloud</a></li>
</ul>
<h2 id="heading-5ymn6kia">前言</h2>
<p>有關 GCP 與 Github Actons 的設定，大多於前一篇文章，說明詳細。</p>
<ul>
<li><a target="_blank" href="https://dotblogs.com.tw/explooosion/2020/10/09/143330">GCP - 使用 Github Actions 部署 React 到 Cloud Run</a></li>
</ul>
<p>因此本篇略過大多設定，以重點設定的方式說明。</p>
<p>在 Github Actions 的部分，</p>
<p>參考使用 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions/tree/master/example-workflows/gke">Google Kubernetes Engine - GitHub Actions</a> 的範例建置。</p>
<p>本篇文章專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/react-gke-deploy-example">https://github.com/explooosion/react-gke-deploy-example</a></p>
<h2 id="heading-1-service-account">1. 新增服務帳戶 Service Account</h2>
<p>根據 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions/tree/master/example-workflows/gke">Google Kubernetes Engine - GitHub Actions</a> 的建議，</p>
<p>新增服務帳戶，並提供兩個權限：</p>
<ul>
<li><p><code>Kubernetes Engine Developer</code> - allows deploying to GKE</p>
</li>
<li><p><code>Storage Admin</code> - allows publishing to Container Registry</p>
</li>
</ul>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602268882.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602268882.png" alt="1602268882.png" /></a></p>
<p>接著取出 Service Account Key，將 JSON file 下載保存。</p>
<p>內容結構如下：</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"service_account"</span>,
  <span class="hljs-attr">"project_id"</span>: <span class="hljs-string">"project-id"</span>,
  <span class="hljs-attr">"private_key_id"</span>: <span class="hljs-string">"key-id"</span>,
  <span class="hljs-attr">"private_key"</span>: <span class="hljs-string">"-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n"</span>,
  <span class="hljs-attr">"client_email"</span>: <span class="hljs-string">"service-account-email"</span>,
  <span class="hljs-attr">"client_id"</span>: <span class="hljs-string">"client-id"</span>,
  <span class="hljs-attr">"auth_uri"</span>: <span class="hljs-string">"https://accounts.google.com/o/oauth2/auth"</span>,
  <span class="hljs-attr">"token_uri"</span>: <span class="hljs-string">"https://accounts.google.com/o/oauth2/token"</span>,
  <span class="hljs-attr">"auth_provider_x509_cert_url"</span>: <span class="hljs-string">"https://www.googleapis.com/oauth2/v1/certs"</span>,
  <span class="hljs-attr">"client_x509_cert_url"</span>: <span class="hljs-string">"https://www.googleapis.com/robot/v1/metadata/x509/service-account-email"</span>
}
</code></pre>
<p>你也可以選擇使用 gcloud 命令方式取得金鑰：</p>
<pre><code class="lang-bash">gcloud iam service-accounts keys create ./key.json --iam-account  --iam-account &lt;sa-name&gt;@&lt;project-id&gt;.iam.gserviceaccount.com
</code></pre>
<h2 id="heading-2-kubernetes">2.  建立 Kubernetes 叢集</h2>
<p>到 Kubernetes Engine 建立新的叢集 cluster。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602269060.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602269060.png" alt="1602269060.png" /></a></p>
<p>在位置區域的配置，筆者選擇 asia-east1-a。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602269082.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602269082.png" alt="1602269082.png" /></a></p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602269105.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602269105.png" alt="1602269105.png" /></a></p>
<h2 id="heading-3-cra-create-react-apphttpszh-hantreactjsorgdocscreate-a-new-react-apphtmlcreate-react-app">3.  建立 CRA 專案 ( <a target="_blank" href="https://zh-hant.reactjs.org/docs/create-a-new-react-app.html#create-react-app">Create React App</a> )</h2>
<p>接著可以建立專案：</p>
<pre><code>npx create-react-app my-app
</code></pre><p>如果你會使用 Docker 部署環境，那麼你可以替換成你想部署的專案。</p>
<h2 id="heading-4-github-actions-workflow-syntax-for-github-actionshttpsdocsgithubcomenfree-pro-teamlatestactionsreferenceworkflow-syntax-for-github-actions">4. 建立 Github Actions ( <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions">Workflow syntax for GitHub Actions</a> )</h2>
<p>[ <strong>Dockerfile</strong> ]</p>
<p>可根據自己的專案撰寫，此專案筆者將專案 build 後放置於 nginx html。</p>
<pre><code>FROM node:<span class="hljs-number">10</span> AS Builder

ENV NPM_CONFIG_LOGLEVEL info

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY . .

ARG GENERATE_SOURCEMAP=<span class="hljs-literal">false</span>

RUN yarn install &amp;&amp; yarn build

FROM nginx:<span class="hljs-number">1.13</span><span class="hljs-number">.3</span>-alpine

RUN rm -rf /usr/share/nginx/html<span class="hljs-comment">/*
COPY --from=Builder /usr/src/app/build /usr/share/nginx/html</span>
</code></pre><p>[ <a target="_blank" href="https://github.com/explooosion/react-gcp-deploy-example/blob/master/.dockerignore">.dockerignore</a> ]</p>
<p>加入一點忽略規則。</p>
<pre><code>Dockerfile
README.md
node_modules
npm-debug.log
</code></pre><p>[ .github / workflows / main.yml ]</p>
<p>建立 workflows，內容參考 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions/tree/master/example-workflows/gke">gke</a>。</p>
<pre><code>name: Build and Deploy to GKE

<span class="hljs-attr">on</span>:
  push:
    branches:
    - master

<span class="hljs-attr">env</span>:
  PROJECT_ID: ${{ secrets.GKE_PROJECT }}
  <span class="hljs-attr">GKE_CLUSTER</span>: cluster<span class="hljs-number">-1</span>    # TODO: update to cluster name
  <span class="hljs-attr">GKE_ZONE</span>: asia-east1-a   # TODO: update to cluster zone
  <span class="hljs-attr">DEPLOYMENT_NAME</span>: gke-app # TODO: update to deployment name
  <span class="hljs-attr">IMAGE</span>: my-image

<span class="hljs-attr">jobs</span>:
  setup-build-publish-deploy:
    name: Setup, Build, Publish, and Deploy
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
    - name: Checkout
      <span class="hljs-attr">uses</span>: actions/checkout@v2

    # Setup gcloud CLI
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      <span class="hljs-attr">with</span>:
        version: <span class="hljs-string">'290.0.1'</span>
        <span class="hljs-attr">service_account_key</span>: ${{ secrets.GKE_SA_KEY }}
        <span class="hljs-attr">project_id</span>: ${{ secrets.GKE_PROJECT }}

    # Configure Docker to use the gcloud command-line tool <span class="hljs-keyword">as</span> a credential
    # helper <span class="hljs-keyword">for</span> authentication
    - run: |-
        gcloud --quiet auth configure-docker
    # Get the GKE credentials so we can deploy to the cluster
    - run: |-
        gcloud container clusters get-credentials <span class="hljs-string">"$GKE_CLUSTER"</span> --zone <span class="hljs-string">"$GKE_ZONE"</span>
    # Build the Docker image
    - name: Build
      <span class="hljs-attr">run</span>: |-
        docker build \
          --tag <span class="hljs-string">"gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA"</span> \
          --build-arg GITHUB_SHA=<span class="hljs-string">"$GITHUB_SHA"</span> \
          --build-arg GITHUB_REF=<span class="hljs-string">"$GITHUB_REF"</span> \
          .
    # Push the Docker image to Google Container Registry
    - name: Publish
      <span class="hljs-attr">run</span>: |-
        docker push <span class="hljs-string">"gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA"</span>
    # <span class="hljs-built_in">Set</span> up kustomize
    - name: <span class="hljs-built_in">Set</span> up Kustomize
      <span class="hljs-attr">run</span>: |-
        curl -sfLo kustomize https:<span class="hljs-comment">//github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64</span>
        chmod u+x ./kustomize
    # Deploy the Docker image to the GKE cluster
    - name: Deploy
      <span class="hljs-attr">run</span>: |-
        ./kustomize edit set image gcr.io/PROJECT_ID/IMAGE:TAG=gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA
        ./kustomize build . | kubectl apply -f -
        kubectl rollout status deployment/$DEPLOYMENT_NAME
        kubectl get services -o wide
</code></pre><p>在 yml 中，使用了幾個需要在 github 建立 secret 的參數：</p>
<ul>
<li>GKE_PROJECT：專案 id</li>
<li>GKE_SA_KEY：專案金鑰，建立 secret 時，務必整個 JSON 內容貼上</li>
</ul>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602272016.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602272016.png" alt="1602272016.png" /></a></p>
<p>而 yml 自身也使用了幾個 env 參數：</p>
<ul>
<li>PROJECT_ID: ${{ secrets.GKE_PROJECT }}  # 自動會帶入來自 secret 設定</li>
<li>GKE_CLUSTER: cluster-1 # 輸入你所建立的叢集名稱</li>
<li>GKE_ZONE: asia-east1-a # 輸入你的叢集主要地區 zone</li>
<li>DEPLOYMENT_NAME: gke-app # 部署名稱, 此名在其他 yml 需要一致</li>
<li>IMAGE: my-image # docker 映像名稱</li>
</ul>
<p>關於 zone，可以回到 GCP 後台叢集查找，</p>
<p>或者使用指令查看：</p>
<pre><code class="lang-bash">gcloud container clusters list
</code></pre>
<p>[ kustomization.yml ]</p>
<p>在專案根目錄建立 kustomization 設定檔。</p>
<pre><code>apiVersion: kustomize.config.k8s.io/v1beta1
<span class="hljs-attr">kind</span>: Kustomization
<span class="hljs-attr">resources</span>:
- deployment.yml
- service.yml
</code></pre><p>[ deployment.yml ]</p>
<p>建立 <a target="_blank" href="https://console.cloud.google.com/kubernetes/workload">工作負載</a> 部署設定檔。</p>
<p>這邊有些與官方不太一樣，appVersion 請使用 apps/v1</p>
<p>並且加上 spec.selector.matchLabels.app: gke-app</p>
<pre><code>apiVersion: apps/v1
<span class="hljs-attr">kind</span>: Deployment
<span class="hljs-attr">metadata</span>:
  name: gke-app
<span class="hljs-attr">spec</span>:
  replicas: <span class="hljs-number">1</span>
  <span class="hljs-attr">selector</span>:
    matchLabels:
      app: gke-app
  <span class="hljs-attr">strategy</span>:
    rollingUpdate:
      maxSurge: <span class="hljs-number">1</span>
      <span class="hljs-attr">maxUnavailable</span>: <span class="hljs-number">1</span>
  <span class="hljs-attr">minReadySeconds</span>: <span class="hljs-number">5</span>
  <span class="hljs-attr">template</span>:
    metadata:
      labels:
        app: gke-app
    <span class="hljs-attr">spec</span>:
      containers:
      - name: gke-app
        <span class="hljs-attr">image</span>: gcr.io/PROJECT_ID/IMAGE:TAG
        <span class="hljs-attr">ports</span>:
        - containerPort: <span class="hljs-number">80</span>
        <span class="hljs-attr">resources</span>:
          requests:
            cpu: <span class="hljs-number">100</span>m
          <span class="hljs-attr">limits</span>:
            cpu: <span class="hljs-number">100</span>m
</code></pre><ul>
<li>containerPort：是指容器內部要映射的 port</li>
</ul>
<p>[ service.yml ]</p>
<p>建立 <a target="_blank" href="https://console.cloud.google.com/kubernetes/discovery">Service 與 Ingress</a> 部署設定檔。</p>
<pre><code>apiVersion: v1
<span class="hljs-attr">kind</span>: Service
<span class="hljs-attr">metadata</span>:
  name: gke-app-service
<span class="hljs-attr">spec</span>:
  type: LoadBalancer
  <span class="hljs-attr">ports</span>:
    - port: <span class="hljs-number">80</span>
      <span class="hljs-attr">targetPort</span>: <span class="hljs-number">80</span>
  <span class="hljs-attr">selector</span>:
    app: gke-react-app
</code></pre><p>接著就可以 git push，看看跑完的結果吧～ </p>
<h2 id="heading-5-container-register-amp-kubernetes-engine">5.  查看 Container Register &amp; Kubernetes Engine</h2>
<p>可以看到順利建置好 image。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602272712.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602272712.png" alt="1602272712.png" /></a></p>
<p>工作負載也順利部署成功。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602332234.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602332234.png" alt="1602332234.png" /></a></p>
<p>Service 與 Ingress 也順利建立，在端點欄位可以看到公開網址。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/201d891d-f853-4e13-8cf5-829845b41e4f/1602332277.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602332277.png" alt="1602332277.png" /></a></p>
<p>看！他在動！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602242737.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-10_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20GKE/1602242737.png" alt="1602242737.png" /></a></p>
<p>本篇文章專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/react-gke-deploy-example">https://github.com/explooosion/react-gke-deploy-example</a></p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[GCP - 使用 Github Actions 部署 React 到 Cloud Run]]></title><description><![CDATA[丟給 github actions 跑就對啦！
利用 Github Actions 部署你的專案到 GCP Cloud Run 上吧！


How to deploy your Cloud Run service using GitHub Actions

前言
在 GCP 環境部署中，提供了網頁上 GUI 的操作、Cloud SDK，
此外你也可以使用 Github Actions 將你的專案部署到 Cloud Run。
本文將介紹如何使用 GoogleCloudPlatform 開源的 g...]]></description><link>https://blog.robby570.tw/gcp-github-actions-react-cloud-run</link><guid isPermaLink="true">https://blog.robby570.tw/gcp-github-actions-react-cloud-run</guid><category><![CDATA[Cloud]]></category><category><![CDATA[Deploy ]]></category><category><![CDATA[Docker]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Google]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 09 Oct 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/banner/1602243230.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>丟給 github actions 跑就對啦！</p>
<p>利用 Github Actions 部署你的專案到 GCP Cloud Run 上吧！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602243230.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602243230.png" alt="1602243230.png" /></a></p>
<ul>
<li><a target="_blank" href="https://medium.com/google-cloud/how-to-deploy-your-cloud-run-service-using-github-actions-e5b6a6f597a3">How to deploy your Cloud Run service using GitHub Actions</a></li>
</ul>
<h2 id="heading-5ymn6kia">前言</h2>
<p>在 <a target="_blank" href="https://console.cloud.google.com/?hl=zh-TW">GCP</a> 環境部署中，提供了網頁上 GUI 的操作、<a target="_blank" href="https://cloud.google.com/sdk">Cloud SDK</a>，</p>
<p>此外你也可以使用 <a target="_blank" href="https://github.com/features/actions">Github Actions</a> 將你的專案部署到 <a target="_blank" href="https://cloud.google.com/run?hl=zh-tw">Cloud Run</a>。</p>
<p>本文將介紹如何使用 <a target="_blank" href="https://github.com/GoogleCloudPlatform">GoogleCloudPlatform</a> 開源的 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions">github actions</a>，將專案部署到 <a target="_blank" href="https://cloud.google.com/run?hl=zh-tw">Cloud Run</a>。  </p>
<p>專案部分使用 <a target="_blank" href="http://zh-hant.reactjs.org/docs/create-a-new-react-app.html">CRA</a> ( create-react-app ) 作為範例，並將 build 好的專案，搭配 nginx 伺服器。</p>
<p>最後，本篇電腦作業系統環境使用 macOS，</p>
<p>對於 Windows 的朋友抱歉了，但設定與操作大同小異！</p>
<p>本篇文章專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/react-gcp-deploy-example">https://github.com/explooosion/react-gcp-deploy-example</a></p>
<p>若有時間，再針對 <a target="_blank" href="https://cloud.google.com/kubernetes-engine?hl=zh-tw">GKE</a> ( Google Kubernetes Engine ) 部署方式撰寫文章。</p>
<p>本篇可能需要具備的微薄知識：</p>
<ol>
<li>Google Cloud Platform</li>
<li>Github Actions</li>
<li>Cloud Run</li>
<li>React</li>
<li>Nginx</li>
<li>Docker</li>
<li>Terminal</li>
<li>Git</li>
<li>YAML</li>
</ol>
<p>由於本篇屬於入門教學，因此設定上大多為預設值。</p>
<p>由於本篇屬於入門教學，內文會有些圖文並茂。</p>
<p>由於本篇屬於入門教學，上述知識略懂聽過即可。</p>
<hr />
<h2 id="heading-5rwb56il6kqq5pio">流程說明</h2>
<p>大致上工作流程 Workflow 如下：</p>
<ol>
<li>透過設定好的 github actions，會將專案建置成 docker image</li>
<li>將 image 發佈到 <a target="_blank" href="https://cloud.google.com/container-registry">Container Registry</a> 存放</li>
<li>使用 <a target="_blank" href="https://cloud.google.com/container-registry">Container Registry</a> 存放的 image 建立 Cloud Run 服務</li>
</ol>
<hr />
<h2 id="heading-55uu6yye">目錄</h2>
<ol>
<li>安裝 gCloud CLI ( <a target="_blank" href="https://cloud.google.com/sdk/docs/install">Installing Google Cloud SDK</a> )</li>
<li>建立 GCP 專案 ( <a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs/building-app/creating-project">Creating Your Project</a> )</li>
<li>建立服務帳戶 ( <a target="_blank" href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys">Creating and managing service account keys</a> )</li>
<li>建立 CRA 專案 ( <a target="_blank" href="https://zh-hant.reactjs.org/docs/create-a-new-react-app.html#create-react-app">Create React App</a> )</li>
<li>建立 Github Actions ( <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions">Workflow syntax for GitHub Actions</a> )</li>
<li>新增 Github Secrets ( <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets">Encrypted secrets</a> )</li>
<li>Push 專案 ＆ 檢視 Actions</li>
<li>啟用 API 服務 ( <a target="_blank" href="http://console.developers.google.com/apis/library">API Library</a> )</li>
<li>新增 Docker &amp; 修改 yml</li>
<li>查看 Container Register &amp; Cloud Run</li>
</ol>
<hr />
<h2 id="heading-1-gcloud-cli-installing-google-cloud-sdkhttpscloudgooglecomsdkdocsinstall">1. 安裝 gCloud CLI ( <a target="_blank" href="https://cloud.google.com/sdk/docs/install">Installing Google Cloud SDK</a> )</h2>
<p>雖然本文操作建立大多使用友善的瀏覽器，</p>
<p>但安裝 CLI 有時也能幫助到我們進行操作，因此建議安裝。</p>
<p>請先安裝好 python，不再特別說明此步驟。</p>
<p>下載好 <a target="_blank" href="https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-313.0.1-darwin-x86_64.tar.gz">google-cloud-sdk-313.0.1-darwin-x86_64.tar.gz</a> 並解壓縮後，以指令安裝。</p>
<pre><code class="lang-bash">./google-cloud-sdk/install.sh
</code></pre>
<pre><code class="lang-bash">./google-cloud-sdk/bin/gcloud init
</code></pre>
<p>接著可能會跳出網頁要你登入、並且選擇你的專案。</p>
<p>或者下指令登入，再重新 gcloud init 取得專案資料，</p>
<p>可參考 <a target="_blank" href="https://cloud.google.com/sdk/docs/initializing">Initializing Cloud SDK</a>。</p>
<pre><code class="lang-bash">gcloud auth login
</code></pre>
<p>你可以嘗試確認現在電腦已授權登入的 google 帳戶</p>
<pre><code class="lang-bash">gcloud auth list
</code></pre>
<p>你可以嘗試確認電腦 gCloud 環境狀態</p>
<pre><code class="lang-bash">gcloud info
</code></pre>
<p>後續會我們會讓 Github Actions 嘗試呼叫 gcloud info</p>
<h2 id="heading-2-gcp-creating-your-projecthttpscloudgooglecomappenginedocsstandardnodejsbuilding-appcreating-project">2. 建立 GCP 專案 ( <a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs/building-app/creating-project">Creating Your Project</a> )</h2>
<p>到 GCP 上，<a target="_blank" href="https://console.cloud.google.com/projectcreate?">建立</a>你的專案，例如 my-project ( 筆者隨便決定的 )。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602227410.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602227410.png" alt="1602227410.png" /></a></p>
<p>完成後到<a target="_blank" href="https://console.cloud.google.com/home/dashboard">首頁</a>，可以看到你的專案資訊。</p>
<p>專案名稱、專案 ID、專案編號 後續會使用到，請務必留意。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602227331.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602227331.png" alt="1602227331.png" /></a></p>
<h2 id="heading-3-creating-and-managing-service-account-keyshttpscloudgooglecomiamdocscreating-managing-service-account-keys">3. 建立服務帳戶 ( <a target="_blank" href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys">Creating and managing service account keys</a> )</h2>
<p>由於我們使用 Github Actions 存取 GCP，自然也要有身份驗證。</p>
<p>到 IAM 與管理頁面 - 服務帳戶，點選 ＋建立服務帳戶</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602227810.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602227810.png" alt="1602227810.png" /></a></p>
<p>下方資料，看各位想怎麼填都可。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602228715.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602228715.png" alt="1602228715.png" /></a></p>
<p>根據 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions/tree/master/example-workflows/cloud-build">cloud build github-actions</a>，我們會用到的角色如下：</p>
<ul>
<li><p><code>Cloud Run Admin</code> - allows for the creation of new services</p>
</li>
<li><p><code>Cloud Build Editor</code> - allows for deploying cloud builds</p>
</li>
<li><p><code>Cloud Build Service Account</code> - allows for deploying cloud builds</p>
</li>
<li><p><code>Viewer</code> - allows for viewing the project</p>
</li>
<li><p><code>Service Account User</code> - required to deploy services to Cloud Build</p>
</li>
</ul>
<p>因此請選擇以下的角色名稱：</p>
<p>此圖是後來筆者重新到 IAM 建立角色，故有些畫面不同。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602241712.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602241712.png" alt="1602241712.png" /></a></p>
<p>如果你覺得很麻煩，權限設定部分可先給最大的擁有者。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602228379.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602228379.png" alt="1602228379.png" /></a></p>
<p>完成後在右邊選擇建立金鑰。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602228383.png" alt="1602228383.png" /></p>
<p>由於私密金鑰只能下載一次，務必保存好。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602228354.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602228354.png" alt="1602228354.png" /></a></p>
<p>內容格式應該類似下方：</p>
<p>這份金鑰我們會用在 Github Actions 的 <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#secrets">Secrets</a> 中</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"service_account"</span>,
  <span class="hljs-attr">"project_id"</span>: <span class="hljs-string">"project-id"</span>,
  <span class="hljs-attr">"private_key_id"</span>: <span class="hljs-string">"key-id"</span>,
  <span class="hljs-attr">"private_key"</span>: <span class="hljs-string">"-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n"</span>,
  <span class="hljs-attr">"client_email"</span>: <span class="hljs-string">"service-account-email"</span>,
  <span class="hljs-attr">"client_id"</span>: <span class="hljs-string">"client-id"</span>,
  <span class="hljs-attr">"auth_uri"</span>: <span class="hljs-string">"https://accounts.google.com/o/oauth2/auth"</span>,
  <span class="hljs-attr">"token_uri"</span>: <span class="hljs-string">"https://accounts.google.com/o/oauth2/token"</span>,
  <span class="hljs-attr">"auth_provider_x509_cert_url"</span>: <span class="hljs-string">"https://www.googleapis.com/oauth2/v1/certs"</span>,
  <span class="hljs-attr">"client_x509_cert_url"</span>: <span class="hljs-string">"https://www.googleapis.com/robot/v1/metadata/x509/service-account-email"</span>
}
</code></pre>
<h2 id="heading-4-cra-create-react-apphttpszh-hantreactjsorgdocscreate-a-new-react-apphtmlcreate-react-app">4.  建立 CRA 專案 ( <a target="_blank" href="https://zh-hant.reactjs.org/docs/create-a-new-react-app.html#create-react-app">Create React App</a> )</h2>
<p>接著可以建立專案了：</p>
<pre><code class="lang-bash">npx create-react-app my-app
</code></pre>
<p>如果你會使用 Docker 部署環境，那麼你可以替換成你想部署的專案。</p>
<h2 id="heading-5-github-actions-workflow-syntax-for-github-actionshttpsdocsgithubcomenfree-pro-teamlatestactionsreferenceworkflow-syntax-for-github-actions">5. 建立 Github Actions ( <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions">Workflow syntax for GitHub Actions</a> )</h2>
<p>建立 workflow，專案參考 <a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions/tree/master/setup-gcloud">setup-gcloud</a>。</p>
<p>[ .github / workflows / main.yml ]</p>
<pre><code>name: Build and Deploy to Cloud Run

<span class="hljs-attr">on</span>:
  push:
    branches:
    - master

<span class="hljs-attr">jobs</span>:
  setup-build-deploy:
    name: Setup, Build, and Deploy
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
    - name: Checkout
      <span class="hljs-attr">uses</span>: actions/checkout@v2

    # Setup gcloud CLI
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      <span class="hljs-attr">with</span>:
        version: <span class="hljs-string">'286.0.0'</span>
        <span class="hljs-attr">service_account_email</span>: ${{ secrets.GCP_SA_EMAIL }}
        <span class="hljs-attr">service_account_key</span>: ${{ secrets.GCP_SA_KEY }}
        <span class="hljs-attr">project_id</span>: ${{ secrets.GCP_PROJECT_ID }}
        <span class="hljs-attr">export_default_credentials</span>: <span class="hljs-literal">true</span>

    # Print gcloud info
    - name: Info
      <span class="hljs-attr">run</span>: gcloud info
</code></pre><ul>
<li>根據說明，checkout@v2 務必使用 v2 </li>
<li>secrets.GCP_SA_EMAIL：到時候要到 github 建立 secrets</li>
<li>secrets.GCP_SA_KEY：到時候要到 github 建立 secrets</li>
<li>secrets.GCP_PROJECT_ID：到時候要到 github 建立 secrets</li>
<li>run: gcloud info：執行命令 gcloud info</li>
</ul>
<h2 id="heading-6-github-secrets-encrypted-secretshttpsdocsgithubcomenfree-pro-teamlatestactionsreferenceencrypted-secrets">6. 新增 Github Secrets ( <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets">Encrypted secrets</a> )</h2>
<p>在 github 建立你的 repo，之後到 Settings 選擇 Secrets。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602232722.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602232722.png" alt="1602232722.png" /></a></p>
<p>點選 New secret，根據前一步驟的 yml，我們會需要用到以下三個 secrets：</p>
<ol>
<li>GCP_PROJECT_ID：一開始建立專案時給予的 id，你也可以從剛剛 金鑰 json 找到 </li>
<li>GCP_SA_KEY：打開 金鑰 json ，將所有內容複製貼上</li>
<li>GCP_SA_EMAIL：打開 金鑰 json ，可以從 client_email 找到，或者回到 IAM 的 <a target="_blank" href="https://console.cloud.google.com/iam-admin/serviceaccounts">服務帳戶</a> 找到</li>
</ol>
<p>命名方式自由選擇，記得 yml 要一樣就好。</p>
<p>你也可以把 金鑰.json 轉 base64 貼到 GCP_SA_KEY 儲存，指令： base64 -i key.json</p>
<p>完成如下：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602235704.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602235704.png" alt="1602235704.png" /></a></p>
<h2 id="heading-7-push-actions">7. Push 專案 ＆ 檢視 Actions</h2>
<p>把你的專案 push 到 github，接著就可以看到 Actions 在執行囉！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602240224.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602240224.png" alt="1602240224.png" /></a></p>
<p>展開 Run gcloud info，就可以看到指令的結果了！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602240266.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602240266.png" alt="1602240266.png" /></a></p>
<h2 id="heading-8-api-api-libraryhttpconsoledevelopersgooglecomapislibrary">8. 啟用 API 服務 ( <a target="_blank" href="http://console.developers.google.com/apis/library">API Library</a> )</h2>
<p>在這一步，我們將要把專案用 docker 建立成 image，並發佈到 <a target="_blank" href="https://console.cloud.google.com/gcr/images">Container Registry</a>。</p>
<p>接著使用 <a target="_blank" href="https://console.cloud.google.com/gcr/images">Container Registry</a> 的映像檔，為我們建立 <a target="_blank" href="https://cloud.google.com/run">Cloud Run</a>。</p>
<p>以下 yml 利用 <a target="_blank" href="https://cloud.google.com/sdk/gcloud/reference/builds/submit">gcloud builds submit</a>、<a target="_blank" href="https://cloud.google.com/sdk/gcloud/reference/run/deploy">gcloud run deploy</a> 等指令操作。</p>
<p>由於我們尚未開通使用  <a target="_blank" href="https://console.cloud.google.com/gcr/images">Container Registry</a> 服務，</p>
<p>請先到該頁面選擇啟用 Container Registry API。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602235136.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602235136.png" alt="1602235136.png" /></a></p>
<p>以下為必啟用的 API，可到  <a target="_blank" href="https://console.developers.google.com/apis/library">API 程式庫</a> 尋找並安裝：</p>
<p>由於我們要使用 gcloud build, gcloud run deploy 等指令，協助我們部署到 Cloud Run，</p>
<p>因此需要啟用 <a target="_blank" href="https://console.developers.google.com/apis/library/cloudbuild.googleapis.com?q=Cloud%20Buil&amp;id=9472915e-c82c-4bef-8a6a-34c81e5aebcc&amp;project=compelling-art-292007&amp;authuser=1">Cloud Build API</a> 和 <a target="_blank" href="https://console.developers.google.com/apis/library/run.googleapis.com">Cloud Run API</a> 。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602240934.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602240934.png" alt="1602240934.png" /></a></p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602241963.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602241963.png" alt="1602241963.png" /></a></p>
<p>以上有任何缺少的服務啟用，很可能會在 Github Actions 階段噴錯，</p>
<p>例如 Cloud Build API 沒啟用的錯誤：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602240995.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602240995.png" alt="1602240995.png" /></a></p>
<h2 id="heading-9-docker-amp-yml">9. 新增 Docker &amp; 修改 yml</h2>
<p>接著開始撰寫 Dockerfile 吧！</p>
<p>[ <strong>Dockerfile</strong> ]</p>
<p>可根據自己的專案撰寫，此專案筆者將專案 build 後放置於 nginx html。</p>
<pre><code>FROM node:<span class="hljs-number">10</span> AS Builder

ENV NPM_CONFIG_LOGLEVEL info

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY . .

ARG GENERATE_SOURCEMAP=<span class="hljs-literal">false</span>

RUN yarn install &amp;&amp; yarn build

FROM nginx:<span class="hljs-number">1.13</span><span class="hljs-number">.3</span>-alpine

RUN rm -rf /usr/share/nginx/html<span class="hljs-comment">/*
COPY --from=Builder /usr/src/app/build /usr/share/nginx/html</span>
</code></pre><p>[ <a target="_blank" href="https://github.com/explooosion/react-gcp-deploy-example/blob/master/.dockerignore">.dockerignore</a> ]</p>
<p>加入一點忽略規則。</p>
<pre><code>Dockerfile
README.md
node_modules
npm-debug.log
</code></pre><p>[ .github / workflows / main.yml ]</p>
<pre><code>name: Build and Deploy to Cloud Run

<span class="hljs-attr">on</span>:
  push:
    branches:
    - master

<span class="hljs-attr">env</span>:
  PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  <span class="hljs-attr">SERVICE_NAME</span>: create-react-app
  <span class="hljs-attr">RUN_REGION</span>: us-central1

<span class="hljs-attr">jobs</span>:
  setup-build-deploy:
    name: Setup, Build, and Deploy
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
    - name: Checkout
      <span class="hljs-attr">uses</span>: actions/checkout@v2

    # Setup gcloud CLI
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      <span class="hljs-attr">with</span>:
        version: <span class="hljs-string">'286.0.0'</span>
        <span class="hljs-attr">service_account_email</span>: ${{ secrets.GCP_SA_EMAIL }}
        <span class="hljs-attr">service_account_key</span>: ${{ secrets.GCP_SA_KEY }}
        <span class="hljs-attr">project_id</span>: ${{ secrets.GCP_PROJECT_ID }}
        <span class="hljs-attr">export_default_credentials</span>: <span class="hljs-literal">true</span>

    # Print gcloud info
    - name: Info
      <span class="hljs-attr">run</span>: gcloud info

    # Build and push image to Google Container Registry
    - name: Build
      <span class="hljs-attr">run</span>: |-
        gcloud builds submit \
          --quiet \
          --tag <span class="hljs-string">"gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA"</span>
    # Deploy image to Cloud Run
    - name: Deploy
      <span class="hljs-attr">run</span>: |-
        gcloud run deploy <span class="hljs-string">"$SERVICE_NAME"</span> \
          --quiet \
          --region <span class="hljs-string">"$RUN_REGION"</span> \
          --image <span class="hljs-string">"gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA"</span> \
          --platform <span class="hljs-string">"managed"</span> \
          --port <span class="hljs-number">80</span> \
          --allow-unauthenticated
</code></pre><ul>
<li>env 的參數是要給後面 gcloud 指令使用</li>
<li>PROJECT_ID：是你原專案名稱，使用 secret 的 ${{ secrets.GCP_PROJECT_ID }} 即可</li>
<li>SERVICE_NAME：是你 image 在 container register 的名稱</li>
<li>gcr.io 是 container register 的網址，我們發佈的映像檔網域</li>
</ul>
<p>接著就可以 git push，看看跑完的結果吧～ </p>
<h2 id="heading-10-container-register-amp-cloud-run">10.  查看 Container Register &amp; Cloud Run</h2>
<p>當 github action 順利建置好並發佈 image 後，</p>
<p>就能看到 image 在列表中了，並且是以私人的方式發佈！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602242218.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602242218.png" alt="1602242218.png" /></a></p>
<p>當 github actions 完全跑完後，就可以查看服務。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602242291.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602242291.png" alt="1602242291.png" /></a></p>
<p>點進去查看，可以看到網址，那就是你的服務公開網址囉！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602242352.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602242352.png" alt="1602242352.png" /></a></p>
<p>看！他在動！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/be9c89cd-fdf5-4f93-a363-601ab3b16219/1602242737.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-10-09_GCP%20-%20%E4%BD%BF%E7%94%A8%20Github%20Actions%20%E9%83%A8%E7%BD%B2%20React%20%E5%88%B0%20Cloud%20Run/1602242737.png" alt="1602242737.png" /></a></p>
<p>本篇文章專案範例放置於：</p>
<p><a target="_blank" href="https://github.com/explooosion/react-gcp-deploy-example">https://github.com/explooosion/react-gcp-deploy-example</a></p>
<h2 id="heading-ref">REF</h2>
<ul>
<li><a target="_blank" href="https://cloud.google.com/docs">Google Cloud Doc</a></li>
<li><a target="_blank" href="https://github.com/GoogleCloudPlatform/github-actions">GoogleCloudPlatform/github-actions</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[NPM - 解決套件全域安裝錯誤 Missing write access（Mac ）]]></title><description><![CDATA[被守門員擋住，無法安裝 global package 啦！
每次在新環境中，難免還是會遇到新手村的問題。

前言
在新環境使用 npm 時，難免有些「新人問題」。
此處意旨，從 0 開始作業容易碰到的起始問題。 
故事是這樣發生的，
筆者在安裝全域套件 ( global package  ) 時，噴了一些錯誤導致安裝失敗。
npm install <package-name> -g

粗估，應該是被守門員阻擋住 ...
並且收到訊息如下：


ref: 鎖鏈康妮

欸不是。
錯誤訊息如下：

考...]]></description><link>https://blog.robby570.tw/npm-missing-write-accessmac</link><guid isPermaLink="true">https://blog.robby570.tw/npm-missing-write-accessmac</guid><category><![CDATA[npm]]></category><category><![CDATA[package]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 18 Jul 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-18_NPM%20-%20%E8%A7%A3%E6%B1%BA%E5%A5%97%E4%BB%B6%E5%85%A8%E5%9F%9F%E5%AE%89%E8%A3%9D%E9%8C%AF%E8%AA%A4%20Missing%20write%20access%EF%BC%88Mac%20%EF%BC%89/banner/1595055493.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>被守門員擋住，無法安裝 global package 啦！</p>
<p>每次在新環境中，難免還是會遇到新手村的問題。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/24194a4b-0024-4797-9aec-9066525d5b53/1595055493.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-18_NPM%20-%20%E8%A7%A3%E6%B1%BA%E5%A5%97%E4%BB%B6%E5%85%A8%E5%9F%9F%E5%AE%89%E8%A3%9D%E9%8C%AF%E8%AA%A4%20Missing%20write%20access%EF%BC%88Mac%20%EF%BC%89/1595055493.png" alt="1595055493.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>在新環境使用 npm 時，難免有些「新人問題」。</p>
<p>此處意旨，從 0 開始作業容易碰到的起始問題。 </p>
<p>故事是這樣發生的，</p>
<p>筆者在安裝全域套件 ( global package  ) 時，噴了一些錯誤導致安裝失敗。</p>
<pre><code class="lang-bash">npm install &lt;package-name&gt; -g
</code></pre>
<p>粗估，應該是被守門員阻擋住 ...</p>
<p>並且收到訊息如下：</p>
<p><a target="_blank" href="https://zh.moegirl.org/zh-tw/%E9%94%81%E9%93%BE%E5%BA%B7%E5%A6%AE"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-18_NPM%20-%20%E8%A7%A3%E6%B1%BA%E5%A5%97%E4%BB%B6%E5%85%A8%E5%9F%9F%E5%AE%89%E8%A3%9D%E9%8C%AF%E8%AA%A4%20Missing%20write%20access%EF%BC%88Mac%20%EF%BC%89/1595038641.jpg" alt="1595038641.jpg" /></a></p>
<ul>
<li>ref: <a target="_blank" href="https://zh.moegirl.org/zh-tw/%E9%94%81%E9%93%BE%E5%BA%B7%E5%A6%AE">鎖鏈康妮</a></li>
</ul>
<p>欸不是。</p>
<p>錯誤訊息如下：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/24194a4b-0024-4797-9aec-9066525d5b53/1595038893.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-18_NPM%20-%20%E8%A7%A3%E6%B1%BA%E5%A5%97%E4%BB%B6%E5%85%A8%E5%9F%9F%E5%AE%89%E8%A3%9D%E9%8C%AF%E8%AA%A4%20Missing%20write%20access%EF%BC%88Mac%20%EF%BC%89/1595038893.png" alt="1595038893.png" /></a></p>
<p>考過太多次英文閱讀測驗的你，</p>
<p>可以很明顯地抓出以下關鍵訊息：</p>
<ol>
<li>Missing write access to /usr/local/lib/node_modules</li>
<li><p>Error: EACCES: permission denied, access '/usr/local/lib/node_modules'</p>
</li>
<li><p>The operation was rejected by your operating system.</p>
</li>
<li><p>try running npm ERR! the command again as root/Administrator.</p>
</li>
</ol>
<p>簡單的說，就是沒有權限對目錄做寫入行為。</p>
<h2 id="heading-6kej5rg65pa55byp">解決方式</h2>
<p>根據錯誤訊息，可以知道套件安裝的目錄位置在：</p>
<pre><code class="lang-bash">/usr/<span class="hljs-built_in">local</span>/lib/node_modules
</code></pre>
<p>注意：此目錄為使用官網 <a target="_blank" href="https://nodejs.org/en/">Node.js</a> 下載後的預設安裝路徑。</p>
<p>因此開啟 terminal，於任意目錄，輸入指令修改該目錄權限即可：</p>
<pre><code class="lang-bash">sudo chown -R <span class="hljs-variable">$USER</span> /usr/<span class="hljs-built_in">local</span>/lib/node_modules
</code></pre>
<p>由於修改存取權限等行為需要管理員身份，</p>
<p>因此下完指令後，電腦會要求輸入電腦用戶密碼，</p>
<p>將該目錄與其子目錄的擁有者，更改為當前電腦的用戶（對就是在講你）。</p>
<p>如此一來，</p>
<p>就算是全域安裝，有存取權限就沒問題了，對吧！？ （ <a target="_blank" href="https://zh.wikipedia.org/zh-tw/%E5%B0%B1%E7%AE%97%E6%98%AF%E5%93%A5%E5%93%A5%EF%BC%8C%E6%9C%89%E6%84%9B%E5%B0%B1%E6%B2%92%E5%95%8F%E9%A1%8C%E4%BA%86%EF%BC%8C%E5%B0%8D%E5%90%A7">Ref</a> ）</p>
<p>果然再嘗試一次安裝套件，就沒問題了～</p>
<h2 id="heading-5b6m6kiy">後記</h2>
<p>如果你想知道這些片段指令是什麼意思 ...</p>
<p>筆者有份作業推薦，並且已經幫你做答完最後一題。</p>
<p>作答完畢後，相信一定可以知道的。</p>
<ul>
<li>sudo：</li>
<li>chown -R：</li>
<li>$USER：</li>
<li>/usr/local/lib/node_modules：</li>
<li><a target="_blank" href="https://zh.moegirl.org/zh-tw/%E9%94%81%E9%93%BE%E5%BA%B7%E5%A6%AE">鎖鏈康妮</a>：</li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[NPM - 套件管理之棄用 deprecate 與分配標籤 distribution tags]]></title><description><![CDATA[啊啊啊！！！發佈一個垃圾怎摸辦！
npm 套件管理中的棄用與分配標籤應用。

前言
很久沒有發表文章了，本篇很簡短的分享遭遇與解法。
如果有使用過 npm 且有發佈過套件的朋友，
對 npm publish 應該再熟悉不過。
筆者日前誤發佈了一個有 bug 的套件，
而且相當嚴重... 吐血，專案直接 crash 那種，
果然沒多久就收來了 issue 表示 latest 版本出問題。
啊啊啊啊啊好慌！
怎麼會！？ 怎麼會！？ 就變成了一灘爛泥...
作法
在 npm 上，找到了沒有特別用過的指...]]></description><link>https://blog.robby570.tw/npm-deprecate-distribution-tags</link><guid isPermaLink="true">https://blog.robby570.tw/npm-deprecate-distribution-tags</guid><category><![CDATA[package]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Tue, 14 Jul 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/banner/1594727263.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>啊啊啊！！！發佈一個垃圾怎摸辦！</p>
<p>npm 套件管理中的棄用與分配標籤應用。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/c901b341-0c8d-4d3a-988c-f9b514f26816/1594727263.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594727263.png" alt="1594727263.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>很久沒有發表文章了，本篇很簡短的分享遭遇與解法。</p>
<p>如果有使用過 npm 且有發佈過套件的朋友，</p>
<p>對 npm publish 應該再熟悉不過。</p>
<p>筆者日前誤發佈了一個有 bug 的套件，</p>
<p>而且相當嚴重... <strong>吐血</strong>，專案直接 crash 那種，</p>
<p>果然沒多久就收來了 issue 表示 latest 版本出問題。</p>
<p>啊啊啊啊啊好慌！</p>
<h4 id="heading-kirmgi7purzmnipvvihvvj8g5oco6bq85pyd77yb77yfiowwseiuiuaikos6hus4goebmoeimazps4ulioq"><strong>怎麼會！？ 怎麼會！？ 就變成了一灘爛泥...</strong></h4>
<h2 id="heading-5l2c5rov">作法</h2>
<p>在 npm 上，找到了沒有特別用過的指令：</p>
<ul>
<li><p><a target="_blank" href="https://docs.npmjs.com/cli/deprecate">npm-deprecate</a>：將指定的版本標註為棄用</p>
</li>
<li><p><a target="_blank" href="https://docs.npmjs.com/cli/dist-tag">npm-dist-tag</a>：修改指定版本的標籤</p>
</li>
</ul>
<p>於是便開始了 Developing in Production</p>
<p>因為沒使用過，某種程度來說就是直接上！</p>
<p>以下筆者使用本次修復的套件作為指令範例</p>
<p>將 0.8.1 改為 0.8.0</p>
<h3 id="heading-1-release">1. Release</h3>
<p>首先到 Github Release 中改成 Pre-release，</p>
<p>防止相關貼紙及 Github、npm 頁面資訊顯示有錯誤的版本：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594725206.png" alt="1594725206.png" /></p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594725242.png" alt="1594725242.png" /></p>
<h3 id="heading-2-deprecate">2. Deprecate</h3>
<p>利用指令 <a target="_blank" href="https://docs.npmjs.com/cli/deprecate">npm-deprecate</a> 將 0.8.1 進行棄用聲明：</p>
<pre><code class="lang-bash">npm deprecate agm-direction@0.8.1 <span class="hljs-string">"critical bug fixed in v0.8.1"</span>
</code></pre>
<p>完成後可以在 <a target="_blank" href="https://www.npmjs.com/package/agm-direction/v/0.8.1">npm 頁面</a>上看到有 deprecated 警語：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/c901b341-0c8d-4d3a-988c-f9b514f26816/1594725328.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594725328.png" alt="1594725328.png" /></a></p>
<p>並且在 Versions 頁籤中可以看到棄用的版本被隱藏了起來。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/c901b341-0c8d-4d3a-988c-f9b514f26816/1594725939.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594725939.png" alt="1594725939.png" /></a></p>
<h3 id="heading-3-tags">3. Tags</h3>
<p>利用指令 <a target="_blank" href="https://docs.npmjs.com/cli/dist-tag">npm-dist-tag</a> 將 0.8.0 設置為 latest 版本：</p>
<pre><code class="lang-bash">npm dist-tag add agm-direction@0.8.0
</code></pre>
<p>由於最後的參數 [] 我們並沒有使用，</p>
<p>因此預設會是以 latest 進行標記。　</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/robby/c901b341-0c8d-4d3a-988c-f9b514f26816/1594726029.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-07-14_NPM%20-%20%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A3%84%E7%94%A8%20deprecate%20%E8%88%87%E5%88%86%E9%85%8D%E6%A8%99%E7%B1%A4%20distribution%20tags/1594726029.png" alt="1594726029.png" /></a></p>
<p>以上完成後，在 npm install 時 latest ，</p>
<p>也就會對應到 0.8.0 版本囉！</p>
<p>最後也順利完成這次的突發事件。</p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[CSS - calc 結合變數，從 CSS、SCSS 喇到 Styled Components]]></title><description><![CDATA[css calc 搭配變數的各種奇淫技巧！
有些經驗技巧，總在奇妙需求後。

前言
在 css 中，calc 的技巧想必大家都相當熟悉。
面對排版、佈局設計時，相當好用。
而當你在寫 scss 時，又多了好幾種解法，
如果你又使用了 styled-components，那麼使用情境就更多樣了。
本篇文章紀錄當要傳入 calc 計算的數值為「變數」的使用方式。
以下將從 css、scss 到 styled-components 依序介紹使用技巧。
ㄧ、CSS
當我們想在 css 檔案內進行計算。
...]]></description><link>https://blog.robby570.tw/css-calc-cssscss-styled-components</link><guid isPermaLink="true">https://blog.robby570.tw/css-calc-cssscss-styled-components</guid><category><![CDATA[CSS]]></category><category><![CDATA[styled-components]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 05 Jun 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-06-05_CSS%20-%20calc%20%E7%B5%90%E5%90%88%E8%AE%8A%E6%95%B8%EF%BC%8C%E5%BE%9E%20CSS%E3%80%81SCSS%20%E5%96%87%E5%88%B0%20Styled%20Components/banner/1591286116.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>css calc 搭配變數的各種奇淫技巧！</p>
<p>有些經驗技巧，總在奇妙需求後。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/1a686640-c6f9-46f1-94fb-6ec2f192b89e/1591286116.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-06-05_CSS%20-%20calc%20%E7%B5%90%E5%90%88%E8%AE%8A%E6%95%B8%EF%BC%8C%E5%BE%9E%20CSS%E3%80%81SCSS%20%E5%96%87%E5%88%B0%20Styled%20Components/1591286116.png" alt="1591286116.png" /></a></p>
<h2 id="heading-5ymn6kia">前言</h2>
<p>在 css 中，<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/calc">calc</a> 的技巧想必大家都相當熟悉。</p>
<p>面對排版、佈局設計時，相當好用。</p>
<p>而當你在寫 <a target="_blank" href="https://sass-lang.com/">scss</a> 時，又多了好幾種解法，</p>
<p>如果你又使用了 <a target="_blank" href="https://styled-components.com/">styled-components</a>，那麼使用情境就更多樣了。</p>
<p>本篇文章紀錄當要傳入 calc 計算的數值為「變數」的使用方式。</p>
<p>以下將從 css、scss 到 styled-components <a target="_blank" href="https://styled-components.com/"></a>依序介紹使用技巧。</p>
<h2 id="heading-css">ㄧ、CSS</h2>
<p>當我們想在 css 檔案內進行計算。</p>
<h3 id="heading-1-howhow-method">1. 直白方法 ( HowHow Method )</h3>
<p><a target="_blank" href="https://www.youtube.com/watch?v=cruXUtlOdTU">男人就是直白</a>，直接寫上數值，這是最入門的用法。</p>
<p>[ Foo.css ]</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">div</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">100vh</span> - <span class="hljs-number">50px</span>);
}
</code></pre>
<p>你知道嗎？筆者尊重性別平等，因此「男人...」這句話不代表本人立場。</p>
<h3 id="heading-2-pseudo-elements-method">2. 偽類別方法 ( Pseudo-elements Method )</h3>
<p>根據偽類別 ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements">Pseudo-elements</a> ) ，你可以使用 <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:root">:root</a>，來定義屬性與值。</p>
<p>然後利用 <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/var">var()</a> 方法來取出指定屬性的值。</p>
<p>[ Foo.css ]</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--head</span>: <span class="hljs-number">50px</span>;
}

<span class="hljs-selector-tag">div</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">100vh</span> - var(--head));
}
</code></pre>
<p>你知道嗎？在 :root 內定義的屬性名稱，必須是 -- 開頭，例如：--head。</p>
<p>你知道嗎？ :root 內的命名方式也可以搞死人，例如：--__-_-_，var(--__-_-_)。</p>
<h2 id="heading-scss">二、SCSS</h2>
<p>當我們想在 scss 檔案內進行計算。</p>
<h3 id="heading-1-pseudo-elements-method">1. 偽類別方法 ( Pseudo-elements Method )</h3>
<p>此方法為 css 本身的方式，因此與你的預處理器 ( scss、sass、less ) 無關。</p>
<p>語法當然也都是一樣的。</p>
<p>[ Foo.scss ]</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--head</span>: <span class="hljs-number">50px</span>;
}

<span class="hljs-selector-tag">div</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">100vh</span> - var(--head));
}
</code></pre>
<p>你知道嗎？不少 css framework 經常使用 :root 來定義設計模式。</p>
<h3 id="heading-2-variables">2. 宣告變數方法 ( Variables )</h3>
<p>當你使用 CSS 預處理器 ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/CSS_preprocessor">CSS Preprocessor</a> ) 時，想必最熟悉的就是變數宣告了。</p>
<p>如果要在 calc 放入變數，需要使用插值法 ( <a target="_blank" href="https://sass-lang.com/documentation/interpolation">Interpolation</a> )，透過 # 與 { }，將變數包起來。</p>
<p>[ Foo.scss ]</p>
<pre><code class="lang-scss"><span class="hljs-variable">$head</span>: <span class="hljs-number">50px</span>;

<span class="hljs-selector-tag">div</span> {
  <span class="hljs-attribute">height</span>: calc(<span class="hljs-number">100vh</span> - #{<span class="hljs-variable">$head</span>});
}
</code></pre>
<h2 id="heading-styled-components">三、styled-components</h2>
<p>當專案使用 react ＋ styled-components，除了元素的主樣式會寫在 js 內：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Main = styled.main<span class="hljs-string">`
  width: 100px;
  color: #f00;
`</span>;
</code></pre>
<p>你可能也會有 scss 資料夾存放各類模組，例如：</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-06-05_CSS%20-%20calc%20%E7%B5%90%E5%90%88%E8%AE%8A%E6%95%B8%EF%BC%8C%E5%BE%9E%20CSS%E3%80%81SCSS%20%E5%96%87%E5%88%B0%20Styled%20Components/1591258035.png" alt="1591258035.png" /></p>
<p>那麼 calc 內的變數，可能會有幾種來源：</p>
<ol>
<li>來自 js 內定義的變數</li>
<li>從 ThemeProvider 載入 scss 的變數</li>
<li>從 ThemeProvider 載入 scss 的 :root</li>
<li>不經過 ThemeProvider ，載入 scss 的變數</li>
<li>不經過 ThemeProvider ，載入 scss 的 :root</li>
</ol>
<p>我們先撇除最佳實踐，單純思考在這些情境下的處理方式。</p>
<p>讓我們開始分析囉！</p>
<h3 id="heading-1-js">1. 來自 js 內定義的變數</h3>
<p>由於 styled.div` ` 是用樣板字串 ( <a target="_blank" href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Template_literals">Template Strings</a> ) 包起樣式，因此可以使用 ${expression} 來包入變數。</p>
<p>第二種只是把 ${expression} 函式改寫成 <a target="_blank" href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions">arrow function</a>。</p>
<p>第三種則是第二種的簡化，即 <a target="_blank" href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions">arrow function</a> 的簡化版。</p>
<p>[ Foo.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> head = <span class="hljs-string">'50px'</span>;

<span class="hljs-keyword">const</span> Foo = styled.div<span class="hljs-string">`
  height: calc(100vh - <span class="hljs-subst">${head}</span>);
`</span>;

<span class="hljs-keyword">const</span> Bar = styled.div<span class="hljs-string">`
  height: <span class="hljs-subst">${() =&gt; <span class="hljs-string">`calc(100vh - <span class="hljs-subst">${head}</span>)`</span>}</span>;
`</span>;

<span class="hljs-keyword">const</span> App = styled.div<span class="hljs-string">`
  height: <span class="hljs-subst">${<span class="hljs-string">`calc(100vh - <span class="hljs-subst">${head}</span>)`</span>}</span>;
`</span>;
</code></pre>
<h3 id="heading-2-themeprovider-scss">2. 從 ThemeProvider 載入 scss 模組中的變數</h3>
<p>基本上用法跟前面樣板字串一樣，只是變數來自 props 的 theme。</p>
<p>[ Foo.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Main = styled.main<span class="hljs-string">`
  height: <span class="hljs-subst">${p =&gt; <span class="hljs-string">`calc(100vh - <span class="hljs-subst">${p.theme.head}</span>)`</span>}</span>;
`</span>;
</code></pre>
<h3 id="heading-3-themeprovider-scss-root">3. 從 ThemeProvider 載入 scss 模組中的 :root</h3>
<p>:root 偽類別與 scss 無關，因此使用方式同 css 一樣。</p>
<p>[ Foo.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Main = styled.main<span class="hljs-string">`
  height: calc(100vh - var(--head));
`</span>;
</code></pre>
<h3 id="heading-4-themeprovider-scss">4. 不經過 ThemeProvider ，載入 scss 的變數</h3>
<p>由於不是從 Provider 倒入，無法從 props 中取出。</p>
<p>但可以透過 <a target="_blank" href="https://github.com/webpack-contrib/css-loader">css-loader</a>，使用 <a target="_blank" href="https://github.com/css-modules/icss#export">:export</a> 將變數以物件形式匯出。</p>
<p>如果是用框架諸如：react、angular、vue 等，基本上 webpack 已經都設定過囉！</p>
<p>以下示範如何將 head 變數匯出。</p>
<p>[ Foo.scss ]</p>
<pre><code class="lang-scss"><span class="hljs-variable">$head</span>: <span class="hljs-number">50px</span>;

:export {
  head: <span class="hljs-variable">$head</span>;
}
</code></pre>
<p>[ Foo.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { head } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Foo.scss'</span>;

<span class="hljs-keyword">const</span> Main = styled.div<span class="hljs-string">`
  height: calc(100vh - <span class="hljs-subst">${head}</span>);
`</span>;
</code></pre>
<h3 id="heading-5-themeprovider-scss-root">5. 不經過 ThemeProvider ，載入 scss 的 :root</h3>
<p>同樣 :root 與 scss 無關，因此也是直接使用。</p>
<p>[ Foo.scss ]</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--head</span>: <span class="hljs-number">50px</span>;
}
</code></pre>
<p>[ Foo.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">'./Foo.scss'</span>;

<span class="hljs-keyword">const</span> Main = styled.main<span class="hljs-string">`
  height: calc(100vh - var(--head));
`</span>;
</code></pre>
<p>以上整理一些 calc 讀取變數的方式，</p>
<p>但為了一致性，個人認為 :root 與 變數 最好選擇使用一種。</p>
<p>最後再分享一篇文章探討：</p>
<p><a target="_blank" href="https://codyhouse.co/blog/post/css-custom-properties-vs-sass-variables">Why we prefer CSS Custom Properties to SASS variables</a></p>
<p>歡迎大家討論自己喜歡的使用方式！</p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[PixiJS - 修正 sprite 為透明背景時的 hitArea]]></title><description><![CDATA[將你的 sprite hitArea 套上完美的 polygons 吧！
PixiJS 很好玩，你一定要試試！


ref：Introduction to PixiJS: An HTML5 2D rendering engine

1. 前言
PixiJS  是個很龐大的 2D 渲染引擎，使用後，你會發現更多美麗的事物。
2. 動機
最近使用 PixiJS 時，在 sprite 的滑鼠事件上遇到了點問題。
情境說明：
 我們隨意找一張圖，下圖為 PixiJS 範例圖。

該圖大小為 119x18...]]></description><link>https://blog.robby570.tw/pixijs-sprite-hitarea</link><guid isPermaLink="true">https://blog.robby570.tw/pixijs-sprite-hitarea</guid><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sat, 07 Mar 2020 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>將你的 sprite hitArea 套上完美的 polygons 吧！</p>
<p>PixiJS 很好玩，你一定要試試！</p>
<p><a target="_blank" href="https://miro.medium.com/max/640/1*x1LOZj2hMIGNJjxOAsMR7w.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/1*x1LOZj2hMIGNJjxOAsMR7w.png" alt="1*x1LOZj2hMIGNJjxOAsMR7w.png" /></a></p>
<ul>
<li>ref：<a target="_blank" href="https://itnext.io/introduction-to-pixijs-an-html5-2d-rendering-engine-64173df9a14e">Introduction to PixiJS: An HTML5 2D rendering engine</a></li>
</ul>
<h2 id="heading-1">1. 前言</h2>
<p>PixiJS  是個很龐大的 2D 渲染引擎，使用後，你會發現更多美麗的事物。</p>
<h2 id="heading-2">2. 動機</h2>
<p>最近使用 PixiJS 時，在 sprite 的滑鼠事件上遇到了點問題。</p>
<h4 id="heading-5oof5akd6kqq5pio77ya">情境說明：</h4>
<p> 我們隨意找一張圖，下圖為 PixiJS 範例圖。</p>
<p><a target="_blank" href="https://pixijs.io/examples/examples/assets/flowerTop.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/1583511339.png" alt="1583511339.png" /></a></p>
<p>該圖大小為 119x181 且為透明背景的可愛河童。</p>
<p>注意！請不要跟我爭辯他到底是河童、西洋梨還是青蛙。</p>
<p>如果你為它寫一個 pointertap 觸碰事件，會發生什麼事？</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> PIXI.Application({
  <span class="hljs-attr">width</span>: <span class="hljs-number">200</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">200</span>,
});
<span class="hljs-built_in">document</span>.body.appendChild(app.view);

<span class="hljs-keyword">const</span> sprite = PIXI.Sprite.from(<span class="hljs-string">'https://pixijs.io/examples/examples/assets/flowerTop.png'</span>);
sprite.buttonMode = <span class="hljs-literal">true</span>;
sprite.interactive = <span class="hljs-literal">true</span>;

sprite.addListener(<span class="hljs-string">'pointertap'</span>, <span class="hljs-function">() =&gt;</span> {
  alert(<span class="hljs-string">'Hola'</span>);
});

app.stage.addChild(sprite);
</code></pre>
<ul>
<li>buttonMode：為了方便辨識，在此設定讓滑鼠移入可觸發範圍時的指標為 pointer 樣式</li>
</ul>
<p>點選了河童，看起來沒問題！</p>
<p><a target="_blank" href="https://i.imgur.com/P14ul0V.gif"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/P14ul0V.gif" alt="P14ul0V.gif" /></a></p>
<p>但接下來，你試著將滑鼠移動到右下角的 「黑色區域」 ...！？</p>
<p>居然能夠觸發觸碰事件，而且滑鼠指標也呈現 pointer 樣式。</p>
<p><a target="_blank" href="https://i.imgur.com/Qofu6Ms.gif"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/Qofu6Ms.gif" alt="Qofu6Ms.gif" /></a></p>
<p>為什麼會這樣？</p>
<p>因為 sprite 預設 hitArea 為圖片本身的大小，包含了那些透明區域。</p>
<p>在理想上，當然會希望僅在 「看得見」的地方 trigger！</p>
<p>因此，如何鎖定在看得見的圖片是個問題！</p>
<p>接下來開始撰寫解決的辦法！</p>
<h2 id="heading-2-1">2. 工具</h2>
<p>目前找到的工具是 <a target="_blank" href="https://www.codeandweb.com/physicseditor">PhysicsEditor</a>，不過他只有七天的試用期。</p>
<p>如果有長期需求，當然建議可以給他買下去，約台幣 $800/year 不到～</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d59d2bde-48ea-485e-8fa6-41cd863f372e/1583513084.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/1583513084.png" alt="1583513084.png" /></a></p>
<p>安裝完畢後啟動，試著加入圖片吧！</p>
<p>[ Add sprites ]</p>
<p><a target="_blank" href="https://user-images.githubusercontent.com/13682994/76091487-9edc7e80-5ff8-11ea-8578-e5a45a193c75.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/76091487-9edc7e80-5ff8-11ea-8578-e5a45a193c75.png" alt="76091487-9edc7e80-5ff8-11ea-8578-e5a45a193c75.png" /></a></p>
<p>接著使用如同魔術棒的輪廓偵測工具，捕捉圖形邊框。</p>
<p>[ Shape tracer ]</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/76091676-ff6bbb80-5ff8-11ea-8f40-14f111042477.png" alt="76091676-ff6bbb80-5ff8-11ea-8f40-14f111042477.png" /></p>
<p>由於我們使用 PixiJS，在右側 Exporter 中，請選擇 Phaser (P2)。</p>
<p>[ Exporter ]</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/76091884-60938f00-5ff9-11ea-8ee9-679058daea09.png" alt="76091884-60938f00-5ff9-11ea-8ee9-679058daea09.png" /></p>
<p>你知道嗎？Phaser 是一套完整性高的 2D 網頁遊戲引擎。</p>
<p>最後將多邊形匯出成 JSON 格式。</p>
<p>[ Publish ]</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/76091960-8a4cb600-5ff9-11ea-92fe-3b03a58e7986.png" alt="76091960-8a4cb600-5ff9-11ea-92fe-3b03a58e7986.png" /></p>
<p>仔細看看它的 JSON 結構：</p>
<p>它是由數個多邊形組合而成的喲！</p>
<pre><code class="lang-json">{ 

    <span class="hljs-attr">"flowerTop"</span>: [
        {
            <span class="hljs-attr">"shape"</span>: [ <span class="hljs-number">27.5</span>,<span class="hljs-number">145</span>, <span class="hljs-number">32.5</span>,<span class="hljs-number">138</span>, <span class="hljs-number">30.5</span>,<span class="hljs-number">152</span>, <span class="hljs-number">27.5</span>,<span class="hljs-number">149</span> ]
        },
        {
            <span class="hljs-attr">"shape"</span>: [ <span class="hljs-number">41.5</span>,<span class="hljs-number">176</span>, <span class="hljs-number">32.5</span>,<span class="hljs-number">138</span>, <span class="hljs-number">89.5</span>,<span class="hljs-number">141</span>, <span class="hljs-number">92.5</span>,<span class="hljs-number">150</span>, <span class="hljs-number">90.5</span>,<span class="hljs-number">152</span>, <span class="hljs-number">61.5</span>,<span class="hljs-number">176</span>, <span class="hljs-number">54</span>,<span class="hljs-number">180.5</span>, <span class="hljs-number">44</span>,<span class="hljs-number">180.5</span> ]
        },
    ]
}
</code></pre>
<ul>
<li>flowerTop：根據你的圖片而命名。</li>
<li>shape：該集合為其中一個多邊形的座標點。</li>
</ul>
<h2 id="heading-3">3. 套件</h2>
<p>接下來可使用套件 <a target="_blank" href="https://github.com/explooosion/hitarea-shapes">hitarea-shapes</a> 將該輪廓套至 sprite 中。</p>
<h3 id="heading-31">3.1 安裝</h3>
<pre><code class="lang-bash">npm install --save pixi.js hitarea-shapes

<span class="hljs-comment"># 或者</span>

yarn add pixi.js hitarea-shapes
</code></pre>
<p>當然，你也可以在 html 中使用 CDN：</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/hitarea-shapes"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>如果你不想使用該套件，你可以參考 <a target="_blank" href="https://github.com/eXponenta/pixi-poly">pixi-poly</a> 是如何實踐出輪廓問題的。</p>
<p>因為 <a target="_blank" href="https://github.com/explooosion/hitarea-shapes">hitarea-shapes</a> 是筆者參考並調整一些程式碼後發佈出來的～</p>
<h3 id="heading-32">3.2 載入模組與多邊形</h3>
<p>如果你是以模組化架構開發，那就直接 import 或 require 進來～</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> HitAreaShapes <span class="hljs-keyword">from</span> <span class="hljs-string">'hitarea-shapes'</span>;
<span class="hljs-keyword">import</span> data <span class="hljs-keyword">from</span> <span class="hljs-string">'flowerTop.json'</span>;
</code></pre>
<p>如果你的環境選擇使用 cdn，也沒有 babel ，</p>
<p>那你可以參考 <a target="_blank" href="https://github.com/explooosion/hitarea-shapes/blob/master/docs/index.html">hiarea-shapes example</a>，先用 fetch 將多邊形 JSON 檔案載入。</p>
<p>然後再實例一個 HitAreaShapes。</p>
<h3 id="heading-33">3.3 實例與套用</h3>
<p>直接把 sprite.hitArea 設為剛剛建立好的實例即可！</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Your sprite</span>
<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> hitAreaShapes = <span class="hljs-keyword">new</span> HitAreaShapes(data);

sprite.hitArea = hitAreaShapes;
</code></pre>
<h3 id="heading-34">3.4 結果</h3>
<p>再次看看網頁結果！</p>
<p>當滑鼠在黑色區域進行觸碰 (click/tap) ，並不會 trigger 囉～</p>
<p>同時指標也不會呈現 pointer ～</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-03-07_PixiJS%20-%20%E4%BF%AE%E6%AD%A3%20sprite%20%E7%82%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E6%99%82%E7%9A%84%20hitArea/vfb9Ucp.gif" alt="vfb9Ucp.gif" /></p>
<h2 id="heading-4">4. 後記</h2>
<p>大家也來學學 PixiJS 吧～</p>
<p>本篇方法也許不是最好，誤打誤撞學習道路上有你有我。</p>
<p>也歡迎提出更棒的做法！！</p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Flutter - 在 macOS 中使用 VSCODE 同時偵錯 Android 和 iOS]]></title><description><![CDATA[如果你使用果粉信仰的 MacPro 進行 APP 開發，那你一定要試試！


ref：Kody Technolab.


目錄

前言
事情是這樣的
開始設定
參考資料


I. 前言
最近接到某個小小案子，專案平台需支援 Android，
一開始怕 deadline 的關係，所以簡單先用 webpack + PWA 建置，
最後再用 Cordova 封裝成 apk 提供安裝。
由於提早將專案完工...
乾脆來趁著機會學學 Flutter。
本篇記錄如何在 vscode 中，使用內建的 debu...]]></description><link>https://blog.robby570.tw/flutter-macos-vscode-android-ios</link><guid isPermaLink="true">https://blog.robby570.tw/flutter-macos-vscode-android-ios</guid><category><![CDATA[Flutter]]></category><category><![CDATA[debug]]></category><category><![CDATA[Visual Studio Code]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Mon, 20 Jan 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/banner/1579488535_77802.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>如果你使用果粉信仰的 MacPro 進行 APP 開發，那你一定要試試！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579488535_77802.jpeg"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579488535_77802.jpeg" alt="1579488535_77802.jpeg" /></a></p>
<ul>
<li>ref：<a target="_blank" href="https://medium.com/@kodyTechnolab?source=post_page-----c07429ca5115----------------------">Kody Technolab.</a></li>
</ul>
<hr />
<h2 id="heading-55uu6yye">目錄</h2>
<ol>
<li><a class="post-section-overview" href="#1">前言</a></li>
<li><a class="post-section-overview" href="#2">事情是這樣的</a></li>
<li><a class="post-section-overview" href="#3">開始設定</a></li>
<li><a class="post-section-overview" href="#4">參考資料</a></li>
</ol>
<hr />
<h2 id="heading-i">I. 前言</h2>
<p>最近接到某個小小案子，專案平台需支援 Android，</p>
<p>一開始怕 deadline 的關係，所以簡單先用 webpack + PWA 建置，</p>
<p>最後再用 <a target="_blank" href="https://cordova.apache.org/">Cordova</a> 封裝成 apk 提供安裝。</p>
<p>由於提早將專案完工...</p>
<p>乾脆來趁著機會學學 <a target="_blank" href="https://flutter.dev/">Flutter</a>。</p>
<p>本篇記錄如何在 vscode 中，使用內建的 debug，</p>
<p>ㄧ次啟動 Android 與 iOS 兩個裝置，並且支援各種神器功能。</p>
<h2 id="heading-ii">II. 事情是這樣的</h2>
<p>你電腦不錯，想要同時執行 Android 與 iOS，</p>
<p>方便在開發時，可以確保雙平台的畫面與邏輯結果。</p>
<p>在多重裝置的啟用上，你可以直接開啟 terminal ，使用官方指令啟動所有裝置：</p>
<pre><code class="lang-bash">flutter run -d all
</code></pre>
<p>但卻無法在存檔 (Save) 程式碼時，自動更新 (Auto Update)，</p>
<p>通常我們使用指令啟動專案後，都會得到一段訊息：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579514200_17817.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579514200_17817.png" alt="1579514200_17817.png" /></a></p>
<p>雖然按下r可以進行 Hot Reload，但此一功能需要每次手動按，實在很麻煩...</p>
<blockquote>
<p>人生而懶，懶覺除了使人恢復體力，更是科技進步的來源。</p>
</blockquote>
<p>懶覺：指人貪睡，不愛起床。 多指早晨晚起。 如：他就愛<strong>睡懶覺</strong>，叫了幾次都不起床。</p>
<ul>
<li>ref: <a target="_blank" href="http://www.chinesewords.org/dict/212609-300.html">漢語網</a></li>
</ul>
<p>而本篇最主要的需求是要同時啟動雙平台裝置，即 Android 與 iOS。</p>
<p>並且透過 vscode ，來協助我們進行 debugger、auto hot reload。</p>
<p>慶幸的是，官方在 2019 年 12 初，釋出<a target="_blank" href="https://github.com/flutter/flutter/wiki/Multi-device-debugging-in-VS-Code/3896748c458888cbfeaa10bec429523359247765">實驗版</a>，</p>
<p>而同年底也正式支援此功能。其實距離現在也才短短不到 1 個月 XD。</p>
<p>原本筆者還不知道，於是查到早期文章的奇淫技巧...，</p>
<p>嚐盡各種苦頭，踩盡各種深坑。</p>
<p>（汗...</p>
<p>總而言之，趕緊來試試這新的功能吧！</p>
<h2 id="heading-iii">III. 開始設定</h2>
<h3 id="heading-1">1. 環境檢查</h3>
<p>在設定前，請確保 Flutter 環境是 ok 的：</p>
<pre><code class="lang-bash">flutter doctor
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579515783_90024.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579515783_90024.png" alt="1579515783_90024.png" /></a></p>
<p>● No issues found !</p>
<p>設定流程很簡單，基本上照著官方流程做就可以了：</p>
<ul>
<li><a target="_blank" href="https://github.com/flutter/flutter/wiki/Multi-device-debugging-in-VS-Code">Multi device debugging in VS Code</a></li>
</ul>
<h3 id="heading-2-launchjson">2. 設定 launch.json</h3>
<p>以專案為工作目錄，開啟 VSCODE ，</p>
<p>到左邊第4個蟲蟲，選擇 [ 建立 launch.json 檔案。 ]。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579516636_77472.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579516636_77472.png" alt="1579516636_77472.png" /></a></p>
<p>請選擇 [ Dart &amp; Flutter ]，就會得到一個範本設定檔了。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579516794_60855.png" alt="1579516794_60855.png" /></p>
<p>接著到官方文件：</p>
<ul>
<li><a target="_blank" href="https://github.com/flutter/flutter/wiki/Multi-device-debugging-in-VS-Code">Multi device debugging in VS Code</a></li>
</ul>
<p>將 Setup 內的設定複製起來，全部覆蓋貼上。</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0.2.0"</span>,
    <span class="hljs-attr">"configurations"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Current Device"</span>,
            <span class="hljs-attr">"request"</span>: <span class="hljs-string">"launch"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"dart"</span>
        },
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Android"</span>,
            <span class="hljs-attr">"request"</span>: <span class="hljs-string">"launch"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"dart"</span>,
            <span class="hljs-attr">"deviceId"</span>: <span class="hljs-string">"android"</span>
        },
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"iPhone"</span>,
            <span class="hljs-attr">"request"</span>: <span class="hljs-string">"launch"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"dart"</span>,
            <span class="hljs-attr">"deviceId"</span>: <span class="hljs-string">"iphone"</span>
        },
    ],
    <span class="hljs-attr">"compounds"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"All Devices"</span>,
            <span class="hljs-attr">"configurations"</span>: [<span class="hljs-string">"Android"</span>, <span class="hljs-string">"iPhone"</span>],
        }
    ]
}
</code></pre>
<p>存！好！就這樣！</p>
<p>設定過後，你的專案也會多一個 .vscode / launch.json 檔案。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579519006_89296.png" alt="1579519006_89296.png" /></p>
<h3 id="heading-3">3. 啟動模擬器</h3>
<p>接下來讓我們來執行看看，</p>
<p>請確保你的模擬器是否都有在執行。</p>
<p>如果 VSCODE 右下顯示，[ No Device ]，請記得點選，然後開啟。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579517149_96616.png" alt="1579517149_96616.png" /></p>
<p>例如筆者的 Android 已經啟動，但 iOS 還沒啟動，</p>
<p>廢話不多說，啟動就對了！</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579517361_66476.png" alt="1579517361_66476.png" /></p>
<p>你知道嗎？尚未開啟的模擬器會有前綴 Start 的字眼。</p>
<h3 id="heading-4">4. 進行偵錯</h3>
<p>再剛剛的 debug 頁籤中，選擇 [ All Devices ]。</p>
<p>如果你要連接的裝置，是目前作用中的一台裝置，那就選 [ Current Device ]。</p>
<p>然後點選 綠綠的按鈕 執行！</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579517714_58432.png" alt="1579517714_58432.png" /></p>
<p>同時你也一定很興奮著看著畫面在跑：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579517967_24801.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579517967_24801.png" alt="1579517967_24801.png" /></a></p>
<p>確認模擬器都開始執行你的專案。</p>
<p><a target="_blank" href="https://i.imgur.com/PqavL7b.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/PqavL7b.png" alt="PqavL7b.png" /></a></p>
<p>除了模擬器，也會自動打開網頁，提供 <a target="_blank" href="https://flutter.dev/docs/development/tools/devtools/overview">Dart DevTools</a>！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579517960_39866.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579517960_39866.png" alt="1579517960_39866.png" /></a></p>
<h3 id="heading-5-auto-hot-reload">5. Auto Hot Reload</h3>
<p>接著你會開始瘋狂的修改與存檔，</p>
<p>並且可以看到偵錯主控台多次的 Reloaded 紀錄。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/d4e40a03-8ebb-46c0-8be3-5adc0808f6e6/1579518521_64291.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579518521_64291.png" alt="1579518521_64291.png" /></a></p>
<h3 id="heading-6">6. 快速偵錯</h3>
<p>當你執行過一次後，VSCODE 底部會顯示快速偵錯的按鈕選單。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579518820_56704.png" alt="1579518820_56704.png" /></p>
<p>括弧內是你的專案名稱喇！</p>
<p>點選後，上頭會出現選單，</p>
<p>你就可以快快樂樂選擇你要的偵錯環境囉！</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-20_Flutter%20-%20%E5%9C%A8%20macOS%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20VSCODE%20%E5%90%8C%E6%99%82%E5%81%B5%E9%8C%AF%20Android%20%E5%92%8C%20iOS/1579518886_79955.png" alt="1579518886_79955.png" /></p>
<h2 id="heading-iv">IV. 參考資料</h2>
<ul>
<li><a target="_blank" href="https://dartcode.org/docs/quickly-switching-between-flutter-devices/">Quickly Switching Between Flutter Devices</a></li>
<li><p><a target="_blank" href="https://github.com/flutter/flutter/wiki/Multi-device-debugging-in-VS-Code">Multi device debugging in VS Code</a></p>
</li>
<li><p><a target="_blank" href="https://stackoverflow.com/questions/50877842/vscode-and-flutter-how-to-connect-multiple-devices">VSCode and flutter, how to connect multiple devices?</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/flutter/flutter-intellij/issues/956">Support running on all connected devices</a></p>
</li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[React.js - 太弱的我，把 Hooks 點滿就對了]]></title><description><![CDATA[都 2020 年了，還不趕快用 Hooks 參加勾肥大戰？
React 在 16.8 版之後納入 Hook 的功能。


ref：阮一峰 - React Hooks 入门教程

前言
去年還在研究所打混，經過老闆再三囑咐。對於額外技術也暫時停止學習。
身為一位學術研究員，不專注於非研究領域的程式發展，也很合邏輯 QQ。
目前 CRA ( create-react-app ) 所使用的版號，也來到 16.12.0，
學習永遠不嫌遲，趁著一年國軍 online，趕緊補一下！
本篇文章僅簡單介紹、筆記...]]></description><link>https://blog.robby570.tw/reactjs-hooks</link><guid isPermaLink="true">https://blog.robby570.tw/reactjs-hooks</guid><category><![CDATA[hooks]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 10 Jan 2020 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/banner/bg2019083104.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>都 2020 年了，還不趕快用 Hooks 參加勾肥大戰？</p>
<p>React 在 16.8 版之後納入 Hook 的功能。</p>
<p><a target="_blank" href="https://www.wangbase.com/blogimg/asset/201908/bg2019083104.jpg"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/bg2019083104.jpg" alt="bg2019083104.jpg" /></a></p>
<ul>
<li>ref：<a target="_blank" href="https://www.ruanyifeng.com/blog/2019/09/react-hooks.html">阮一峰 - React Hooks 入门教程</a></li>
</ul>
<h2 id="heading-5ymn6kia">前言</h2>
<p>去年還在研究所打混，經過老闆再三囑咐。對於額外技術也暫時停止學習。</p>
<p>身為一位學術研究員，不專注於非研究領域的程式發展，也很合邏輯 QQ。</p>
<p>目前 CRA ( <a target="_blank" href="https://create-react-app.dev/docs/getting-started/">create-react-app</a> ) 所使用的版號，也來到 16.12.0，</p>
<p>學習永遠不嫌遲，趁著一年國軍 online，趕緊補一下！</p>
<p>本篇文章僅簡單介紹、筆記，建議大家可以閱讀：</p>
<ol>
<li><a target="_blank" href="https://zh-hant.reactjs.org/docs/hooks-intro.html">React 介紹 Hook</a></li>
<li><a target="_blank" href="https://www.ruanyifeng.com/blog/2019/09/react-hooks.html">阮一峰 - React Hooks 入门教程</a></li>
</ol>
<h2 id="heading-1-hooks">1. Hooks 介紹</h2>
<p>React 在 2019 年 2 月 <a target="_blank" href="https://github.com/facebook/react/releases/tag/v16.8.0">Release v16.8.0</a>，</p>
<p>該版本推出了 Hooks ，鉤子 (？</p>
<p>可以讓我們在 functional component 進行一些 state 的控管。</p>
<p>儘管 Angular 已經來到 8.3 ...</p>
<p>儘管 Vue 已經來到 2.6 ...</p>
<p>但 Hooks 的名詞，勢必會影響不少生態圈（Ｘ</p>
<blockquote>
<p><em>春秋百家爭鳴 大家抄來抄去 到底誰有道理 誰的比較高明</em></p>
</blockquote>
<p>你知道嗎？Hooks 其實就是希望能像鉤子一樣，可以隨手勾取所需工具、物品。</p>
<h2 id="heading-2-functional-component">2. Functional Component</h2>
<p>建議讀者可以直接拿官方的專案建置練習：<a target="_blank" href="https://github.com/facebook/create-react-app">create-react-app</a></p>
<p>在使用之前， Hooks 都是建構在 functional component 上，</p>
<p>因此建議至少先看過長什麼樣子：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      Hello React With Hooks
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>當然你也可以選擇使用 ES6 的 const 進行 arrow function 宣告：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      Hello React With Hooks
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>注意！ 好你現在看過了，可以繼續看下去囉！</p>
<h2 id="heading-3-hooks">3. Hooks 鉤鉤種類</h2>
<p>React 的 Hooks 基本上常見的包含：</p>
<ul>
<li>useState</li>
<li>useContext</li>
<li>useReducer ( 本篇不介紹，不是懶，是忙投票 )</li>
<li>useEffect ( 本篇不介紹，不是懶，是忙投票 )</li>
</ul>
<h3 id="heading-usestate">useState</h3>
<p>跟 setState 是同樣意思的，useState 內直接放入初始值即可。</p>
<p>返回陣列的第一個為當前值，第二個 setUser 則是用於設定。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-string">'Anonymous'</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setUser('Robby')}&gt;
        Hi,
        <span class="hljs-tag">&lt;<span class="hljs-name">small</span>&gt;</span>{user}<span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>你知道嗎？設定值的變數名稱，以最佳實踐來說，大多使用 setUser, setData 等，駝峰式的命名。</p>
<h3 id="heading-usecontext">useContext</h3>
<p>如果我們要從父組件傳值給子子孫孫等組件，通常要 props 很多層，</p>
<p>而你的專案可能又包了一堆 HOC，例如：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> withTranslation()(connect(mapStateToProps)(Todo));
</code></pre>
<p>嚴重時會破壞你的結構，大 guy 會變成下麵這葛樣子， ( 俗稱 <strong>Wrapper Hell</strong> )：</p>
<p><a target="_blank" href="https://pbs.twimg.com/media/DoD6dSSVAAAvdkO?format=jpg&amp;name=4096x4096"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/DoD6dSSVAAAvdkO" alt="DoD6dSSVAAAvdkO" /></a></p>
<ul>
<li>Ref: <a target="_blank" href="https://twitter.com/GrexQL/status/1045110734550589441">https://twitter.com/GrexQL/status/1045110734550589441</a></li>
</ul>
<p>如今！！！</p>
<p>只要你會丟出鉤子，稍微彎拉一下...</p>
<p><a target="_blank" href="http://war3nobu.wltw.org/champion1"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578899498_3868.gif" alt="1578899498_3868.gif" /></a></p>
<p><a target="_blank" href="http://war3nobu.wltw.org/champion1"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578899530_4571.gif" alt="1578899530_4571.gif" /></a></p>
<ul>
<li><a target="_blank" href="http://war3nobu.wltw.org/cp_b01">信長之野望資訊站 - WL</a></li>
</ul>
<p>就能實現並解決這葛垢病！</p>
<p>請搭配著下方 createContext 章節閱讀。</p>
<h3 id="heading-createcontext">createContext</h3>
<p>在使用 useContext 之前，要先認識 createContext，它是用來創建 Context 的 Provider 。</p>
<p>Context ？？？ </p>
<p>Provider ？？？</p>
<p>以下使用常見的套件來比喻：</p>
<ol>
<li>react-redux 的 Provider</li>
<li>styled-components 的 ThemeProvider </li>
</ol>
<p>如果這比喻不太明白，簡單說就是要從頂部灌水泥，灌資料下去喇 QQ</p>
<p>筆者簡單舉例父子組件傳遞值範例：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> name = <span class="hljs-string">'Robby'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">User</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{name}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{props.name}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>現在將試著使用 createContex 改寫看看：</p>
<p>引入 createContex，宣告變數 UserContext，其參數就是初始值。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> name = <span class="hljs-string">'Robby'</span>;

<span class="hljs-keyword">const</span> UserContext = createContext(name);
</code></pre>
<p>接著於父子組件中使用：</p>
<p>在 UserContext 中，提供了 Provider 以及 Consumer，</p>
<ol>
<li>Provider：用於父層，並利用 value，來塞入指定的初始值。</li>
<li>Consumer：顧名思義，就是用於接收子組件，內容為回傳一個 function。</li>
</ol>
<p>請務必命名為 value，不可為其他屬性名稱。</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">User</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Provider</span>&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
      {name =&gt; name}
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Consumer</span>&gt;</span></span>
  );
}
</code></pre>
<ul>
<li>Provider 內需放入一個指定的 component，因此這邊建立一個 User 組件。</li>
<li>Consumer 內需放入一個 function，因此這邊使用一個簡易的 arrow function，</li>
</ul>
<p>過度謹慎的你可能會想改寫...</p>
<p><a target="_blank" href="https://zh.wikipedia.org/zh-tw/%E9%80%99%E5%80%8B%E5%8B%87%E8%80%85%E6%98%8E%E6%98%8E%E8%B6%85TUEEE%E5%8D%BB%E9%81%8E%E5%BA%A6%E8%AC%B9%E6%85%8E"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578706954_85843.png" alt="1578706954_85843.png" /></a></p>
<p>沒錯，你也可以拉出來再寫一個 const arrow function。</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> renderName = <span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> name;
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
      {renderName}
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Consumer</span>&gt;</span></span>
  );
}
</code></pre>
<p>需要注意的是，不可以在 Consumer 同層額外寫其他東西。</p>
<p>但你可以在 Consumer 組件中寫其他元素。</p>
<p>過度謹慎的你可能會不相信...</p>
<p><a target="_blank" href="https://zh.wikipedia.org/zh-tw/%E9%80%99%E5%80%8B%E5%8B%87%E8%80%85%E6%98%8E%E6%98%8E%E8%B6%85TUEEE%E5%8D%BB%E9%81%8E%E5%BA%A6%E8%AC%B9%E6%85%8E"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578705650_64386.png" alt="1578705650_64386.png" /></a></p>
<p>沒錯，如果你用以下方式寫，是有問題的。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
    {renderName}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>123<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Consumer</span>&gt;</span></span>
);
</code></pre>
<p>沒錯，如果你用以下方式寫，是沒問題的。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hi, my name is<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
      {renderName}
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<p>-</p>
<p>過度謹慎的你可能會越想越不對勁...</p>
<p><a target="_blank" href="https://zh.wikipedia.org/zh-tw/%E9%80%99%E5%80%8B%E5%8B%87%E8%80%85%E6%98%8E%E6%98%8E%E8%B6%85TUEEE%E5%8D%BB%E9%81%8E%E5%BA%A6%E8%AC%B9%E6%85%8E"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578704598_81701.png" alt="1578704598_81701.png" /></a></p>
<p>一開始已經在 createContext 設定過初始值：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> UserContext = createContext(name);
</code></pre>
<p>為何在又要在 UserContext.Provider 內設定 value 初始值？</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>&gt;</span>
</code></pre>
<p>：「沒錯親愛的，是多寫了。」</p>
<p>如果父層省略掉 Provider ...，</p>
<p>那就是直接以當初 createContext 的初始值為主，</p>
<p>否則會優先以 Provider 的 value 為主。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> name = <span class="hljs-string">'Robby'</span>;

<span class="hljs-keyword">const</span> UserContext = createContext(name);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">User</span> /&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Consumer</span>&gt;</span>
      {name =&gt; name}
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Consumer</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>所以上述範例才故意這樣寫兩次。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/q7Dr5pR.png" alt="q7Dr5pR.png" /></p>
<p>-</p>
<p>過度謹慎的你可能會想省時間...</p>
<p><a target="_blank" href="https://zh.wikipedia.org/zh-tw/%E9%80%99%E5%80%8B%E5%8B%87%E8%80%85%E6%98%8E%E6%98%8E%E8%B6%85TUEEE%E5%8D%BB%E9%81%8E%E5%BA%A6%E8%AC%B9%E6%85%8E"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578706445_77621.png" alt="1578706445_77621.png" /></a></p>
<p>每次都寫 UserContext.Consumer 也太大坨麻煩了吧？</p>
<p>：「沒錯親愛的，終於可以使用 useContext 了！」</p>
<p>useContext 必須放入經由 createContext 所建立的變數。</p>
<p>由於當初設定的 name 為字串，因此勾出來的時候，當然也就是字串囉！</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useContext, Fragment } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> name = <span class="hljs-string">'Robby'</span>;

<span class="hljs-keyword">const</span> UserContext = createContext(name);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">User</span> /&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> name = useContext(UserContext);
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Fragment</span>&gt;</span>
      {name}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Fragment</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>你知道嗎？ 組件不想用任何元素包住子元素時， 可以使用 Fragment (片段) ，避免冗余的元素。</p>
<p>礙於時間不早，加上趕著早起投票。</p>
<p>本篇僅教學到 useState、useContext、createContext 應用。</p>
<p>為了不讓大家覺得筆者本篇... 過於敷衍過於廢文</p>
<p>最後提供一葛整合今日所學的奇淫技巧。</p>
<p>你知道嗎？奇淫技巧不一定代表是好的作法。</p>
<h2 id="heading-4-usestateusecontextcreatecontext">4. useState＋useContext＋createContext</h2>
<p>前面學到的 useContext，依照本篇的內容，目前僅能讀取，卻不能改變。</p>
<p>大多的使用方式為： useContext + useReducer</p>
<p>有興趣的人，建議可以先利用「神Ｏ海Ｏ」詢問看看有沒有文章可以看。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/1578677584_58859.jpg" alt="1578677584_58859.jpg" /></p>
<p>接著，來為大家介紹這種邪門歪道，殘害新人的範例應用，</p>
<p>參照剛剛的範例，我們繼續修改：</p>
<p>首先在上面 import 我們要使用的鉤鉤，</p>
<p>接著將 createContext 初始值改為 null， 因為等等要改在 component 內初始化。</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useContext, useState, Fragment } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> UserContext = createContext(<span class="hljs-literal">null</span>);
</code></pre>
<p>接著我們利用 useState，建立一葛 name 的 state，</p>
<p>並使用 Provider 將初始值設定為陣列 [name, setName]。</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">'Robby'</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{[name,</span> <span class="hljs-attr">setName</span>]} &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">User</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Provider</span>&gt;</span></span>
  );
}
</code></pre>
<p>你知道嗎？為了方便管理，可以使用物件，將 [name, setName] 用物件包起來，讀取時就會是物件！</p>
<p>最後在子組件同樣使用 useContext 提取出來。</p>
<p>就可以進行讀取，以及使用 setName 修改囉～</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [name, setName] = useContext(UserContext);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Fragment</span>&gt;</span>
      {name}
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setName('Jack')}&gt;ChangeName<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Fragment</span>&gt;</span></span>
  );
}
</code></pre>
<p>完整程式碼如下：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useContext, useState, Fragment } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> UserContext = createContext(<span class="hljs-literal">null</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">'Robby'</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UserContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{[name,</span> <span class="hljs-attr">setName</span>]} &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">User</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">UserContext.Provider</span>&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">User</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [name, setName] = useContext(UserContext);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Fragment</span>&gt;</span>
      {name}
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setName('Jack')}&gt;ChangeName<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Fragment</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>雖然上述做法可以達到 Context 的修改，不過很少這樣使用。</p>
<p>你知道嗎？筆者沒有特別這樣使用過... 是不是很 GY。</p>
<p>因為基於 useState 所產生的值，應由所屬的 component 控管。</p>
<p>因此正規來說，還是會以 useContext + useReducer 的方式進行管理。</p>
<p>-</p>
<h2 id="heading-5pya5b6m">最後</h2>
<p>什麼是勾肥大戰？你不知道！？太弱惹喇！</p>
<p><a target="_blank" href="https://truth.bahamut.com.tw/s01/201102/916edd5e8389184a0131477e7511602c.JPG"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2020-01-10_React.js%20-%20%E5%A4%AA%E5%BC%B1%E7%9A%84%E6%88%91%EF%BC%8C%E6%8A%8A%20Hooks%20%E9%BB%9E%E6%BB%BF%E5%B0%B1%E5%B0%8D%E4%BA%86/916edd5e8389184a0131477e7511602c.JPG" alt="916edd5e8389184a0131477e7511602c.JPG" /></a></p>
<ul>
<li>Ref：<a target="_blank" href="https://forum.gamer.com.tw/Co.php?bsn=03044&amp;sn=2277663">【心得】勾肥入門教學文【1.25版】</a>，</li>
</ul>
<h3 id="heading-xmcom6vowwsewfiomameaoo8joakleelqowou8jooeheoehe8gv8"><em>那麼就先這樣，投票去，ㄅㄅ！</em></h3>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Cloudflare - 在世界各地邊緣你的 Workers 吧！]]></title><description><![CDATA[邊緣人在世界各地邊緣程式碼。
cloudflare 推出 Workers ，每葛人都該來體驗邊緣運算的快感！


ref：Everyone can now run JavaScript on Cloudflare with Workers


前言
最近因為 SideProject 而買了新網址，又來到 Cloudflare 設定 DNS 了。
在 google 強大的推薦演算法之下，看到了 Cloudflare Workers 的產品廣告，
才發現原來服務已經推出一年多了～還不趕快「跟著大哥走...]]></description><link>https://blog.robby570.tw/cloudflare-workers</link><guid isPermaLink="true">https://blog.robby570.tw/cloudflare-workers</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Mon, 28 Oct 2019 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/banner/1572227963_93755.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>邊緣人在世界各地邊緣程式碼。</p>
<p>cloudflare 推出 Workers ，每葛人都該來體驗邊緣運算的快感！</p>
<p><a target="_blank" href="https://blog.cloudflare.com/cloudflare-workers-unleashed/"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572227963_93755.png" alt="1572227963_93755.png" /></a></p>
<ul>
<li>ref：<a target="_blank" href="https://blog.cloudflare.com/cloudflare-workers-unleashed/">Everyone can now run JavaScript on Cloudflare with Workers</a></li>
</ul>
<hr />
<h2 id="heading-5ymn6kia">前言</h2>
<p>最近因為 <a target="_blank" href="https://github.com/explooosion/browndust-share">SideProject</a> 而買了新網址，又來到 <a target="_blank" href="https://www.cloudflare.com/zh-tw/">Cloudflare</a> 設定 DNS 了。</p>
<p>在 google 強大的推薦演算法之下，看到了 <a target="_blank" href="https://www.cloudflare.com/zh-tw/products/cloudflare-workers/">Cloudflare Workers</a> 的產品廣告，</p>
<p>才發現原來服務已經推出一年多了～還不趕快「<a target="_blank" href="https://gnn.gamer.com.tw/detail.php?sn=160363">跟著大哥走</a>！」</p>
<hr />
<h2 id="heading-55uu6yye">目錄</h2>
<ol>
<li><a class="post-section-overview" href="#1">介紹喇賽</a></li>
<li><a class="post-section-overview" href="#2">實作說明</a></li>
<li><a class="post-section-overview" href="#3">以 Dashboard 方式建立</a></li>
<li><a class="post-section-overview" href="#4">以 CLI 方式建立</a></li>
</ol>
<h2 id="heading-5lia44cb5lul57s55zah6lo9">一、介紹喇賽</h2>
<p>Cloudflare Workers 在去年 2018 年初推出 beta ，於 3 月正式 release 推出 🎉！</p>
<p>Workers 以邊緣運算  ( <a target="_blank" href="https://zh.wikipedia.org/wiki/%E9%82%8A%E7%B7%A3%E9%81%8B%E7%AE%97">Edge computing</a> ) 運作，目前在世界各地有很多節點 ( node )。</p>
<p>根據官方指出，目前服務已經擴展到 90 個國家，194 個城市。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572229745_14051.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572229745_14051.png" alt="1572229745_14051.png" /></a></p>
<ul>
<li>ref：<a target="_blank" href="https://blog.cloudflare.com/scaling-the-cloudflare-global/">Cloudflare Global Network Expands to 193 Cities</a></li>
<li>ref：<a target="_blank" href="https://www.cloudflare.com/network/">The Cloudflare Global Anycast Network</a> ( updated to 194 )</li>
</ul>
<p>你知道嗎？　臺灣也有站點哦！目前設置在台北，是第 77 個 data center！</p>
<ul>
<li><a target="_blank" href="https://blog.cloudflare.com/taipei/">台北：CloudFlare的第七十七個數據中心已經上線喔！</a></li>
<li><a target="_blank" href="https://www.cloudflarestatus.com">Cloudflare System Status</a></li>
</ul>
<p><a target="_blank" href="https://www.cloudflarestatus.com">​</a>Cloudflare Workers 也可以叫做 Web Workers，</p>
<p>是基於 W3C 標準規範的 <a target="_blank" href="https://w3c.github.io/ServiceWorker/">Service Worker</a> 所開發而來，因此主要以 JavsScript 進行撰寫。</p>
<p>對於要開始火紅的 wasm ( <a target="_blank" href="https://webassembly.org/">Webassembly</a>) 更不能放過，</p>
<p>Cloudflare 在去年 10 月開始支援 Webassembly，</p>
<p>開發者可以將 C/C++、Rust、Go 等語言 Compile 成 wasm，再進行提交。</p>
<ul>
<li><a target="_blank" href="https://blog.cloudflare.com/webassembly-on-cloudflare-workers/">WebAssembly on Cloudflare Workers</a></li>
</ul>
<p>Workers 可以做些什麼事情？</p>
<p>大概就是跟 <a target="_blank" href="https://aws.amazon.com/tw/lambda/">AWS Lambda</a> 87% 相似。</p>
<p>Workers 這葛 <a target="_blank" href="https://en.wikipedia.org/wiki/Serverless_computing">serverless</a> 服務，除了主要提供 cache ，</p>
<p>還可以建置各種的 function、rewrite、redirect、A/B testing ...，</p>
<p>當然也可以渲染 json 或是 html。</p>
<p>如果你想將網頁佈署在 Worker 上，那就建議使用 <a target="_blank" href="https://workers.cloudflare.com/sites">Workers Sites</a>。</p>
<p>由於要將部署的網站上傳到 <a target="_blank" href="https://www.cloudflare.com/products/workers-kv/">storage</a> 需要付費，因此要自行斟酌看看費用惹。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572239290_13745.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572239290_13745.png" alt="1572239290_13745.png" /></a></p>
<p>如果不想付費...</p>
<p>那你就得想辦法把網站程式碼全部塞進 JS 裡面：</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">const</span> init = {
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html;charset=UTF-8'</span>,
    },
  }
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(someHTML, init)
}
addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> event.respondWith(handleRequest(event.request))
})
<span class="hljs-keyword">const</span> someHTML =  <span class="hljs-string">`&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
  &lt;h1&gt;Hello World&lt;/h1&gt;
  &lt;p&gt;This is all generated using a Worker&lt;/p&gt;
  &lt;iframe
      width="560"
      height="315"
      src="https://www.youtube.com/embed/dQw4w9WgXcQ"
      frameborder="0"
      allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen
  &gt;&lt;/iframe&gt;
  &lt;/body&gt;
&lt;/html&gt;
`</span>
</code></pre>
<p>上述的作法，其實就是 SSR、isomorphic 的概念，</p>
<p>有興趣可以參考專案：<strong><a target="_blank" href="https://github.com/explooosion/isomorphic-worker-webpack">isomorphic-worker-webpack</a></strong></p>
<h2 id="heading-5lqm44cb5am5l2c6kqq5pio">二、實作說明</h2>
<p>本文實作範例，其實跟官網步驟一樣，可以看看 <a target="_blank" href="https://developers.cloudflare.com/workers/">Cloudflare Workers Documentation</a>。</p>
<p>目前文件提供不少 <a target="_blank" href="https://developers.cloudflare.com/workers/templates/">Templates</a> 範例，可以試著在裡面發散構想。</p>
<p>搞不好你還可以因此發 PR 給 <a target="_blank" href="https://github.com/cloudflare">Cloudflare</a>！</p>
<p>Worker 的建立，可以選擇到 Dashboard 頁面建立或是使用  CLI 建置：</p>
<h4 id="heading-dashboard">[ Dashboard ]</h4>
<p>Cloudflare 提供了後台管理系統，讓你可以直接在網頁上撰寫程式碼並測試，</p>
<p>但是無法提供程式碼的預處理（例如：webpack, babel, bundle, minify）。</p>
<h4 id="heading-cli">[ CLI ]</h4>
<p>利用套件管理系統 nodejs ( npm ) 安裝腳本指令套件，或使用 rust ( cargo )，</p>
<p>在 local 寫好專案後，利用 build，將 ES6 編譯壓縮，再進行 publish。</p>
<p>兩者各有好處，以下分別簡單步驟說明：</p>
<p>在開始之前，請務必到 <a target="_blank" href="https://dash.cloudflare.com/sign-up">Cloudflare</a> 註冊帳號。</p>
<h2 id="heading-dashboard-1">三、以 Dashboard 方式建立</h2>
<p>在登入後，請至首頁右側，<a target="_blank" href="https://dash.cloudflare.com">點選</a> Workers。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572263239_80575.png" alt="1572263239_80575.png" /></p>
<p>在初次進入後台時，</p>
<p>系統會要求你設定 subdomain 名稱，作為 Workers 部署之後的雲端網址，</p>
<p>設定之後將無法更改！... 筆者手殘 ... 好想更改啊！！！！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572263596_44201.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572263596_44201.png" alt="1572263596_44201.png" /></a></p>
<ul>
<li>圖源：<a target="_blank" href="https://www.sitepoint.com/cloudflare-workers/">An Introduction to Cloudflare Workers</a></li>
</ul>
<p>進入 Workers Dashboard 後，捲到最下方會有 Account ID，</p>
<p>請複製或留意取得方式，如果是使用 CLI 建立，則會需要提供該 ID。</p>
<p>別擔心，隨時都可以到後台取得 Account ID。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572264547_79724.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572264547_79724.png" alt="1572264547_79724.png" /></a></p>
<p>接著開始建立新的 Worker，很明顯可以看到一顆按鈕 Create a Worker，點下去。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572264698_52554.png" alt="1572264698_52554.png" /></p>
<p>進入編輯頁面後，左上角圖片紅框處可以看到系統隨機給予的 Worker Name，當然，是可以更改的！</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572264812_61768.png" alt="1572264812_61768.png" /></p>
<p>程式碼編輯區塊可以看到已經有 Sample Code，右半邊則是 Preview。</p>
<p>大多 Worker 都是建立 fetch 的事件監聽，當 user 進行頁面 Request 的時候，就會觸發。</p>
<p>在範例中，Response 會直接渲染文字以及 http-status 回去。</p>
<p>此外也可以看到 Wasm 的頁籤按鈕，你可以直接上傳編譯好的 wasm 檔案上去。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572265049_79675.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572265049_79675.png" alt="1572265049_79675.png" /></a></p>
<p>接著在下方可以看見，系統預設已經幫我們啟用：</p>
<p>Will be deployed to your workers.dev subdomain</p>
<p>如果不使用，則不會發佈到帶有 .dev 的網址。</p>
<p>除非你要指定到自己的網域，否則就要啟用他送你的 workers.dev subdomain。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572265329_13015.png" alt="1572265329_13015.png" /></p>
<p>再點選 Save and Deploy 之後，系統會提示部署後的網址。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572265551_99108.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572265551_99108.png" alt="1572265551_99108.png" /></a></p>
<p>接著你可以點選頁面左上角 Cloudflare Logo 下方的左箭頭〈，返回列表。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572265769_01402.png" alt="1572265769_01402.png" /></p>
<p>回到 Workers Dashboard，就會看到新的 Worker 已經在列表中。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572265651_79275.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572265651_79275.png" alt="1572265651_79275.png" /></a></p>
<p>如果你點開瀏覽，應該會發現目前無法以 SSL 方式連入，請改用 http:// 即可！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572266238_38023.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572266238_38023.png" alt="1572266238_38023.png" /></a></p>
<p>最後就可以成功看到頁面出現：</p>
<p>hello world</p>
<p>如果想要使用 SSL 方式連入，請先準備好你的 Domain。</p>
<h3 id="heading-worker-domain">※ 自訂 Worker Domain</h3>
<p>以下 Cloudflare 是筆者自己的網域範例。</p>
<p>先切換到自己註冊的 DNS 管理後台，</p>
<p>接著點選 Workers，下方新增路由，Add route。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572267150_73665.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572267150_73665.png" alt="1572267150_73665.png" /></a></p>
<p>在表單中，Route 是你 DNS 的路徑規則，</p>
<p>robby.tw 是我的域名，前面的 test 就是你指定的 route。</p>
<p>* 星號....就不用解釋惹吧，還不知道就該打屁股惹。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572269227_179.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572269227_179.png" alt="1572269227_179.png" /></a></p>
<p>接著到 DNS 新增 <a target="_blank" href="https://zh.wikipedia.org/zh-tw/CNAME%E8%AE%B0%E5%BD%95">CNAME</a>：</p>
<ul>
<li>Type：CNAME</li>
<li>Name：就看你想取蛇摸</li>
<li>Target：steep-dawn-7db4.your_subdomain.workers.dev ( 範例 )</li>
</ul>
<p>steep-dawn-7db4 只是我的 Worker Name。</p>
<p>你可以複製剛剛的 Worker dev 網址，貼上並修改，去除 http 等格式。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572269927_60483.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572269927_60483.png" alt="1572269927_60483.png" /></a></p>
<p>於是頁面就出來惹～<a target="_blank" href="https://test.robby570.tw">https://test.robby570.tw</a></p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572270031_24985.png" alt="1572270031_24985.png" /></p>
<h2 id="heading-cli-1">四、以 CLI 方式建立</h2>
<p>本範例使用 nodejs 的 npm 作為 CLI 後端環境執行。</p>
<p>根據官方非常簡單的<a target="_blank" href="https://developers.cloudflare.com/workers/quickstart/">說明文件</a>，步驟如下：</p>
<h3 id="heading-41">4.1 下載套件</h3>
<pre><code class="lang-bash">npm install -g @cloudflare/wrangler
</code></pre>
<p>注意：不支援 32 位元... 原本想在工作的時候測試 CLI... QQ</p>
<h3 id="heading-42-local">4.2 初次要在 local 設定金鑰</h3>
<pre><code class="lang-bash">wrangler config
</code></pre>
<p>只有第一次要設定哦！</p>
<p>依序會要你填入四個項目：</p>
<ol>
<li><a class="post-section-overview" href="#4-1">Account ID</a></li>
<li><a class="post-section-overview" href="#4-2">Zone ID</a></li>
<li><a class="post-section-overview" href="#4-3">Global API Key</a></li>
<li><a class="post-section-overview" href="#4-4">Email address</a></li>
</ol>
<p>Account ID</p>
<p>在你的 Cloudflare Dashboard 就有囉！</p>
<p>Zone ID</p>
<p>欄位內容不是必須，</p>
<p>除非你要像前面步驟一樣，想要指派給自訂的 CNAME，</p>
<p>如果想使用原本的網址，就不需要設定。</p>
<p>Global API Key</p>
<p>如果你會看英文就看文件說明：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572270630_04918.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572270630_04918.png" alt="1572270630_04918.png" /></a></p>
<p>如果你比較笨，看看下面的步驟....</p>
<p>-----------------------------------------------------------------------</p>
<p>到你的 Workers 首頁右邊，點選 Get your API token。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572270768_41695.png" alt="1572270768_41695.png" /></p>
<p>找到最下方的 API Keys，點選 Global API Key 的 View</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572271543_40837.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572271543_40837.png" alt="1572271543_40837.png" /></a></p>
<p>輸入帳號密碼，然後勾選一葛 通過率很低 的 reCAPTCHA。 </p>
<p>筆者也不知道為什麼... 總是驗證失敗。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572271644_43675.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572271644_43675.png" alt="1572271644_43675.png" /></a></p>
<p>最後得到的 Key 就把他複製起來，</p>
<p>貼到剛剛指令 wrangler config 所要求提供的 Global API Key。</p>
<p>Email address</p>
<p>最後的 email 各位讀者們就自行發揮ㄅ～</p>
<p>-----------------------------------------------------------------------</p>
<h3 id="heading-43">4.3 初始專案</h3>
<p>接著開始初始 Worker 專案</p>
<pre><code class="lang-bash">wrangler generate my-worker
</code></pre>
<p>你也可以使用現成 Github 的專案</p>
<pre><code class="lang-bash">wrangler generate my-router-app https://github.com/cloudflare/worker-template-router
</code></pre>
<p>進入目錄</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-worker
</code></pre>
<p>用 VScode 打開，或任何你喜歡的編輯器。</p>
<pre><code class="lang-bash">code .
</code></pre>
<p>[ wrangler.toml ]</p>
<pre><code>account_id = <span class="hljs-string">""</span>
name = <span class="hljs-string">"my-worker"</span>
type = <span class="hljs-string">"webpack"</span>
route = <span class="hljs-string">""</span>
workers_dev = <span class="hljs-literal">true</span>
zone_id = <span class="hljs-string">""</span>
</code></pre><ul>
<li>account_id：在 cloudflare dashboard 下方可以找到，上文也有提到，用於辨別帳號的</li>
<li>name：發佈後的 Worker 名稱</li>
<li>type：wrangler 跟 react-create-app 一樣，最後會經由 webapck bundle</li>
<li>route：如同剛剛前面從網頁上新增的 route 一樣，例如 https://test.my_domain.tw/*</li>
<li>workers_dev：是否部署在 dev 上</li>
<li>zone_id：如果你要在指定的網域上使用，可以在 cloudflare 所要使用的網域上找到 </li>
</ul>
<p>如果你想要使用一些自訂的 webpack lib，可以在 wrangler.toml，新增一行：</p>
<p>[ wrangler.toml ]</p>
<pre><code>webpack_config = <span class="hljs-string">"webpack.config.js"</span>
</code></pre><ul>
<li>webpack_config：指定你的檔案路徑與檔名</li>
</ul>
<p>內容就看你想怎麼設定，例如：</p>
<p>[ webpack.config.js ]</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv-webpack'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
<span class="hljs-keyword">const</span> HtmlWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'html-webpack-plugin'</span>);

<span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">mode</span>: <span class="hljs-string">'production'</span>,
    <span class="hljs-attr">entry</span>: <span class="hljs-string">'index.js'</span>,
    <span class="hljs-attr">module</span>: {
        <span class="hljs-attr">rules</span>: [
            {
                <span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.html$/i</span>,
                use: <span class="hljs-string">'raw-loader'</span>,
            },
            {
                <span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.js$/</span>,
                exclude: <span class="hljs-regexp">/node_modules/</span>,
                use: <span class="hljs-string">'babel-loader'</span>
            },
        ],
    },
    <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> Dotenv(),
        <span class="hljs-keyword">new</span> HtmlWebpackPlugin(),
    ],
};
</code></pre>
<p>[ index.js ]</p>
<p>這葛，就不解釋惹，官網文件看看，有很多範例應用，建議讀者要具備 <a target="_blank" href="https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Headers">HTTP headers</a> 的知識。</p>
<pre><code class="lang-javascript">addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  event.respondWith(handleRequest(event.request))
})
<span class="hljs-comment">/**
 * Respond with hello worker text
 * <span class="hljs-doctag">@param <span class="hljs-type">{Request}</span> <span class="hljs-variable">request</span></span>
 */</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-string">'Hello worker!'</span>, {
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/plain'</span> },
  })
}
</code></pre>
<h3 id="heading-44">4.4 預覽專案</h3>
<p>接著可以預覽畫面。</p>
<pre><code class="lang-bash">wrangler preview --watch
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572305324_1316.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572305324_1316.png" alt="1572305324_1316.png" /></a></p>
<p>如果啟動的時候出現下列訊息，代表你 wrangler.toml 的 account_id 忘了設定囉！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572305307_06625.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572305307_06625.png" alt="1572305307_06625.png" /></a></p>
<h3 id="heading-45">4.5 建置專案</h3>
<p>調適好之後，就可以建置專案。</p>
<pre><code class="lang-bash">wrangler build
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572305786_5931.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572305786_5931.png" alt="1572305786_5931.png" /></a></p>
<h3 id="heading-46">4.6 佈署專案</h3>
<p>最後再將你的 Worker 發佈到 Cloudflare 上即可。</p>
<pre><code class="lang-bash">wrangler publish
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572305887_65647.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572305887_65647.png" alt="1572305887_65647.png" /></a></p>
<p>這時候回到 <a target="_blank" href="https://dash.cloudflare.com/">Cloudflare Workers Dashboard</a> ，就可以看到剛剛建置的 Worker 了。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572306082_68797.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572306082_68797.png" alt="1572306082_68797.png" /></a></p>
<p>如果你有設定 zone_id，那就可以在 <a target="_blank" href="https://dash.cloudflare.com">Cloudflare</a> 上的網域 Workers 列表中找到！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/3beca2b9-0a7c-4a6d-9a70-20cd6b7c199e/1572318735_90901.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-10-28_Cloudflare%20-%20%E5%9C%A8%E4%B8%96%E7%95%8C%E5%90%84%E5%9C%B0%E9%82%8A%E7%B7%A3%E4%BD%A0%E7%9A%84%20Workers%20%E5%90%A7%EF%BC%81/1572318735_90901.png" alt="1572318735_90901.png" /></a></p>
<h3 id="heading-47-dev-prodiction">4.7 佈署專案 dev 與 prodiction</h3>
<p>如果 Worker 在 push 的時候，</p>
<p>想分成 dev 與 production，可以將 wrangler.toml 新增 [ env.* ] ，</p>
<p>例如：[ env.production ]</p>
<p>[ wrangler.toml ]</p>
<pre><code>account_id = <span class="hljs-string">""</span>
name = <span class="hljs-string">"my-worker"</span>
type = <span class="hljs-string">"webpack"</span>
webpack_config = <span class="hljs-string">"webpack.config.js"</span>
workers_dev = <span class="hljs-literal">true</span>
entry-point = <span class="hljs-string">"workers-site"</span>

[env.production]
zone_id = <span class="hljs-string">""</span>
route = <span class="hljs-string">"https://your-website/*"</span>
</code></pre><p>當使用 publish 指令，則 [ env.production ] 的 zone_id, route 會被忽略。</p>
<pre><code class="lang-bash">wrangler publish
</code></pre>
<p>當使用 publish --env production 指令，則 [ env.production ] 的 zone_id, route 會被帶入。</p>
<pre><code class="lang-bash">wrangler publish --env production
</code></pre>
<h4 id="heading-xs7pes4iuwwjwwjwgwl8jowqhos9jemciue3os7los5nwqwoumciue3owmluwqp8gv8"><em>以上小小心得，各位邊緣仔也快去邊緣化吧！</em></h4>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Github - CICD 使用 Actions 以 React 建置 pages 為例]]></title><description><![CDATA[Actions 推出啦～～趕快來體驗看看！
讓網站運作通通自己動起來！


圖源：https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/

前言
好久沒發文了，距離上次文章已經有十個月左右，
接下來即將入伍，本篇之後大概又要隔一年了...
前陣子 Github 推出了 CI/CD，大概是換了富爸爸的關係吧（Ｘ
這次記錄一下，並以 create-react-app 作為範例，
當 local 端 push 之後，自動將專案...]]></description><link>https://blog.robby570.tw/github-cicd-actions-react-pages</link><guid isPermaLink="true">https://blog.robby570.tw/github-cicd-actions-react-pages</guid><category><![CDATA[GitHub]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Fri, 06 Sep 2019 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/banner/1567739924_33717.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Actions 推出啦～～趕快來體驗看看！</p>
<p>讓網站運作通通自己動起來！</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567739924_33717.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567739924_33717.png" alt="1567739924_33717.png" /></a></p>
<ul>
<li>圖源：<a target="_blank" href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/">https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/</a></li>
</ul>
<h2 id="heading-5ymn6kia">前言</h2>
<p>好久沒發文了，距離上次文章已經有十個月左右，</p>
<p>接下來即將入伍，本篇之後大概又要隔一年了...</p>
<p>前陣子 Github 推出了 <a target="_blank" href="https://zh.wikipedia.org/wiki/持續整合">CI</a>/<a target="_blank" href="https://zh.wikipedia.org/wiki/持續交付">CD</a>，大概是換了富爸爸的關係吧（Ｘ</p>
<p>這次記錄一下，並以 <a target="_blank" href="https://github.com/facebook/create-react-app">create-react-app</a> 作為範例，</p>
<p>當 local 端 push 之後，自動將專案編譯建置 (yarn build)，</p>
<p>並透過 <a target="_blank" href="https://pages.github.com/">Github Pages</a>，建立並持續更新靜態網站。</p>
<p>後續的延伸，可以用在 Personal Website、Blog、SideProject 等等。</p>
<h2 id="heading-55uu6yye">目錄</h2>
<ol>
<li><a class="post-section-overview" href="#1">事前準備</a></li>
<li><a class="post-section-overview" href="#2">建立Repo</a></li>
<li><a class="post-section-overview" href="#3">新增專案</a></li>
<li><a class="post-section-overview" href="#4">YML設定檔</a></li>
<li><a class="post-section-overview" href="#5">建立令牌</a></li>
<li><a class="post-section-overview" href="#6">設定版本控制</a></li>
<li><a class="post-section-overview" href="#7">驗證結果</a></li>
<li><a class="post-section-overview" href="#8">參考文章</a></li>
</ol>
<h2 id="heading-5lql5ymn5rqw5ykz">事前準備</h2>
<p>請記得至 <a target="_blank" href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/">Github Actions</a>，申請 beta 服務使用。</p>
<p>申請印象沒有立即生效，可能需要等數天。</p>
<p>如果申請成功，應該可以發現你任何一個 repo，都會多一個 Actions 的選項：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567742053_26249.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567742053_26249.png" alt="1567742053_26249.png" /></a></p>
<h2 id="heading-repo">建立 Repo</h2>
<p>在 Github 上建立新的 <a target="_blank" href="https://github.com/new">repository</a>，本文以 my-app 為例。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567743262_30611.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567743262_30611.png" alt="1567743262_30611.png" /></a></p>
<h2 id="heading-5paw5ake5bci5qgi">新增專案</h2>
<p>打開你的 IDE 使用<a target="_blank" href="https://github.com/facebook/create-react-app">官方指令</a>建置：</p>
<pre><code class="lang-bash">npx create-react-app my-app
</code></pre>
<p>由於本文 React 不是重點，因此安裝過程不再贅述說明。</p>
<p>由於發佈的靜態網頁網址是在：http://{你的帳號}.github.io/my-app</p>
<p>所有 link, script 的檔案相對路經，必須要去設定 react 環境變數 %PUBLIC_URL% ，</p>
<p>讓路徑都在 /my-app 之下。</p>
<p>可參考：<a target="_blank" href="https://create-react-app.dev/docs/deployment#building-for-relative-paths">create-react-app - Building for Relative Paths</a></p>
<p>因此請編輯 package.json，新增 homepage，並請依照個人的 github 帳號網址而定，</p>
<p>或是綁定的 cname ，例如：http://robby570.tw/my-app。</p>
<p>[ package.json ]</p>
<pre><code class="lang-json">{
 <span class="hljs-attr">"homepage"</span>: <span class="hljs-string">"http://explooosion.github.io/my-app"</span>,
}
</code></pre>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567746547_21315.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567746547_21315.png" alt="1567746547_21315.png" /></a></p>
<h2 id="heading-yml">YML 設定檔</h2>
<p><a target="_blank" href="https://zh.wikipedia.org/wiki/YAML">yml</a> 是一種描述資料的一種格式結構，</p>
<p>如果你是網頁開發人員，且具有 CICD、k8s 經驗者，應該不陌生。</p>
<p>請在專案根目錄，建立資料夾 .github，子層再建立資料夾 workflows，</p>
<p>該子層資料夾中，建立檔案：main.yml，如下圖範例。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567747543_62604.png" alt="1567747543_62604.png" /></p>
<p>接著補上描述內容：</p>
<p>[ main.yml ]</p>
<pre><code class="lang-makefile"><span class="hljs-section">name: Build and Deploy</span>
<span class="hljs-section">on:</span>
  push:
    branches:
      - master
<span class="hljs-section">jobs:</span>

  install-and-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [8.x, 10.x, 12.x]
    steps:
    - uses: actions/checkout@master
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@master
      with:
        node-version: ${{ matrix.node-version }}
    - name: Install and Test
      run: |
        yarn
        yarn test
      env:
        CI: true

  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master

    - name: Build and Deploy
      uses: JamesIves/github-pages-deploy-action@master
      env:
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
        BRANCH: gh-pages
        FOLDER: build
        BUILD_SCRIPT: cp .env.example .env.local &amp;&amp; yarn &amp;&amp; yarn build
</code></pre>
<p>以下簡述一下設定說明。</p>
<p>詳細請查看：<a target="_blank" href="https://github.com/JamesIves/github-pages-deploy-action">github-pages-deploy-action</a> 或是 <a target="_blank" href="https://help.github.com/en/articles/configuring-a-workflow">Configuring a workflow</a> </p>
<p>name: Build and Deploy </p>
<p>代表本次整體任務的名稱，會顯示於下方紅框處中：</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567747953_85882.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567747953_85882.png" alt="1567747953_85882.png" /></a></p>
<p>on:</p>
<p>代表處發任務的時機，本次設定為當 master 進行 push 的時候便會觸發。</p>
<p>jobs:</p>
<p>所要進行的工作，本次設定有兩個工作，可任意命名，分別為：install-and-test、build-and-deploy。</p>
<p>install-and-test：該工作主要進行安裝與測試。</p>
<p>build-and-deploy：該工作則是將專案進行建置，並且程式碼 push 至指定的 branch 上。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567748330_73247.png" alt="1567748330_73247.png" /></p>
<p>uses: JamesIves/github-pages-deploy-action@master</p>
<p>由於部署的動作要進行一連串的 git 指令處理，因此偷懶學習一下，</p>
<p>使用人家寫好的腳本 <a target="_blank" href="https://github.com/JamesIves/github-pages-deploy-action">JamesIves/github-pages-deploy-action</a>。</p>
<p>ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}</p>
<p>由於使用第三方的腳本指令，需要提供存取權限，因此必須提供令牌。</p>
<p>後面會接著說明如何設定。</p>
<p>BRANCH: gh-pages</p>
<p>github 除了 master 可以作為靜態網站外，另一個選擇就是另開分支，命名為 gh-pages 。</p>
<p>可參考：<a target="_blank" href="https://help.github.com/en/articles/configuring-a-publishing-source-for-github-pages">Configuring a publishing source for GitHub Pages</a></p>
<p>FOLDER: build</p>
<p>根據 <a target="_blank" href="https://github.com/JamesIves/github-pages-deploy-action#configuration-">github-pages-deploy-action</a> 設定說明，編譯建置的輸出資料夾位於 build 裡面。</p>
<p>如果你使用 angular 作為開發，那就是 FOLDER: dist 囉！</p>
<p>BUILD_SCRIPT: cp .env.example .env.local &amp;&amp; yarn &amp;&amp; yarn build</p>
<p>除了 yarn 安裝以及 yarn build 之外，如果你有使用 <a target="_blank" href="https://github.com/motdotla/dotenv">dotenv</a>，</p>
<p>該指令可以複製 .env.example 成 .env.local，並設定 env 參數。</p>
<p>如果要使用，請記得先在前幾行設定好變數值，例如 REACT_APP_TITLE：</p>
<p>[ main.yml ]</p>
<pre><code class="lang-makefile"><span class="hljs-section">env:</span>
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
        BRANCH: gh-pages
        FOLDER: build
        REACT_APP_TITLE: MY APP
        BUILD_SCRIPT: cp .env.example .env.local &amp;&amp; yarn &amp;&amp; yarn build
</code></pre>
<p>[ .env.example ]</p>
<pre><code class="lang-makefile">REACT_APP_TITLE=
</code></pre>
<p>[ public / index.html ]</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    ...
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>%REACT_APP_TITLE%<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    ...
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

  ...

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>如果沒使用，則可以直接把指令設定成：</p>
<p>BUILD_SCRIPT: yarn &amp;&amp; yarn build</p>
<h2 id="heading-5bu656ul5luk54mm">建立令牌</h2>
<p>首先到個人 github 上的 <a target="_blank" href="https://github.com/settings/profile">Settings</a>。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750317_91042.png" alt="1567750317_91042.png" /></p>
<p>點選 <a target="_blank" href="https://github.com/settings/apps">Developer settings</a>。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750391_28159.png" alt="1567750391_28159.png" /></p>
<p>然後選擇 <a target="_blank" href="https://github.com/settings/tokens">Personal access tokens</a>。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750451_37442.png" alt="1567750451_37442.png" /></p>
<p>接著點選 <a target="_blank" href="https://github.com/settings/tokens/new">Generate new token</a> 建立私人令牌。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567750566_24903.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750566_24903.png" alt="1567750566_24903.png" /></a></p>
<p>在欄位中，Note 是輸入可以識別的名稱，日後可以改，</p>
<p>權限則勾選最上面區塊 repo 就好，會自動勾取其子項目，最後確定產生。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567750843_39012.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750843_39012.png" alt="1567750843_39012.png" /></a><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567750843_41935.png" alt="1567750843_41935.png" /></p>
<p>產生後，請確保成功複製 token，因為之後無法再進行複製的動作，必須要重新產生。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567751127_34335.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567751127_34335.png" alt="1567751127_34335.png" /></a></p>
<p>接著到你 github 的專案 Settings。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567751263_57526.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567751263_57526.png" alt="1567751263_57526.png" /></a></p>
<p>選擇 Secrets。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567751402_9566.png" alt="1567751402_9566.png" /></p>
<p>點選 Add a new secret。</p>
<p>Name 請輸入：ACCESS_TOKEN</p>
<p>Value 請貼上剛剛複製的 token。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567751403_0206.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567751403_0206.png" alt="1567751403_0206.png" /></a></p>
<p>如果成功，應該會看到 綠色 鎖頭。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567751627_40074.png" alt="1567751627_40074.png" /></p>
<p>484 OS： 耖尼瑪的版主顏色亂搞我！嘿嘿！</p>
<h2 id="heading-6kit5a6a54mi5pys5o6n5yi2">設定版本控制</h2>
<p>接著終於可以將你的專案 push 上去了！請根據你的專案設定哦～</p>
<pre><code class="lang-bash">git remote add origin https://github.com/explooosion/my-app.git
</code></pre>
<pre><code class="lang-bash">git add -A
</code></pre>
<pre><code class="lang-bash">git commit -m <span class="hljs-string">"Initial commit"</span>
</code></pre>
<p>你可以直接使用 vscode 內建的 git gui 操作。</p>
<h2 id="heading-6amx6k2j57wq5p6c">驗證結果</h2>
<p>推送完畢後，你可以到 github 專案 Actions 中查看，是不是順利通過。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567753280_99129.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567753280_99129.png" alt="1567753280_99129.png" /></a></p>
<p>如果失敗，預設會發送電子郵件給你。</p>
<p>試著切換分支，可以發現多了 gh-pages。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567753331_312.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567753331_312.png" alt="1567753331_312.png" /></a></p>
<p>在 Settings 中，也可以發現 Github Pages，自動選擇 gh-pages 分支，</p>
<p>並且可以看到提示訊息的網址。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/ec9f149d-a499-4399-9640-01ddb7401ba9/1567753449_47213.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567753449_47213.png" alt="1567753449_47213.png" /></a></p>
<p>因為筆者有設定過 <a target="_blank" href="https://help.github.com/en/articles/quick-start-setting-up-a-custom-domain">Custom domain</a>，所以網址會不太一樣！</p>
<p>點開網址，就可以看到所發佈的網站囉！</p>
<p>並且如果有設定環境變數，title 也順利被套用上去。</p>
<p><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2019-09-06_Github%20-%20CICD%20%E4%BD%BF%E7%94%A8%20Actions%20%E4%BB%A5%20React%20%E5%BB%BA%E7%BD%AE%20pages%20%E7%82%BA%E4%BE%8B/1567753737_38252.png" alt="1567753737_38252.png" /></p>
<p>恭喜！</p>
<h2 id="heading-5yd6icd5pah56ug">參考文章</h2>
<ul>
<li><a target="_blank" href="https://dev.to/pierresaid/deploy-node-projects-to-github-pages-with-github-actions-4jco">Deploy your projects to Github Pages with GitHub Actions</a></li>
<li><a target="_blank" href="https://github.com/marketplace/actions/deploy-to-github-pages">Deploy to GitHub Pages</a></li>
<li><a target="_blank" href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/">GitHub Actions now supports CI/CD, free for public repositories</a></li>
</ul>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item><item><title><![CDATA[Rust - 關閉警語 嗡嗡嗡 Keep It Silence]]></title><description><![CDATA[如果你也被 rust 的警語感到不耐煩 ...
如果你在撰寫 Rust 的時候，遇到了嗡嗡嗡 ...

本篇不超過一百字，只為了解決一個問題。
— 警語 Warning。
一、作法
有時候我們只是為了 debug 或其他用途而聲明 use ，
但總是卻被警語 warning warning warning ...
本筆記提供兩個常用的關閉方法。
針對匯入 import 的警語 ：
#[allow(unused_imports)]

針對變數 variable 的警語：
#[allow(unuse...]]></description><link>https://blog.robby570.tw/rust-keep-it-silence</link><guid isPermaLink="true">https://blog.robby570.tw/rust-keep-it-silence</guid><category><![CDATA[Rust]]></category><dc:creator><![CDATA[Robby Wu]]></dc:creator><pubDate>Sun, 25 Nov 2018 00:00:00 GMT</pubDate><enclosure url="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2018-11-25_Rust%20-%20%E9%97%9C%E9%96%89%E8%AD%A6%E8%AA%9E%20%E5%97%A1%E5%97%A1%E5%97%A1%20Keep%20It%20Silence/banner/1543131514_31819.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>如果你也被 rust 的警語感到不耐煩 ...</p>
<p>如果你在撰寫 Rust 的時候，遇到了嗡嗡嗡 ...</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/a22b1a90-dfde-4438-9ee2-23d5abd7596f/1543131514_31819.png"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2018-11-25_Rust%20-%20%E9%97%9C%E9%96%89%E8%AD%A6%E8%AA%9E%20%E5%97%A1%E5%97%A1%E5%97%A1%20Keep%20It%20Silence/1543131514_31819.png" alt="1543131514_31819.png" /></a></p>
<p>本篇不超過一百字，只為了解決一個問題。</p>
<h3 id="heading-warning">— 警語 Warning。</h3>
<h2 id="heading-5lia44cb5l2c5rov">一、作法</h2>
<p>有時候我們只是為了 debug 或其他用途而聲明 <strong>use</strong> ，</p>
<p>但總是卻被警語 warning warning warning ...</p>
<p>本筆記提供兩個常用的關閉方法。</p>
<h4 id="heading-import">針對匯入 import 的警語 ：</h4>
<pre><code class="lang-bash"><span class="hljs-comment">#[allow(unused_imports)]</span>
</code></pre>
<h4 id="heading-variable">針對變數 variable 的警語：</h4>
<pre><code class="lang-bash"><span class="hljs-comment">#[allow(unused_variables)]</span>
</code></pre>
<p>更多方法可參考：</p>
<ul>
<li><a target="_blank" href="https://doc.rust-lang.org/1.1.0/rustc_lint/lint/builtin/index.html">Module rustc_lint::lint::builtin</a></li>
<li><a target="_blank" href="https://doc.rust-lang.org">doc.rust-lang.org</a></li>
<li><a target="_blank" href="https://github.com/rust-lang/rfcs/issues/1710">Option to suppress unused variable warnings on unimplemented!() functions.</a></li>
</ul>
<h4 id="heading-warning-1">如果你想一次把「<strong>所有」</strong>警語 warning 關閉，你可以嘗試這招：</h4>
<pre><code class="lang-bash"><span class="hljs-comment">#![allow(warnings)]</span>
</code></pre>
<p>我想應該就會很安靜了 ...</p>
<p>如果久了，你怕安靜了 ...</p>
<p>：「我想我真的怕安靜　少了你吵我不開心」</p>
<h2 id="heading-kirlmzmqkg"><strong>噓</strong></h2>
<p>建議不爽過後，還是要把警語開啟哦～</p>
<h2 id="heading-w5cuioe1koiqng">×. 結語</h2>
<p>嗡嗡嗡。</p>
<p><a target="_blank" href="https://dotblogsfile.blob.core.windows.net/user/incredible/cc6102ac-4231-43e1-857e-94d5ab0fa44e/1543133147_43545.jpg"><img src="https://raw.githubusercontent.com/explooosion/blogs/refs/heads/main/docs/images/2018-11-25_Rust%20-%20%E9%97%9C%E9%96%89%E8%AD%A6%E8%AA%9E%20%E5%97%A1%E5%97%A1%E5%97%A1%20Keep%20It%20Silence/1543133147_43545.jpg" alt="1543133147_43545.jpg" /></a></p>
<p>有勘誤之處，不吝指教。ob'_'ov</p>
]]></content:encoded></item></channel></rss>