<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by PJCHENder on Medium]]></title>
        <description><![CDATA[Stories by PJCHENder on Medium]]></description>
        <link>https://medium.com/@PJCHENder?source=rss-11c8b4a64460------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*kYCtmdjjlmoPmUpUlQhUnA.png</url>
            <title>Stories by PJCHENder on Medium</title>
            <link>https://medium.com/@PJCHENder?source=rss-11c8b4a64460------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 00:50:57 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@PJCHENder/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[後 Redux 時代！？留意那些過去常被視為理所當然的事]]></title>
            <link>https://medium.com/onedegree-tech-blog/%E5%BE%8C-redux-%E6%99%82%E4%BB%A3-%E7%95%99%E6%84%8F%E9%82%A3%E4%BA%9B%E9%81%8E%E5%8E%BB%E5%B8%B8%E8%A2%AB%E8%A6%96%E7%82%BA%E7%90%86%E6%89%80%E7%95%B6%E7%84%B6%E7%9A%84%E4%BA%8B-debe8ffc7140?source=rss-11c8b4a64460------2</link>
            <guid isPermaLink="false">https://medium.com/p/debe8ffc7140</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[swr]]></category>
            <category><![CDATA[state-management]]></category>
            <dc:creator><![CDATA[PJCHENder]]></dc:creator>
            <pubDate>Thu, 18 Nov 2021 15:10:55 GMT</pubDate>
            <atom:updated>2021-12-13T15:18:24.667Z</atom:updated>
            <content:encoded><![CDATA[<h3>後 Redux 時代：我該放棄 Redux 嗎？留意那些過去常被視為理所當然的事</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Y4gi8RP814XgDq9g.jpg" /><figcaption>圖片來源：<a href="https://chriscourses.com/blog/redux">https://chriscourses.com/blog/redux</a></figcaption></figure><p>說個題外話先，這次在看 Redux 的 Logo 時，突然發現怎麼和台南女中的校徽這麼像（誤）</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/220/0*7sVClbm5mgwkdAdh.png" /></figure><p>咳咳…回到正題。</p><p>過去多數人在開發大型 React 專案時，Redux 幾乎是不可獲缺的選項，但當 React 支援 context 和 custom hooks 後，許多人開始反思專案中整合 Redux 的必要性 — — 太多重複的程式碼、零碎的變數和檔案、較高的學習曲線等等，進而使得許多開發者開始提倡用 useContext + useReducer 來取代 redux。</p><p>在這篇文章中，並不會給予要不要整合 Redux 到專案中的建議，但會從自身經驗中分享那些過去使用 Redux 開發時，覺得理所當然，卻在移除 Redux 後才意識到的部分，並提出替代方案，最後再由讀者自行評估是否從專案中移除或導入 Redux。</p><p>在開始這篇文章前想要先問問讀者：</p><ul><li>如果有一個中大型的專案，你會想要使用 Redux 嗎？</li><li>如果不會的話，是什麼原因讓你不想要用 Redux？如果會的話，又是什麼原因？</li><li>如果要替換掉 Redux 的話，你第一個會想到的是什麼工具？</li></ul><blockquote>本篇文章可以搭配 <a href="https://you-should-know-without-redux.netlify.app/react">you-should-know-without-redux</a> 的實作閱讀。</blockquote><h3>Redux 的主要任務<a href="https://pjchender.dev/webdev/note-without-redux/#redux-%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BB%BB%E5%8B%99">​</a></h3><p>Redux 一開始是個單純的「狀態管理工具」，解決了 React 中需要透過 prop drilling 的方式來將狀態傳來傳去，並透過 reducer 避免開發者直接 mutate 資料，以減少可能的錯誤並且能追蹤每次資料的變化。</p><p>Redux 本身並不支援非同步的資料處理，但因為在前端的資料很難不透過 API 從後端取得，為了解決非同步的問題，有了 redux-thunk、redux saga 和 redux observable 這些用來處理非同步資料請求的工具。在這些工具中，實際上 redux-thunk 不太能作為「非同步資料請求」的工具，它的性質還是比較適合拿來處理簡單基本的「非同步」操作時使用，這點會在稍後做說明。</p><p>總結來說，過去 Redux 的使用扮演了兩個重要的任務：</p><ul><li>資料狀態管理：包含「全域」和「模組內」的資料傳遞，避免惱人的 prop drilling</li><li>非同步資料請求：透過 API 取得資料後，整合到資料狀態中</li></ul><p>現在就讓我們來看一下，拿掉了 Redux 之後，在這兩個主要任務上，有什麼需要留意的地方。</p><h3>資料狀態管理：常被忽略的問題<a href="https://pjchender.dev/webdev/note-without-redux/#%E8%B3%87%E6%96%99%E7%8B%80%E6%85%8B%E7%AE%A1%E7%90%86%E5%B8%B8%E8%A2%AB%E5%BF%BD%E7%95%A5%E7%9A%84%E5%95%8F%E9%A1%8C">​</a></h3><h4>避免過多的 Prop Drilling</h4><p>Redux 的其中一個好處是可以避免過度的 Prop Drilling，透過 selector 可以在任何元件取出所需要的資料。</p><pre>App<br>--- Todos (todos)<br>------ TodoList (todos)<br>--------- TodoItem (todo)<br>------------ ToggleTodo (todo.id)</pre><blockquote>可以參考這張<a href="https://christiankohler.net/static/e23cc4a6c42df7ff0e07afdc3080ace5/4cac5/PropDrilling.webp">示意圖</a> @ <a href="https://christiankohler.net/how-to-avoid-prop-drilling-in-angular">How to avoid Prop-drilling in Angular</a></blockquote><h4>避免不必要的 re-render</h4><p>假設現在有一個調色盤的模組 &lt;App /&gt;，裡面可以調整不同顏色的比重，每個顏色的調整各是一個獨立的元件，上方 &lt;Display /&gt; 的地方則會顯示最終的顏色：</p><ul><li>每個顏色當前的值會顯示在方框中</li><li>方框右上角會顯示重新 render 的次數</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*N8nChhHRuVGUoyIE.png" /></figure><p><a href="https://pjchender.dev/webdev/note-without-redux/#%E9%81%BF%E5%85%8D%E4%B8%8D%E5%BF%85%E8%A6%81%E7%9A%84-rerender">​</a><a href="https://pjchender.dev/webdev/note-without-redux/#%E9%81%BF%E5%85%8D%E9%81%8E%E5%A4%9A%E7%9A%84-prop-drilling">​</a>它的資料狀態會像是這樣：</p><pre><em>const</em> colorState = {<br>  red: 0,<br>  blue: 0,<br>  green: 0,<br>};</pre><p>這是用 Redux 寫起來的效果（<a href="https://you-should-know-without-redux.netlify.app/redux">Redux Demo</a>），每個調色的元件間各自獨立，調整紅色時並不會導致藍色或綠色重新轉譯：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*miw55Jp5ydVZZe7b.gif" /><figcaption>調整一個顏色時，不會導致其他顏色的元件重新 render</figcaption></figure><p>看起來非常自然，沒什麼特別的對吧！？</p><p>但這裡我們要留意到，colorState 是一個物件，也就是說即使只是想要改變其中一個顏色的值，實際上它會是一個全新的物件。</p><p>也就是說，在只使用 React 且不做優化的情況下（例如，使用 memo），因為資料都是來自父層的物件，因此只要調整任何一個顏色，都會導致另外兩個顏色的調色盤也重新轉譯（<a href="https://you-should-know-without-redux.netlify.app/react">React Demo</a>）：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kAL4Ft-0xiXvZqPX.gif" /><figcaption>調整一個顏色時，會導致其他顏色的元件重新 render</figcaption></figure><p>也就是說，過去在使用 Redux 時，我們可能不自覺的解決了這個避免重複轉譯（rerender）的問題。這裡看起來可能微不足道，但當資料量大或是每個 component 的資料在呈現時需要重新運算時，這樣的影響就非常巨大。</p><h3>資料狀態管理：可能的解決方案<a href="https://pjchender.dev/webdev/note-without-redux/#%E8%B3%87%E6%96%99%E7%8B%80%E6%85%8B%E7%AE%A1%E7%90%86%E5%8F%AF%E8%83%BD%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88">​</a></h3><p>看到了上面的範例後，可以想想那是什麼原因，在資料來源同樣是物件的情況下，Redux 可以減少不必要的 rerender 呢？</p><p>這其實有賴於 redux 提供的 <a href="https://redux.js.org/usage/deriving-data-selectors">useSelector</a>：</p><pre><em>import</em> { useSelector } <em>from</em> &#39;react-redux&#39;;</pre><pre><em>// 只有該對應的 color 改變時，該 component 才會 rerender</em><br><em>const</em> count = useSelector((state) =&gt; state.counter[color]);</pre><p>現在讓我們想想，在拿掉了 Redux 作為狀態管理的工具後，能夠用什麼作為替代方案呢？</p><h4>useContext + useReducer</h4><p>許多開發者鼓勵用 useContext 和 useReducer 來取代 Redux，如果是針對 Props Drilling 這點，透過 Context API 的確可以獲得解決良好的解決。</p><p>然而如果是要避免不必要的 rerender 這點，因為 Context 本身並沒有 selector 的機制，除非使用額外類似 <a href="https://github.com/dai-shi/use-context-selector">use-context-selector</a> 這種工具，否則一樣無法解決重複 render 的問題（<a href="https://you-should-know-without-redux.netlify.app/context">Context Demo</a>）。</p><p>另外，為了避免不必要的重新轉譯，使用 context 時不能把資料放在同一個物件中，例如：</p><pre><em>// 不建議這麼做</em><br><em>const</em> contextState = {<br>  applicationNumber: &#39;A0000000012&#39;,<br>  insuredSubject: {},<br>  product: {},<br>  quotation: {},<br>};<a href="https://pjchender.dev/webdev/note-without-redux/#usecontext--usereducer">​</a></pre><p>這麼做會使得當 contextState 中有任何資料改變時，所有有用到這個 contextState 的 component 都會 rerender。比較好的方式是先將這些資料進行分類，然後建立多個不同 context：</p><pre><em>const</em> applicationContextState = {};<br><em>const</em> insuredSubjectContextState = {};<br><em>const</em> productContextState = {};<br><em>const</em> quotationContextState = {};</pre><p>但這麼做就會需要包很多層的 Provider。可能會在最外層的 &lt;App /&gt; 先包了許多不同的 Provider，在特定模組中又包一層該模組所需的 Provider：</p><pre><em>// module 內共用的資料狀態</em><br><em>const</em> ApplicationModule = () =&gt; {<br>  <em>return</em> (<br>    &lt;ApplicationProvider&gt;<br>      &lt;InsuredSubjectProvider&gt;<br>        &lt;ProductProvider&gt;<br>          &lt;QuotationProvider&gt;<br>            &lt;YourApp /&gt;<br>          &lt;/QuotationProvider&gt;<br>        &lt;/ProductProvider&gt;<br>      &lt;/InsuredSubjectProvider&gt;<br>    &lt;/ApplicationProvider&gt;<br>  );<br>};</pre><pre><em>// global 共用的資料狀態</em><br><em>const</em> App = () =&gt; {<br>  <em>return</em> (<br>    &lt;AuthenticationProvider&gt;<br>      &lt;ApplicationModule /&gt;<br>    &lt;/AuthenticationProvider&gt;<br>  );<br>};</pre><p>實在不如使用 redux 時只包一個 store 來的乾淨俐落：</p><pre><em>import</em> { Provider } <em>from</em> &#39;react-redux&#39;;</pre><pre><em>const</em> App = () =&gt; {<br>  <em>return</em> (<br>    &lt;Provider store={store}&gt;<br>      &lt;YourApp /&gt;<br>    &lt;/Provider&gt;<br>  );<br>};</pre><p>此外，除了上述提到需要包許多層 Provider 的問題之外，使用 Context 時，有可能一不小心就回到 redux 被許多人詬病的 boilerplate 的問題。</p><p>目前許多開發者使用的 useContext + useReducer 的 pattern 會像這樣：</p><pre><em>import</em> { createContext, useContext, useReducer } <em>from</em> &#39;react&#39;;</pre><pre><em>const</em> initialState = {<br>  red: 0,<br>  blue: 0,<br>  green: 0,<br>};</pre><pre><em>export</em> <em>const</em> CountContext = createContext(initialState);</pre><pre><em>export</em> <em>const</em> countReducer = (state, action) =&gt; {<br>  <em>const</em> { color, value } = action.payload;<br>  <em>switch</em> (action.type) {<br>    <em>case</em> &#39;increment&#39;: {<br>      <em>return</em> {<br>        ...state,<br>        [color]: state[color] + 1,<br>      };<br>    }<br>    <em>case</em> &#39;decrement&#39;: {<br>      <em>return</em> {<br>        ...state,<br>        [color]: state[color] - 1,<br>      };<br>    }<br>    <em>case</em> &#39;update&#39;: {<br>      <em>return</em> {<br>        ...state,<br>        [color]: value,<br>      };<br>    }<br>    <em>default</em>:<br>      <em>return</em> state;<br>  }<br>};</pre><pre><em>const</em> CountProvider = ({ children }) =&gt; {<br>  <em>const</em> [state, dispatch] = useReducer(countReducer, initialState);<br>  <em>const</em> value = { state, dispatch };<br>  <em>return</em> &lt;CountContext.Provider value={value}&gt;{children}&lt;/CountContext.Provider&gt;;<br>};</pre><pre><em>const</em> useCount = () =&gt; {<br>  <em>const</em> ctx = useContext(CountContext);<br>  <em>if</em> (ctx === <em>undefined</em>) {<br>    <em>throw</em> <em>new</em> Error(&#39;useCount must be used with a CountProvider&#39;);<br>  }</pre><pre>  <em>return</em> ctx;<br>};</pre><pre><em>export</em> { CountProvider, useCount };</pre><p>在父層 component 可以簡單的使用 Provider 包起來：</p><pre><em>import</em> { CountProvider } <em>from</em> &#39;./features/colorPaletteContext/counterContext&#39;;</pre><pre><em>const</em> App = () =&gt; (<br>  &lt;CountProvider&gt;<br>    &lt;ColorPaletteContext /&gt;<br>  &lt;/CountProvider&gt;<br>);</pre><p>而在內部需要使用到 Context 資料的地方則可以簡單使用 useCount 這個 hook 來取得和改變 Context 中資料：</p><pre><em>import</em> { useCount } <em>from</em> &#39;./counterContext&#39;;</pre><pre><em>const</em> ColorPalette = () =&gt; {<br>  <em>const</em> { state, dispatch } = useCount();<br>  <em>// ...</em><br>};</pre><p>然而，值得思考的是，這麼做是否真的解決 redux 中大量 boilerplate 的問題。</p><h4>Recoil</h4><p><a href="https://recoiljs.org/docs/introduction/motivation">recoil</a> 是目前 facebook 實驗性的一個專案，它是專門為 react 所做的 state management，官方是這麼寫的：</p><blockquote><em>A state management library for React</em></blockquote><p>也因為是 Facebook 自己的產品，它最大化支援 concurrent mode 等其他新的 feature，有興趣的話，讀者可以看 <a href="https://recoiljs.org/docs/introduction/motivation">recoil/motivation</a> 的部分。</p><p>個人認為它解決了上述不論是 Redux 或 React Context 的問題。</p><p>和 Redux 一樣，它只需在最外層掛一個 Provider：</p><pre><em>import</em> { RecoilRoot } <em>from</em> &#39;recoil&#39;;</pre><pre><em>const</em> App = () =&gt; {<br>  <em>return</em> (<br>    &lt;RecoilRoot&gt;<br>      &lt;YourApp /&gt;<br>    &lt;/RecoilRoot&gt;<br>  );<br>};<a href="https://pjchender.dev/webdev/note-without-redux/#recoil">​</a></pre><p>它的基本用法非常簡單，<strong>就好像直接使用 React 原生的 </strong><strong>useState 一樣</strong>：</p><pre><em>import</em> { useRecoilState } <em>from</em> &#39;recoil&#39;;</pre><pre><em>const</em> counterState = atom({<br>  key: &#39;counterState&#39;,<br>  <em>default</em>: initialState,<br>});</pre><pre><em>// In the React component</em><br><em>const</em> [count, setCount] = useRecoilState(counterState);</pre><p>更重要的是，它也支援 selector 的作法，也就是說，它可以只取出需要的資料，只有這個資料改變時，這個 component 才會 rerender：</p><pre><em>import</em> { useRecoilValue } <em>from</em> &#39;recoil&#39;;</pre><pre><em>// 只取出特定顏色的資料來用</em><br><em>const</em> count = useRecoilValue(colorCounterSelector(color));</pre><p>如果回到最開始的例子，也就是說，切換其中一個顏色時，並不會導致另外兩個調色盤的元件重新轉譯（<a href="https://you-should-know-without-redux.netlify.app/recoil">Recoil Demo</a>）。</p><p>Recoil 除了能用來作為資料狀態管理之外，因為也支援 concurrent mode，所以內建就整合了非同步的 API 請求，但這個部分因為相對前衛，目前用起來的問題稍多，會在後面再做說明。但如果單純作為一個狀態管理工具的話，我認為是非常方便好用的。</p><h3>非同步資料請求：常被忽略的問題<a href="https://pjchender.dev/webdev/note-without-redux/#%E9%9D%9E%E5%90%8C%E6%AD%A5%E8%B3%87%E6%96%99%E8%AB%8B%E6%B1%82%E5%B8%B8%E8%A2%AB%E5%BF%BD%E7%95%A5%E7%9A%84%E5%95%8F%E9%A1%8C">​</a></h3><p>在 Web Fetch API 推出之後，大幅降低了使用 AJAX 呼叫 API 的複雜度，許多開發者開始不再依賴第三方套件而是直接使用原生的 Fetch API 來呼叫 AJAX 請求。</p><p>同樣的，過去在 React 中如果要呼叫 API 並針對 API 回傳的資料進行狀態管理，常會依賴像是 redux-thunk、redux-saga、redux-observable 這類的工具，也因為 Redux 本身沒辦法處理非同步的資料處理，所以搭配這些工具常常覺得再自然不過。</p><p>然而隨著 react hooks 和 fetch API 的普及，要在 React 的 component 中呼叫 API，並直接使用 API 回傳的資料到 component 中使用變得非常容易，我們幾乎可以在到處看到這樣的範例寫法：</p><pre><em>function</em> UserProfile({ userId }: Props) {<br>  <em>const</em> [user, setUser] = useState&lt;IUser | <em>undefined</em>&gt;();</pre><pre>  useEffect(() =&gt; {<br>    <em>const</em> fetchData = <em>async</em> () =&gt; {<br>      <em>const</em> data = <em>await</em> fetchUsers(&#39;/users&#39;, userId);<br>      setUser(data);<br>    };<br>    fetchData();<br>  }, [userId]);</pre><pre>  <em>return</em> {<br>    <em>/* ... */</em><br>  };<br>}</pre><p>看起來很簡便，也不太需要依靠第三方套件或處理非同步請求的狀態管理工具，但實際上，當我們自己在 component 中發送 API 請求時，有一些需要特別留意的咩咩角角，許多是被我們視為理所當然，但實際上是第三方工具幫我們處理好的。</p><h4>dedupe：避免重複打相同的 API</h4><p>dedupe 是 deduplication 的縮寫，也就是不要重複的意思，這點在處理 API 請求時是非常重要的一點。</p><p>以下圖為例，假設使用者正在檢視個人資料頁，這裡有兩個 component，一個是上方的 Navbar 需要知道使用者的登入狀態和基本資料、另一個是下方的 UserProfile，用來呈現該使用者的資料。</p><p>這時候 Navbar 和 UserProfile 這兩個 component 都需要打同一隻 API。如果我們是直接把 fetch User API 的方法各自寫在 Navbar 和 UserProfile 時，會發生什麼事呢？<a href="https://pjchender.dev/webdev/note-without-redux/#dedupe%E9%81%BF%E5%85%8D%E9%87%8D%E8%A4%87%E6%89%93%E7%9B%B8%E5%90%8C%E7%9A%84-api">​</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*LiB69U7kLlYdq8Tz.png" /></figure><p>如同預期的，Navbar 和 UserProfile 這兩個 component 會各自打各自的 API，也就是說，同樣的 API 會在使用者進來這頁時被重複呼叫兩次，並且得到同樣的資料。雖然感覺只是多打一次好像還好，但換成倍數的話，光是這頁 BE 就需要沒有意義的多承受兩倍的負擔。</p><p>以下圖為例，如果我們在同一個頁面在不同元件呼叫相同的 API 時，API 請求會重複的被發出（<a href="https://you-should-know-without-redux.netlify.app/user-without-dedupe">no dedupe DEMO</a>）：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eSTkFDdZG9DgMyBg.png" /></figure><h4>Race Condition：避免因非同步顯示了錯誤的資料內容</h4><p>這個問題會發生在資料回來的時間點不同時，例如使用者一開始請求 User1 的資料，後來發現要看的是 User2，這時候同時發出了 User1 和 User2 的 API 請求，理想上我們會期望 User1 的資料先回來後，User2 的資料才會回來，但千萬要記得「<strong>非同步的世界是很危險滴！</strong>」，實際上 API 卻先回了 User2 的資料，這時候會發生什麼事呢？</p><figure><img alt="圖片來源：learn recoil" src="https://cdn-images-1.medium.com/max/1024/0*vNdOe_nqKcY121YT.png" /><figcaption>圖片來源：<a href="https://course.learnrecoil.com/">learn recoil</a></figcaption></figure><p>以實際的例子來看，若在短時間內快速發出多個 User 資料的請求（分別是 UserId 1 ～ 4），如果 UserId 2 的資料比 UserId 4 還慢回來時，畫面上會顯示 UserId 幾的資料呢？</p><p>我們會期望畫面上顯示的應該要是 UserId 4 的使用者資料，但實際上從結果我們可以看到，畫面上最終顯示的卻是 UserId 為 2 的使用者，而不是 UserId 4 的使用者（<a href="https://you-should-know-without-redux.netlify.app/user-with-race-condition">race condition DEMO</a>），之所以如此，是<strong>因為我們有個「錯誤的」假設是「先發出請求的資料會先回來」</strong>：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*R8-OLdFZpzz9Cvfl.gif" /></figure><p>之所以會這樣，是因為 UserId 2 的資料比 UserId 4 的資料還晚回了最後才回來，最後一個資料回來後，會觸發 React 重新轉譯，進而使得畫面上顯示的是 UserId 2 的使用者。</p><h3>非同步資料請求：可能的解決方案<a href="https://pjchender.dev/webdev/note-without-redux/#%E9%9D%9E%E5%90%8C%E6%AD%A5%E8%B3%87%E6%96%99%E8%AB%8B%E6%B1%82%E5%8F%AF%E8%83%BD%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88">​</a></h3><h4>Redux Sage 或 Redux Observable<a href="https://pjchender.dev/webdev/note-without-redux/#redux-sage-%E6%88%96-redux-observable">​</a></h4><p>上述這些情況如果是使用 Redux 搭配 Redux Saga 或 Redux Observable，都有機會在 middleware 被處理掉。</p><p>但要留意的是，<strong>如果你只是用 redux 中 RTK（redux toolkit）內建的 redux-thunk，那就和自己在 component 發送 API 請求一樣，對於上述情況是完全沒有幫助的</strong>。</p><h4>Recoil<a href="https://pjchender.dev/webdev/note-without-redux/#recoil-1">​</a></h4><p>前面有提到，Recoil 處理能作為狀態管理之外，也能用來當作非同步資料處理的工具，同時它也會處理掉上面提到的 race condition 的問題。由於它已經最大化整合了 React concurrent mode，所以預設就會使用 &lt;Suspense /&gt; 和 &lt;ErrorBoundary /&gt; 這些功能。</p><p>這樣前衛的功能是它的特色卻同時也是它的缺點，由於 concurrent mode 在目前 React 17 還是實驗性質的，進而連帶導致 recoil 中有許多的 API 也掛著 UNSTABLE_ 的前綴。再加上主流的 SSR 方案（Next.js）也還不支援 Suspense 這類 concurrent mode 的使用（同樣是實驗性質，需手動啟用），進而使得用 recoil 仍不適合作為非同步資料請求的主要工具，但若是單純作為全域或模組內的狀態管理則沒什麼問題。</p><p>未來 React 18 的 concurrent mode 穩定後，加上主流 SSR 方案跟上後，非常期待 recoil 在非同步的資料請求上能有更突破的進展。</p><h4>SWR、React Query、RTK Query<a href="https://pjchender.dev/webdev/note-without-redux/#swrreact-queryrtk-query">​</a></h4><p>這裡我們以 <a href="https://swr.vercel.app/">SWR</a> 為例來說明，但這些工具都有對上述的問題進行處理：</p><pre><em>import</em> useSWR <em>from</em> &#39;swr&#39;;<br><em>import</em> { fetchUsers } <em>from</em> &#39;./user&#39;;</pre><pre><em>const</em> { data, error } = useSWR([&#39;/users&#39;, id], fetchUsers);</pre><p>SWR 在呼叫 API 請求時，實際上這些請求的內容會先經過「中央」，所以 SWR 的 dedupe 機制就會避免在短時間內，對同樣的 API endpoint 呼叫多次帶有內容相同的請求，接著把該次請求得到的結果保存在「中央」，讓所有需要使用此資料的元件都可以取用的到，以此來避免了重複發生相同 API 的問題（<a href="https://you-should-know-without-redux.netlify.app/user-with-dedupe">dedupe DEMO</a>）。</p><p>可以看到使用了 SWR 之後，同樣 endpoint 和內容的 request 不會被重複計算發出：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B71qRcp4mb6OwSN98SefJA.gif" /><figcaption>透過 SWR 解決了 dedupe 的問題</figcaption></figure><p>SWR 同樣會協助處理 race condition 的問題，確保畫面上顯示的資料是使用者當前所選擇的，而不會因為 API 回傳的時間問題而導致畫面上呈現錯誤的資料（<a href="https://you-should-know-without-redux.netlify.app/user-with-race-condition">race condition DEMO</a>）。</p><p>可以看到使用了 SWR 之後，左側的 UI 不會因為 UserId 2 的資料比較晚回來而顯示錯誤的資料：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4BFUA61lA4njym6ELdRGmw.gif" /></figure><p>SWR 是透過 cache 的方式，根據畫面上當前要顯示的資料的 key 從 cache 中把資料取出，以此避免 API 資料回應時間不同的問題。舉例來說，當畫面上的 userId 是 4 時，就只會從 cache 中去拿 key = 4 的資料，其他的還沒回來的請求就不理會了：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*DTcWljRxyykurNTtvuQ_aQ.png" /></figure><p>讀者如果有興趣的話，可以另外參考 Kyle 的<a href="https://medium.com/onedegree-tech-blog/%E4%BA%86%E8%A7%A3-swr-%E7%9A%84%E9%81%8B%E4%BD%9C%E6%A9%9F%E5%88%B6-how-these-async-state-managers-work-6236fc4f9f6">了解 SWR 的運作機制，How this async state manager works ?</a> @ OneDegree Tech Blog。</p><h3>總結<a href="https://pjchender.dev/webdev/note-without-redux/#%E7%B8%BD%E7%B5%90">​</a></h3><p>過去似乎理所當然的使用了 React + Redux 的組合，有些沒料想到的問題，在 Redux 默默幫開發者處理掉後也就忽略了，直到拿掉了 Redux 之後，那些視為理所當然的使用情境才發現要開始尋找替代方案或更好的作法。</p><p>不論最終你的專案決定要不要整合 Redux，這些問題都是 Web App 會碰到的，認識和了解這些問題更能夠幫助開發者找出不同的解決方案，而非盲目的把東西拆掉後以為解決了問題，卻衍生更多問題。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=debe8ffc7140" width="1" height="1" alt=""><hr><p><a href="https://medium.com/onedegree-tech-blog/%E5%BE%8C-redux-%E6%99%82%E4%BB%A3-%E7%95%99%E6%84%8F%E9%82%A3%E4%BA%9B%E9%81%8E%E5%8E%BB%E5%B8%B8%E8%A2%AB%E8%A6%96%E7%82%BA%E7%90%86%E6%89%80%E7%95%B6%E7%84%B6%E7%9A%84%E4%BA%8B-debe8ffc7140">後 Redux 時代！？留意那些過去常被視為理所當然的事</a> was originally published in <a href="https://medium.com/onedegree-tech-blog">OneDegree Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[建立公司內部使用的 eslint-config package]]></title>
            <link>https://medium.com/onedegree-tech-blog/%E5%BB%BA%E7%AB%8B%E5%85%AC%E5%8F%B8%E5%85%A7%E9%83%A8%E4%BD%BF%E7%94%A8%E7%9A%84-eslint-config-package-4b76c089848?source=rss-11c8b4a64460------2</link>
            <guid isPermaLink="false">https://medium.com/p/4b76c089848</guid>
            <category><![CDATA[eslint-config]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[eslint]]></category>
            <dc:creator><![CDATA[PJCHENder]]></dc:creator>
            <pubDate>Tue, 17 Aug 2021 08:22:01 GMT</pubDate>
            <atom:updated>2021-08-20T01:22:30.726Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wg2KVuuJVoL1M2O0GqgFGw.jpeg" /></figure><p>在現今的 JavaScript 專案中，為了確保程式碼的品質和撰寫風格，ESLint 的設定和使用幾乎可以算是標配。</p><p>透過 ESLint 除了可以避免掉許多不必要的程式語法錯誤，還能確保開發者彼此之間有一致的程式撰寫風格（coding style），才不會 A 開發者寫出來的程式碼和 B 開發者寫出來的，在撰寫風格上有太大的落差。</p><h3>為什麼要建立 OneDegree 自己的 eslint-config package</h3><p>eslint-config package 指的就是把 ESLint 設定檔，打包成一個 npm 套件，這裡面包含了要套用那些規則、套用這些規則的邏輯等等，目的則是讓不同專案能夠直接套用。</p><p>幾個大家可能聽過的像是 eslint-config-standard、eslint-config-airbnb、eslint-config-prettier、eslint-config-google、…等等，都是包好的 ESLint 設定檔，讓想要套用的開發者可以直接下載使用。</p><p>一般來說，每個專案都會有獨立的一隻 ESLint 設定檔，可以針對不同專案進行設定就好，不需要額外抽成一個套件。然而，在 OneDegree 中有許多不同的專案同時在進行，包含 B2B 和多個 B2C 專案，如果每次都要在各個專案中複製貼上相同的設定，會是相當麻煩的一件事；另外，如果在專案中有針對部分規則進行個別的微調，久了之後可能會忘了原始套用的設定是長什麼樣子，甚至發生不同的專案套用的設定差異過大的情況。</p><p>這時候如果可以把 ESLint 中的設定打包成一個套件，未來新開專案時只需要使用 npm 安裝這個套件後，就可以套用到公司內部一致的設定，將會省下非常多不必要的麻煩。</p><h3>認識 ESLint 中的幾個設定欄位</h3><p>實際上要建立 eslint-config 的套件並沒有太大的難度，因為本質上就是把寫好的 ESLint 設定進行匯出而已。比較需要釐清的反而是先了解和釐清 ESLint 設定檔中的幾個欄位。下面是一隻 ESLint 設定檔常見的欄位：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/877c646a7d64ea2021eceac97bb045ee/href">https://medium.com/media/877c646a7d64ea2021eceac97bb045ee/href</a></iframe><p>其中包含了：</p><ul><li>env：讓 ESLint 知道那些是原本就存在專案中的全域變數（global variable），例如， alert、 window 等等，否則當你在專案中直接使用了這些全域變數卻又沒有定義的話，ESLint 就會認為這個變數不存在而報錯。</li><li>parserOptions ：告訴 ESLint 專案中所使用的 JavaScript 語法版本（例如，ECMAScript6、ECMAScript7）。</li><li>rules ：透過這個欄位的設定，可以讓 ESLint 知道當不同的規則觸發時，ESLint 要用什麼類型的方式給予提示，是要當成是 error、warning、或是不用理會。例如，在這裏可以設定，假如開發者在程式中定義了變數卻沒有使用到這個變數時，要當成是嚴重的錯誤（error），出現警告的提示（warning）就好，或是可以完全不用理會這個情況（off）。</li></ul><p>除了這幾個欄位之外，比較容易混淆的則是 plugins 和 extends 這兩個欄位，因此會花多一些的篇幅來描述。</p><h4>plugins</h4><p>plugins 是一系列由其他開發者撰寫好的規則，讓使用的人可以把這些規則載入到 ESLint 中使用，但要留意的是：「 <strong>它只是定義好的規則，並沒有說明要如何使用這些規則</strong>」，也就是說，plugin 只會載入規則，但不會說明這些規則發生時到底要被 ESLint 判斷成是 error、warning、還是可以不用理會，這個部分一樣是要在 rules 欄位中設定。</p><p>常見 eslint plugin 的套件像是 eslint-plugin-react、eslint-plugin-import、eslint-plugin-vue、eslint-plugin-prettier、eslint-plugin-jest、…等等。</p><p>以 <a href="https://github.com/yannickcr/eslint-plugin-react/tree/master/lib/rules">eslint-plugin-react</a> 為例，可以看到這裡定義好了一系列可以被 ESLint 使用的規則：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3HxDEcLTpA7FP1Nd" /></figure><p>在這些 plugin 中都有定義了許多不同的規則可以載入到 ESLint 中，再由開發者自行針對這些規則，透過 rules 欄位來決定嚴重程度。</p><p>從這些 eslint plugin 的套件中可以看到，每個套件的前綴都是一樣的，都是以 eslint-plugin-* 當作開頭，因此當我們在 ESLint 的設定檔中，想要啟用這個 plugin 的規則時，可以省略掉這個前綴。</p><p>例如，在上面範例的 .eslintrc.js 中，可以看到 plugins 欄位只寫了 react，而實際上它完整的套件名稱是 eslint-plugin-react ，eslint-plugin-* 的部分可以省略：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6366b1f7879e47fc27d8c8272a9990c3/href">https://medium.com/media/6366b1f7879e47fc27d8c8272a9990c3/href</a></iframe><blockquote><em>當我們掛入 plugins 時，其實就只是把許多定義好的規則載入到 ESLint 中，但這些規則要怎麼被使用並不包括在 plugins 的範疇內，需要透過 rules 欄位，設定每個規則要如何被使用後才算真的有效。</em></blockquote><h4>extends</h4><p>最後來說明 extends 這個欄位，extends 的功能其實很單純，就是把其他開發者寫好的 eslint-config（ESLint 設定檔）給載入進來。</p><p>舉例來說，上面範例的 .eslintrc.js 就是一個 ESLint 的設定檔，如果其他開發者想要使用同樣的設定，除了用複製貼上的方式，把同樣的設定貼到自己的 ESLint 設定檔之外，也可以透過用 extends 這個欄位，直接套用別人寫好的設定，ESLint 就會把對應的設定也都載入到你專案的 ESlint 設定檔中。</p><p>也就是說，我們可以先建立了公司內部要共用的 ESLint 設定檔後，在不同專案的 extends 欄位都去載入這個共用的 ESLint 設定檔後，這些設定以及建立好的規則判斷（哪些規則要顯示為嚴重、哪些是警告、哪些可以不用理會）就會自動套用到專案當中，如此就可以避免需要重複複製貼上的問題。</p><p>常見的 ESLint 設定檔像是文章開頭提到的 eslint-config-standard、eslint-config-airbnb、eslint-config-prettier、eslint-config-google，同樣的，你會發現它們也都有一樣的前綴，都是以 eslint-config-* 開頭，因此在使用時可以省略掉 eslint-config- 的前綴，像是這樣：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d94b004e4e047092d0501d271409f842/href">https://medium.com/media/d94b004e4e047092d0501d271409f842/href</a></iframe><p>這裡 extends 了 airbnb，其實指的就是載入 <a href="https://github.com/airbnb/javascript/tree/master/packages">eslint-config-airbnb</a> 的 ESLint 設定檔。</p><p>在這裏我們還看到另外也載入了一個名為 plugin:react/recommend 的 ESLint 設定檔，之所以前面是以 plugin:react 開頭，是因為這隻設定檔實際上是放在 eslint-plugin-react 中（注意，是 eslint-plugin-react而不是 eslint-config-react 喔！）。</p><p>為什麼要把 React 的 ESLint 設定檔放在 eslint-plugin-react 中，而不是獨立成 eslint-config-react 呢？</p><p>這呼應到前面有提到的，plugins 本質上只是用來定義一系列的規則，但這些規則怎麼被使用並不在 plugin 的範疇內。可是，既然都把規則定義好了，何不乾脆幫大家把怎麼使用這些規則的建議設定也一併提供在 plugin 的專案中，方便大家下載 plugin 後就可以直接使用呢？這也就是為什麼在 eslint-plugin-react 中，還有 <a href="https://github.com/yannickcr/eslint-plugin-react/blob/master/index.js#L119">recommended</a> 這個 eslint-config 可以使用。</p><blockquote>eslint-plugin-react 本身是 ESLint 的 plugin，但套件裡同時提供了 plugin:react/recommended 這個 ESLint 設定檔（eslint-config）讓開發者使用。</blockquote><p>當你在 extends 欄位中載入了 plugin:react/recommended 後，就會連帶的把使用這些規則的建議設定也載入到你的專案當中：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JP0dll08-F80yU8I" /></figure><p>extends 和 plugin 的作用不同，但很容易搞混的原因也在這裡。</p><p>簡單來說，plugin 定義的是一系列可以被使用的規則，雖然不是必須，但它經常也會順便提供許多建議可以怎麼使用這些規則的設定，讓開發者可以直接透過 extends 來套用。像是大家常聽到的 prettier 也是透過這樣的方式，它先定義好一系列的規則後放在 eslint-plugin-prettier 中，而在這個 plugin 的套件裡也順便提供了建議的 ESLint 設定讓開發者能夠套用，所以你可能經常會看到這樣的 ESLint 設定檔：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/acc2a163513bb867d762b08571ee566a/href">https://medium.com/media/acc2a163513bb867d762b08571ee566a/href</a></iframe><p>都是類似的道理。</p><blockquote><em>plugins 提供許多的規則，讓開發者可以自行設定要如何使用這些規則，而 plugin 的作者也可能一併提供了他認為合理的設定檔讓開發者可以放在 </em><em>extends 中直接使用，但這並不是強制的。</em></blockquote><p>有些時候你可能會看到下面這種只有 extends 卻沒有載入 plugins 的情況而感到困惑：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/497b3786170293c40d7fe2fb6bfde508/href">https://medium.com/media/497b3786170293c40d7fe2fb6bfde508/href</a></iframe><p>之所以可以這樣寫，是因為通常在這些 extends 的 ESLint 設定檔中，都已經幫你把使用到的 plugin 給定義好，所以你只要確保有安裝了這些 plugin，即使沒有在專案的 ESLint 設定檔中明確透過 plugins 定義這些 plugin，它們還是會被載入。</p><p>例如，你可以在 eslint-plugin-react 的 <a href="https://github.com/yannickcr/eslint-plugin-react/blob/master/index.js#L121">recommended config</a> 中看到，它已經幫你把 plugins: [&#39;react&#39;] 寫好了：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/0*LAnzz3oR0owdE3o8" /></figure><p>因此一旦你有使用 extends: [&#39;plugin:react/recommended&#39;] 載入這個設定檔，它就已經幫你在 plugins 的地方載入 eslint-plugin-react。</p><h3>實作 OneDegree 內部的 ESLint config</h3><p>在了解 ESLint 中 plugin、extends 和 rules 的概念後，就可以知道，我們只需要先建立好一隻可以被共用的 ESLint 設定檔，在這裡面定義好各專案都希望遵循的規則及套用規則的邏輯後，接著只要在各個專案中，各自透過 extends 的方式，載入這個共用的設定檔，就可以達到預期的效果。</p><p>以程式碼的概念會像是這樣子。</p><p>先建立一支準備被共用的 ESLint 設定檔，例如這裡取名叫 base.eslint.config.js ：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bf3f4e4af464a3957a2b3d84807f914b/href">https://medium.com/media/bf3f4e4af464a3957a2b3d84807f914b/href</a></iframe><p>接著在專案本身的 ESLint 設定檔中，透過 extends 的方式去讀取到這支共用的設定檔就可以了。</p><p>假設這支共用的設定檔和專案自身的 ESLint 設定檔放在相同的路徑時，可以直接這樣載入：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/887461405ab35b15b97979d156af0152/href">https://medium.com/media/887461405ab35b15b97979d156af0152/href</a></iframe><p>如此這個專案就會載入並套用這支共用的設定。</p><p>如果要確定專案有沒有實際吃到 extends 的內容，可以透過下列指令，複製專案中 ESLint 最終實際會套用到的設定來瞧瞧：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58c035807e214d25d3252b34281d8011/href">https://medium.com/media/58c035807e214d25d3252b34281d8011/href</a></iframe><p>可以看到，雖然專案本身的 ESLint config 只有短短一行 extends: [&#39;./base.eslint.config&#39;] ，但透過上述的指令，可以看到實際套用的設定非常多：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EcFqjVPISJTCwSsS" /></figure><p>除了可以看到有套用了幾個 plugins 之外，我們寫在 base.eslint.config.js 中的設定（例如： no-console 的規則邏輯）也有出現在最終實際會被 ESLint 使用到的設定中。</p><h4>根據需要建立不同類型的設定檔</h4><p>由於共用的設定檔本身也只是 JavaScript，因此不需要把所有的設定都放在同一支 base.eslint.config.js 中，而是可以透過 JavaScript 模組的方式來進行管理。</p><p>例如，可以有一支 base 的設定檔：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3fade203ab611f6594c943f0f91d7bad/href">https://medium.com/media/3fade203ab611f6594c943f0f91d7bad/href</a></iframe><p>接著在建立一隻專門給 React 專案使用的，在這支 react.eslint.config.js 中，會先去 extends base 的設定，把剛剛寫的設定檔載入後，再加上針對 React 專案要套用的規則：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cd77d6b0b8b473207c5ca73e32e061aa/href">https://medium.com/media/cd77d6b0b8b473207c5ca73e32e061aa/href</a></iframe><p>針對 TypeScript 的專案，可以同樣 extends base 的設定後，針對 TypeScript 的部分進行設定：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a4e93e36d97d34693a592a044ac50b61/href">https://medium.com/media/a4e93e36d97d34693a592a044ac50b61/href</a></iframe><p>如此，未來如果新開的是 react 的專案，該專案可以直接 extends react.eslint.config.js；如果是 TypeScript 的專案，則可以 extends typescript.eslint.config.js 即可。</p><h4>包成 NPM 套件</h4><p>實際上，我們不只是會建立共用的 ESLint 設定檔，還會把這個共用的 eslint 設定檔發佈到 npm 上，就像是前面提到的 eslint-config-standard 或 eslint-config-airbnb 一樣，讓想要套用這個設定檔的專案只需要透過 npm 就可以安裝對應的設定檔，並且透過 extends 的方式來載入。</p><p>以 OneDegree 為例，我們把共用的 eslint 設定檔包成一個名為 eslint-config-onedegree 的套件，未來開新專案的時候，只需透過 npm install eslint-config-onedegree 下載設定檔後，接著在專案本身的 ESLint 設定檔中去 extends 它就可以了：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c414562085b5b7b5712b622519a5dafd/href">https://medium.com/media/c414562085b5b7b5712b622519a5dafd/href</a></iframe><p>如果是針對特定類型的專案一樣可以像這樣進行 extends：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c8827404a68706b0edb123f6f1f8198a/href">https://medium.com/media/c8827404a68706b0edb123f6f1f8198a/href</a></iframe><p>或是針對 TypeScript 的專案：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d930e2f787ba29e2262d49461ecfcc03/href">https://medium.com/media/d930e2f787ba29e2262d49461ecfcc03/href</a></iframe><blockquote><em>註：在 OneDegree 中，公司內部有自己的 npm registry，因此佈署時，是發佈到公司內部 host 的 npm registry。</em></blockquote><h4>規則使用的邏輯是可以被覆蓋的</h4><p>另外因為 extends 的概念是載入另一個設定檔進來，如果有需要根據專案客製化調整的話，還是可以在專案本身的 eslint 設定檔透過 rules 進行覆蓋和調整。</p><p>舉例來說，雖然在 eslint-config-onedegree 預設針對 no-console 這個規則是警告（warning），但根據專案個別的需要，還是可以覆蓋和調整：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ead38d08f2e4cb58fc7bce38fd8a3ce/href">https://medium.com/media/1ead38d08f2e4cb58fc7bce38fd8a3ce/href</a></iframe><p>一樣透過先前提供的指令，複製最終執行的 ESLint 設定檔後，可以看到調整後最終出來的設定會是這樣：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4HIPTRyLEm04Opdk" /></figure><h3>OneDegree 內部使用的一些規則設定</h3><p>最後來分享一些 OneDegree 內部使用的 ESLint 設定。</p><p>首先，因為 OneDegree 中有部分專案是從 JavaScript 導入成 TypeScript 的，因此針對 TS 的檔案我們是使用 ESLint 提供的 overrides 欄位來進行規則覆蓋，也就是 TS 的設定只會套用在以 .ts 或 .tsx 為副檔名的檔案，而不會套用到 JS 的檔案，如此確保在專案中 JS 和 TS 的檔案可以共存：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/55df52fb4c34242648c983c9e8736a96/href">https://medium.com/media/55df52fb4c34242648c983c9e8736a96/href</a></iframe><p>另外針對 TypeScript 的 enum 型別，會限制只能用大寫字母搭配底線來進行定義：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/db67b1bcbed21041d0e5529901781bd7/href">https://medium.com/media/db67b1bcbed21041d0e5529901781bd7/href</a></iframe><p>以及內部一些開發上的慣例，也會建在 ESLint 中。像是針對型別沒有一定要在檔案中的上方先定義後，才能在下方被使用：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/875ff2bc4b99fc616de47a77c420ab36/href">https://medium.com/media/875ff2bc4b99fc616de47a77c420ab36/href</a></iframe><p>針對 TypeScript 的 React 元件，因為已經有透過 TypeScript 進行 props 的定義，就可以把原本的 react/prop-types 的規則給關掉：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e815eca0fe5df1c8bf6921c396432460/href">https://medium.com/media/e815eca0fe5df1c8bf6921c396432460/href</a></iframe><p>另外，為了避免在 React 專案中，有人使用 named import，有人使用 default import，導致程式碼中會同時出現這兩種不同的 pattern：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/df90803cd88eae48b60d08f569216b35/href">https://medium.com/media/df90803cd88eae48b60d08f569216b35/href</a></iframe><p>一樣可以透過 ESLint 的設定來達到一致的撰寫規範：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/55bd1b83a8879c4e403bce5d891f57dd/href">https://medium.com/media/55bd1b83a8879c4e403bce5d891f57dd/href</a></iframe><h3>總結</h3><p>透過把公司內部共用的 ESLint 設定檔包成 npm 套件，後續新開的專案都可以直接透過 ESLint 的 extends 欄位來共用設定，就可以有效解決新專案每次都要先複製重複的 ESLint 設定，也可以避免各專案間彼此 ESLint 設定差異過大的麻煩。此外，如果公司有什麼新規則希望可以套用到各個專案中時，也只需要去修改這個在 npm 上共用的 eslint-config package 就可以了，而不需要每個專案都去改動。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4b76c089848" width="1" height="1" alt=""><hr><p><a href="https://medium.com/onedegree-tech-blog/%E5%BB%BA%E7%AB%8B%E5%85%AC%E5%8F%B8%E5%85%A7%E9%83%A8%E4%BD%BF%E7%94%A8%E7%9A%84-eslint-config-package-4b76c089848">建立公司內部使用的 eslint-config package</a> was originally published in <a href="https://medium.com/onedegree-tech-blog">OneDegree Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[為什麼暴露商品的缺點卻賣得更好？]]></title>
            <link>https://medium.com/@PJCHENder/%E7%82%BA%E4%BB%80%E9%BA%BC%E6%9A%B4%E9%9C%B2%E5%95%86%E5%93%81%E7%9A%84%E7%BC%BA%E9%BB%9E%E5%8D%BB%E8%B3%A3%E5%BE%97%E6%9B%B4%E5%A5%BD-bbf55ac3d7e7?source=rss-11c8b4a64460------2</link>
            <guid isPermaLink="false">https://medium.com/p/bbf55ac3d7e7</guid>
            <category><![CDATA[psychology]]></category>
            <category><![CDATA[reciprocity]]></category>
            <category><![CDATA[marketing]]></category>
            <dc:creator><![CDATA[PJCHENder]]></dc:creator>
            <pubDate>Fri, 27 May 2016 08:38:59 GMT</pubDate>
            <atom:updated>2016-05-27T08:38:59.619Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bOb9DNVqsUd6VQEO4h7sqw.png" /></figure><p>我們都知道人不是完全理性的，我們的大腦裡內建了很多快速啟動的捷徑，這些捷徑的主要目的都是幫助我們能夠又快又正確的做出決策判斷，就像看到紅燈我們就會不加思索的停下車來一樣，我們不會先去思考紅燈會不會其實故障了，因為這樣太耗費我們的精力和時間。</p><p>這種接受到某個刺激就快速做出反應的內建捷徑，大部分都是對我們很有幫助的！然而，許多的有心人士，卻也會利用這樣的捷徑來讓我們做出令自己也感到出乎意料的行為。</p><p>這次我們就來談談互惠原理吧！</p><h3>什麼是互惠原理</h3><p><strong>互惠原理（reciprocity principle）簡單來說就是當我們收到別人的好處時，就有回報對方好處的衝動</strong>。就積極面來說，就是禮尚往來，雙方透過給予彼此好處，建立起越來越緊密的關係，社會聯繫的建立可不能沒有它；但就消極面來說，就是「人情債」，當我們接受別人的幫忙後，我們因為不想要積欠對方任何的人情債，於是我們也會做出相對應的補償。</p><p>互惠原理的例子幾乎到處可見，像是LINE常常推出加好友送貼圖，多數人或許是下載完貼圖後馬上封鎖對方帳號，但我也有些朋友覺得既然有下載對方的貼圖，就應該偶爾接受一下對方發送的訊息！</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/332/0*O8M5Gq55zGoGUnop.png" /><figcaption>圖片來源：<a href="http://www.appledaily.com.tw/realtimenews/article/new/20150414/592152/">蘋果日報</a></figcaption></figure><p>另外，量販店或老街攤販也常常會有試吃的活動，試吃除了真的要讓你品嚐外，更重要的是引發你的人情債壓力。你可能會想，有這麼嚴重嗎？我都吃完就走了，但其實很多人會在吃了以後，感受到強烈的”人情債”，好像吃了人家東西就應該買一點什麼才可以離開。</p><p>你覺得好像沒這麼嚴重，你都可以吃完就走人，但如果店員在你吃的當下跟你閒聊兩句，或者吃的時候只有你一個人，你和店員四目相接，這時更不得了，原本好像可以吃吃就走掉，但因為你和店員有了接觸，有了”人情”，即便你最後沒買，你可能還是會有一絲不好意思的感覺！</p><h3>為什麼他更容易收到別人捐的發票？</h3><p>談到互惠原理，最經典的應用就是<strong>先提出一個一定會被拒絕的大請求，接著再提出自己實際的小請求</strong>，也就是所謂的<strong>互惠式讓步</strong>。讓我來分享一個親身經驗吧！</p><p>記得在某一天早上，我坐在火車站旁的麥當勞吃著早餐，開放式的麥當勞有時會有一些阿婆阿公手上提著一籃商品兜售，像是口香糖阿、毛巾阿、筆阿等等的，因為先前已經碰過太多這類阿公阿嬤，早知道他們販售的商品是我所不需要的，可是又不想要忍受自己好像很沒愛心的感覺，所以在她還沒使出拜託招式時，大部分我都會馬上直接拒絕。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/0*siVfh0BZnZmh3mRl.jpg" /><figcaption>圖片來源：<a href="http://maxwelltest.dareno.org/public_html/archives/portfolio/genesis-social-welfare-foundation">創世基金會</a></figcaption></figure><p>這時候一個阿婆帶著商品朝著我走過來，心理已經下意識的決定要拒絕她，以下是發生的過程：</p><p>阿婆：「少年耶，你可以幫我買個口香糖嗎？」</p><p>我：「對不起，我不需要。」</p><p>阿婆：「（指著桌上的發票），那你可以給我你的發票嗎？」</p><p>我：「喔…喔，好阿！」</p><p>在我還沒意會到實際上發生了什麼事時，我已經把發票給了她。然後，我看著她走到另一桌的客人，再次上演了拒絕買商品，成功獲取發票的模式。</p><p>突然，我意識到，這不就是互惠原理中，先提出大請求，等到被拒絕後，在提出小請求，而這個小請求往往才是真正的目的。因為當對方從大請求降到小請求的時候，你會覺得對方對你施予恩惠做出了讓步，於是我們很本能的，會想要回饋對方。</p><p>吃麥當勞時，大部分的人都會把發票放在桌上，等吃完餐點在把發票收起來，對於要發票來說，這真的是個絕佳的場所阿！</p><p>— -</p><p>互惠式讓步最經典的例子是，當你要跟別人借錢時，如果你實際上想要借1000元，你要先問：「我最近手頭比較緊，不知道你方不方便借我$5000（如果對方就同意，你就賺到了）」，但是通常對方會覺得價格太高而馬上拒絕，這時候你可以做出讓步，說：「那能不能借我$1000，先撐過這週就好」。這時候，成功借到錢的機會將大大增加，因為對方會感受到你釋出了善意，出於本能的，他會覺得自己也該釋出善意。</p><h3>為什麼暴露商品的缺點卻賣得更好？</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/0*g-tklaeG-dCazQUS.jpg" /><figcaption>圖片來源：<a href="http://scarbrough-intl.com/tag/drawback/">Scarbrough</a></figcaption></figure><p>在來談談另一個互惠原理的應用，記得我以前在手機店打工，當時因為白色的手機皮套缺貨，但有些客人一進來卻想要買白色的保護套，若是沒有經驗的店員大概做不成這一筆生意了。然而，當時的店員卻成功做成了一筆生意，他很貼心的告訴顧客：「白色雖然很好看，但是白色很容易髒，我們很多客人買了白色，回去之後沒多久，又來買另一個顏色，因為白色一下就髒了，所以我會建議你買灰色比較好，免得到時候清洗很不方便」。</p><p>這時厲害了，多數客人好像著了魔地忘了當初自己是想要來買白色皮套的，他們感受到的是這個店員很替客戶著想（但其實是因為白色缺貨），他願意講出商品實際的缺點，他一定是誠實可信的好店員，提醒我買白色回去會後悔（感到受惠），所以我還是聽從店員的建議買灰色比較好險（回報恩惠）。</p><p>同樣的例子也常常出現在手機推銷上，厲害店員不會重覆地一直告訴你這支手機有多好又多好，而是會故意暴露出這支手機一些無關緊要的小缺點，讓你感受到店員他是非常誠懇的，竟然連手機的缺點都願意講，這時，多數的客戶心裡會想：「他願意告訴我手機的缺點，他沒有為了賣商品而只說好話，他是一個可信的賣家」，於是，為了回報他的誠實以對，你也更願意向他購買商品。</p><h3>為什麼互惠原理這麼有效</h3><p>互惠原理之所以有效，第一個是因為我們很討厭對別人有所虧欠的感覺，當我們內心有這種虧欠感時，這種感覺壓在心中讓我們感到難以承受，它就像焦慮一樣讓我們感到不舒服，我們會想盡辦法希望能消除這種不適感，消除人情債的最快方法，就可回報對方人情。</p><p>另一個原因在於，社會對於「忘恩負義」的人貼上了很負面的標籤。當被貼上忘恩負義的標籤後，沒有人會想接近你，沒有人會想對你好，為了避免被貼上這樣的標籤，我們內化了「得了別人的恩情，一定要給予回報」這樣的信念。</p><p>互惠原理更厲害的一點在於，有些時候，你好像不能拒絕別人給你的恩惠，因為這樣好像顯得自己不近人情，特別是在上屬給予恩惠時，不接受時還要擔心別人覺得你沒禮貌！然而，當你一旦接受了對方施予的恩惠，即使你沒有做出具體的回報，你的心理可能也做出了相對應的回應（例如，認定他真的是個善解人意的好主管）。</p><p>和我們先前說明的其他心理捷徑一樣，不是所有使用這些方法的人，都是奸邪狡詐的壞人，唯有用這些方法欺騙我們，碰壞人與人之間信任的，才是我們應該加以抨擊和避免繼續往來的。</p><blockquote>原文網址：<a href="https://pjchender.blogspot.tw/2016/05/blog-post_20.html">https://pjchender.blogspot.tw/2016/05/blog-post_20.html</a></blockquote><blockquote>頁首圖片：<a href="http://mspdesigngroup.com/marketing/">http://mspdesigngroup.com/marketing/</a></blockquote><blockquote>參考資料：<a href="http://www.books.com.tw/products/0010495733">影響力：讓人乖乖聽話的說服術</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bbf55ac3d7e7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>