-
Notifications
You must be signed in to change notification settings - Fork 0
/
14827416854836.html
661 lines (441 loc) · 26 KB
/
14827416854836.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
[實習筆記] React & Redux 中前端怎麼跟 server 要資料? - My Frontend Note
</title>
<link href="atom.xml" rel="alternate" title="My Frontend Note" type="application/atom+xml">
<link rel="stylesheet" href="asset/css/foundation.min.css" />
<link rel="stylesheet" href="asset/css/docs.css" />
<script src="asset/js/vendor/modernizr.js"></script>
<script src="asset/js/vendor/jquery.js"></script>
<script src="asset/highlightjs/highlight.pack.js"></script>
<link href="asset/highlightjs/styles/github.css" media="screen, projection" rel="stylesheet" type="text/css">
<script>hljs.initHighlightingOnLoad();</script>
<script type="text/javascript">
function before_search(){
var searchVal = 'site:yucj.github.io/logMine ' + document.getElementById('search_input').value;
document.getElementById('search_q').value = searchVal;
return true;
}
</script>
</head>
<body class="antialiased hide-extras">
<div class="marketing off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<nav class="top-bar docs-bar hide-for-small" data-topbar>
<section class="top-bar-section">
<div class="row">
<div style="position: relative;width:100%;"><div style="position: absolute; width:100%;">
<ul id="main-menu" class="left">
<li id=""><a target="self" href="index.html">Home</a></li>
<li id=""><a target="_self" href="archives.html">Archives</a></li>
</ul>
<ul class="right" id="search-wrap">
<li>
<form target="_blank" onsubmit="return before_search();" action="http://google.com/search" method="get">
<input type="hidden" id="search_q" name="q" value="" />
<input tabindex="1" type="search" id="search_input" placeholder="Search"/>
</form>
</li>
</ul>
</div></div>
</div>
</section>
</nav>
<nav class="tab-bar show-for-small">
<a href="javascript:void(0)" class="left-off-canvas-toggle menu-icon">
<span> My Frontend Note</span>
</a>
</nav>
<aside class="left-off-canvas-menu">
<ul class="off-canvas-list">
<li><a href="index.html">HOME</a></li>
<li><a href="archives.html">Archives</a></li>
<li><a href="about.html">ABOUT</a></li>
<li><label>Categories</label></li>
<li><a href="git_note.html">Git 筆記</a></li>
<li><a href="javascript_note.html">JS 筆記</a></li>
<li><a href="ES2015.html">ES2015</a></li>
<li><a href="%E5%AF%A6%E7%BF%92%E7%AD%86%E8%A8%98.html">實習筆記</a></li>
<li><a href="Babel.html">Babel</a></li>
<li><a href="Best%20Practices.html">Best Practices</a></li>
<li><a href="Webpack.html">Webpack</a></li>
</ul>
</aside>
<a class="exit-off-canvas" href="#"></a>
<section id="main-content" role="main" class="scroll-container">
<script type="text/javascript">
$(function(){
$('#menu_item_index').addClass('is_active');
});
</script>
<div class="row">
<div class="large-8 medium-8 columns">
<div class="markdown-body article-wrap">
<div class="article">
<h1>[實習筆記] React & Redux 中前端怎麼跟 server 要資料?</h1>
<div class="read-more clearfix">
<span class="date">2016/12/26</span>
<span>posted in </span>
<span class="posted-in"><a href='%E5%AF%A6%E7%BF%92%E7%AD%86%E8%A8%98.html'>實習筆記</a></span>
<span class="comments">
</span>
</div>
</div><!-- article -->
<div class="article-content">
<p>這次做《報導者》網站的作者列表、作者資料頁、和作者搜尋的前端部分,練習到怎麼樣在 React + Redux 的架構中安排前端和後端的資料傳遞。寫個筆記記錄一下:</p>
<p><strong>本文目錄:</strong></p>
<ul>
<li>
<a href="#toc_0">一、Client 到 Database 之間的路徑</a>
</li>
<li>
<a href="#toc_1">二、怎麼在 React & Redux 載入外部資料</a>
<ul>
<li>
<a href="#toc_2">1. Promise object in ES2015 (ES6)</a>
</li>
<li>
<a href="#toc_3">2. 在 Redux 架構中處理引進外部資料的非同步請求:</a>
<ul>
<li>
<a href="#toc_4">What Is an Action Creator? What Do We Expect It to Do?</a>
</li>
<li>
<a href="#toc_5">What is a Container?</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>程式原始碼放在 GitHub 上:</strong></p>
<ul>
<li>[Update] Add components of authors list, author page, and sponsor #453
<a href="https://github.com/twreporter/twreporter-react/pull/453">https://github.com/twreporter/twreporter-react/pull/453</a></li>
</ul>
<span id="more"></span><!-- more -->
<h2 id="toc_0">一、Client 到 Database 之間的路徑</h2>
<p>從《報導者》網站的設計可以看到一般內容網站前後端資料間傳遞的架構安排,目前《報導者》網站儲存資料的地方有兩個,一個是在我們自己 server 的主要 database,另一個是這次作者相關頁面測試使用的 Algolia 服務(Algolia的資料是定時從我們的 database 上傳的):</p>
<p><strong>Client→API_1→(Redis cache)→API_2→DB 的情況:</strong></p>
<pre><code class="language-sequence">Client->API_1: 傳request
API_1->Redis: 檢查有沒有快取
note left of Redis: 如果未有快取
API_1->API_2: 叫資料
API_2->Database: 跟資料庫叫資料
Database->API_2: 回傳資料
API_2->API_1: 回傳資料
API_1->Redis: 儲存快取
API_1->Client: 回傳資料
</code></pre>
<p>各節點說明如下:</p>
<ul>
<li><p><strong>Client</strong>:使用者在瀏覽器操作觸發需要取資料的事件後(例如點擊「載入更多」按鈕)透過 Redux 架構中的 actions 發 <code>fetch</code>(<code>isomorphic-fetch</code>,取代用 <code>XMLHttpRequest</code> 或 <code>$.ajax</code>)。</p></li>
<li><p><strong>API_1</strong>:用 node.js 的 <code>Express</code> 寫成。測試路徑設定為<code>localhost:3030</code>,原始碼資料夾在 <code>~/api/</code>。</p></li>
<li><p><strong>Redis</strong>:Redis 是一個用 key-value pair 架構儲存資料的資料庫,我們架在自己的機器上用來作為快取減少 call API 的次數:<code>key</code> 是 request url,<code>value</code> 是 response 的 json 字串。</p></li>
<li><p><strong>API_2</strong>:用 Python 寫成的 rest-api server。測試路徑設定為<code>localhost:8080</code>,原始碼是在另一個 repo <code>tr-projects-rest</code>。</p></li>
<li><p><strong>Database</strong>:使用 MongoDB 資料庫,測試路徑設定為<code>localhost:27017</code>,<code>db_name</code> 是 <code>keystone</code>。</p></li>
</ul>
<p>這次開發作者列表和作者頁面時,我們嘗試把資料放在 Algolia 搜尋服務上(我同事幫忙寫了一個 node 的程式和 cron job 會定時把 mongo 的資料更新上 Algolia)。這樣前端叫資料的架構變成如下圖:</p>
<p><strong>Client→API_1→(Redis cache)→API_2→Algolia 的情況:</strong></p>
<pre><code class="language-sequence">Client->API_1: 傳request
API_1->Redis: 檢查有沒有快取
note left of Redis: 如果未有快取
API_1->Algolia_API: 傳搜尋 query
Algolia_API->Algolia_Index: 跟資料庫叫資料
Algolia_Index->Algolia_API: 回傳和排序過濾處理
Algolia_API->API_1: 回傳搜尋結果
API_1->Redis: 儲存快取
API_1->Client: 回傳資料
</code></pre>
<p><strong>程式怎麼判斷要跟哪邊要資料?</strong></p>
<p>報導者網站程式的設計是,呼叫 <code>API_1</code> 時,網址 <code>localhost:3030/actionName?xxx=ooo</code> 的 actionName 會有其相對應的 module,在不同的 module 裡面決定是要呼叫 <code>Algolia_API</code> 還是 <code>API_2</code> 要資料。</p>
<h2 id="toc_1">二、怎麼在 React & Redux 載入外部資料</h2>
<p>對 Javascript 新手如我來說,這部分主要是有兩個學習重點:</p>
<ul>
<li>熟悉使用 ES2016 的 <strong>Promise</strong> 作非同步的處理。</li>
<li>理解在 Redux 的架構下,怎麼引入 middleware 來抓取外部資料。</li>
</ul>
<h3 id="toc_2">1. Promise object in ES2015 (ES6)</h3>
<p>我 ES2015 的學習主要是上 Code School 裡面的 <code>ES2015: The Shape of JavaScript to Come</code>,還有閱讀以下兩個電子書:</p>
<ul>
<li> 阮一峰,《ECMAScript 6 入门》
<a href="http://es6.ruanyifeng.com/">http://es6.ruanyifeng.com/</a></li>
<li> Axel Rauschmayer, <em>Exploring ES6</em>
<a href="http://exploringjs.com/es6.html">http://exploringjs.com/es6.html</a></li>
</ul>
<p>(2017.01.08 新增:最近發現一個線上讀書會有整理好的影片和資源)</p>
<ul>
<li> ES6 入門讀書會
<a href="https://github.com/softnshare/es6">https://github.com/softnshare/es6</a></li>
</ul>
<p>從最基礎的來說,一個 Promise object 通常是透過 Promise 建構式生成的 instance:</p>
<pre><code class="language-js">var promise = new Promise(function(resolve, reject) {
// (a) ... some code:要執行的非同步作業
if (/* 非同步作業完成 */){ // (b) 判斷當完成時傳出結果
resolve(value);
} else { // (c) 判斷當失敗時傳出錯誤
reject(error);
}
})
</code></pre>
<blockquote>
<p>Promise 構造函數接受一個函數作為參數,該函數的兩個參數分別是 <code>resolve</code> 和 <code>reject</code> 。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。</p>
<p><code>resolve</code> 函數的作用是,將 Promise object 的狀態從「未完成」變為「成功」(即從 Pending 變為 Resolved ),在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去;<code>reject</code> 函數的作用是,將 Promise 對象的狀態從「未完成」變為「失敗」(即從 Pending 變為 Rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。</p>
<p>Promise 實例生成以後,可以用 then 方法分別指定 Resolved 狀態和 Reject 狀態的回調函數。</p>
<p><a href="http://es6.ruanyifeng.com/#docs/promise#%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95">http://es6.ruanyifeng.com/#docs/promise#基本用法</a></p>
</blockquote>
<p>簡單來說就是只能透過 <code>resolve()</code>、<code>reject()</code> 改變 Promise object 的狀態(pending / resolved / rejected),而不同的狀態透過 <code>then()</code> 和 <code>catch()</code> 等方法可以指定該狀態的 handling callback function。</p>
<p>現在許多進行非同步作業的函式都是 return Promise object ,例如 <code>isomorphic-fetch</code> 的 <code>fetch()</code> 或 Algolia javascript client 的 <code>index.search()</code>。所以呼叫這些函數時都可以接 <code>Promise.prototype.then()</code> 或 <code>Promise.prototype.catch()</code> 處理。</p>
<h3 id="toc_3">2. 在 Redux 架構中處理引進外部資料的非同步請求:</h3>
<p>在<a href="http://chentsulin.github.io/redux/docs/advanced/AsyncFlow.html"> Redux 的官方教學</a>中有提到:「不使用 middleware 的話,Redux 的 store 只支援同步資料流」。但我們一般引進外部資料,都要透過非同步的方式處理,以免整個程式在取得資料的一個指令上就卡住,所以就要使用 middleware 來處理。</p>
<blockquote>
<p>Asynchronous middleware like redux-thunk or redux-promise wraps the store's dispatch() method and allows you to dispatch something other than actions, for example, functions or Promises. </p>
<p><a href="http://redux.js.org/docs/advanced/AsyncFlow.html">http://redux.js.org/docs/advanced/AsyncFlow.html</a></p>
<p>Redux middleware... provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. </p>
<p><a href="http://redux.js.org/docs/advanced/Middleware.html">http://redux.js.org/docs/advanced/Middleware.html</a></p>
</blockquote>
<p>在《報導者》的網站裡,目前是有用 <code>redux-thunk</code> 還有自己寫的 一個 call api 轉址 middleware。原始碼可見 <code>~/src/store/configureStore.js</code>。一個簡單的範例示意圖如下:</p>
<p><strong>當使用者點擊載入更多按鈕,去 API 抓下一頁資料回來更新作者列表:</strong></p>
<pre><code class="language-sequence">note right of Container: User Click
Container->Store: trigger dispatch
note left of Action: ↓↓Middleware↓↓
Store->Action: dispatch(action)
Action->API: fetch
API->Action: response
note left of Action: ↑↑Middleware↑↑
Store->Reducer: pass previous state tree & action
Reducer->Store: return next state
Store->Container: trigger rerender
note right of Container: display data
</code></pre>
<p>除了官網連結的 <code>redux-thunk</code> 和 <code>redux-promise</code> 以外,在網路上有看到中國支付寶前端成員整理了處理 <code>store.dispatch(action)</code> 的 middlewares 的幾個選擇:</p>
<blockquote>
<p><strong>五、Action <> Store,业务逻辑处理</strong></p>
<p><strong>需求</strong><br/>
统一处理业务逻辑,尤其是异步的处理。</p>
<p><strong>我們選擇的方案</strong><br/>
<code>redux-saga</code>: 用于管理 action,处理异步逻辑。可测试、可 mock、声明式的指令。</p>
<p><strong>其他選項</strong><br/>
<code>redux-loop</code>: 适用于相对简单点的场景,可以组合异步和同步的 action 。但他有个问题是改写了 combineReducer,会导致一些意想不到的兼容问题,比如我在特定场景下用不了 redux-devtool 。<br/>
<code>redux-thunk</code>, <code>redux-promise</code> 等: 相对原始的异步方案,适用于更简单的场景。在 action 需要组合、取消等操作时,会不好处理。</p>
<p><a href="https://github.com/sorrycc/blog/issues/1">https://github.com/sorrycc/blog/issues/1</a></p>
</blockquote>
<p>在 React + Redux 架構怎麼處理資料請求的回傳資料整理和呈現這邊,我有遇到一個疑惑的問題點在於 React 的 container 和 Redux 的 actions 之間的分工怎麼切分會比較好。</p>
<p>原則上來說,container 是在 React 裡面,核心任務是處理 view,actions 是在 Redux 架構裡面處理 business logic。但這其實這裡的分工還會牽涉到我的 store/state 要長什麼樣子,api 如果不是給定的要怎麼設計、container 在 render 的時候要做哪些處理等等。</p>
<p>我們網站在這邊處理的資料量很小,邏輯也還算單純,所以在區分上我就沒有特別細究,跑得起來就好(?)。資料量更大或邏輯更複雜的情況,在效能和團隊維護上可能會有影響。</p>
<p>我們的網站大多數處理放在 actions 裡,如果只是內部使用就可以不需要 export,如果是很多地方都會用到的資料處理 function 可以寫成 <code>~/src/utils</code> 裡的 modules。</p>
<p>以下是之前摘要的官方教學裡面對 action creators 和 containers 的說明,也列出來供參考:</p>
<h4 id="toc_4">What Is an Action Creator? What Do We Expect It to Do?</h4>
<blockquote>
<p>Note that a reducer is a pure function. It only computes the next state. It should be completely predictable: calling it with the same inputs many times should produce the same outputs. It shouldn't perform any side effects like API calls or router transitions. <strong>These should happen before an action is dispatched</strong>.</p>
<p><a href="http://redux.js.org/docs/basics/DataFlow.html">http://redux.js.org/docs/basics/DataFlow.html</a></p>
</blockquote>
<p>平常 action creator 是回傳 <strong>action object</strong> 給 dispatch function 使用:</p>
<pre><code class="language-js">export function requestAuthors(url) {
return {
type: CONSTANTS.FETCH_AUTHORS_REQUEST,
url
}
}
</code></pre>
<p>當 action creator 是回傳一個 <strong>function</strong> 時,則稱為 <strong>thunk</strong> :</p>
<pre><code class="language-js">export function fetchAuthors(page=1) {
return (dispatch, getState) => { // eslint-disable-line no-unused-vars
let url = formatUrl('authors?page='+page)
dispatch(requestAuthors(url))
return fetch(url)
// => fetch('http://localhost:3030/authors?page=') or fetch('api/authors')
.then((response) => {
if (response.status >= 400) {
throw new InternalServerError('Bad response from API, response:' + JSON.stringify(response))
}
return response.json()
})
.then((response) => {
const camelizedJson = camelizeKeys(response)
let items = normalize(camelizedJson.items, arrayOf(authorSchema))
let meta = camelizedJson.meta
return dispatch(receiveAuthors(items, meta))
}, (error) => {
return dispatch(failToReceiveAuthors(error))
})
}
}
</code></pre>
<blockquote>
<p>by using this specific middleware, an action creator can return a function instead of an action object. This way, the action creator becomes a thunk.</p>
<p>When an action creator returns a function, that function will get executed by the Redux Thunk middleware. This function doesn't need to be pure; it is thus allowed to have side effects, including executing asynchronous API calls. The function can also dispatch actions—like those synchronous actions we defined earlier.</p>
<p>Thunk middleware knows how to handle functions. It passes the dispatch method as an argument to the function, thus making it able to dispatch actions itself.</p>
<p>The function called by the thunk middleware can return a value, that is passed on as the return value of the dispatch method.</p>
<p>In this case, we return a promise to wait for. This is not required by thunk middleware, but it is convenient for us.</p>
<p><a href="http://redux.js.org/docs/advanced/AsyncActions.html#async-action-creators">http://redux.js.org/docs/advanced/AsyncActions.html#async-action-creators</a></p>
</blockquote>
<h4 id="toc_5">What is a Container?</h4>
<blockquote>
<p><strong>Containers vs. Components</strong></p>
<table>
<thead>
<tr>
<th></th>
<th scope="col" style="text-align:left">Presentational Components</th>
<th scope="col" style="text-align:left">Container Components</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" style="text-align:right">用途</th>
<td>怎麼看事情(markup, styles)</td>
<td>怎麼做事情 (抓資料, 更新state)</td>
</tr>
<tr>
<th scope="row" style="text-align:right">意識到 Redux</th>
<td>否</th>
<td>是</th>
</tr>
<tr>
<th scope="row" style="text-align:right">取得資料</th>
<td>從 props 讀取資料</td>
<td>訂閱 Redux state</td>
</tr>
<tr>
<th scope="row" style="text-align:right">改變資料</th>
<td>從 props 呼叫 callback</td>
<td>Dispatch Redux action</td>
</tr>
<tr>
<th scope="row" style="text-align:right">從哪被寫入</th>
<td>經由手動</td>
<td>通常由 React Redux 產生</td>
</tr>
</tbody>
</table>
<p><a href="http://chentsulin.github.io/redux/docs/basics/UsageWithReact.html">http://chentsulin.github.io/redux/docs/basics/UsageWithReact.html</a></p>
</blockquote>
</div>
<div class="row">
<div class="large-6 columns">
<p class="text-left" style="padding:15px 0px;">
<a href="Improve-Performance-in-JavaScript.html"
title="Previous Post: Level 2: 寫出效率較好的 JavaScript(JavaScript Best Practice @ Code School)">« Level 2: 寫出效率較好的 JavaScript(JavaScript Best Practice @ Code School)</a>
</p>
</div>
<div class="large-6 columns">
<p class="text-right" style="padding:15px 0px;">
<a href="JavaScript-Syntax-about-Condition.html"
title="Next Post: Level 1: JavaScript 的好用條件語法(JavaScript Best Practice @ Code School)">Level 1: JavaScript 的好用條件語法(JavaScript Best Practice @ Code School) »</a>
</p>
</div>
</div>
<div class="comments-wrap">
<div class="share-comments">
<div id="disqus_thread"></div>
<script>
/**
* RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
* LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */
/*
var disqus_config = function () {
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = '//logmine.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>
</div>
</div><!-- article-wrap -->
</div><!-- large 8 -->
<div class="large-4 medium-4 columns">
<div class="hide-for-small">
<div id="sidebar" class="sidebar">
<div id="site-info" class="site-info">
<h1>My Frontend Note</h1>
<div class="site-des">我的前端筆記</div>
<div class="social">
<a target="_blank" class="rss" href="atom.xml" title="RSS">RSS</a>
</div>
<div style="{width:90%; height:auto; margin: 5px auto 5px auto;}"><img src="http://i.imgur.com/iFetvNZ.jpg?1"></div>
</div>
<div id="site-categories" class="side-item ">
<div class="side-header">
<h2>Categories</h2>
</div>
<div class="side-content">
<p class="cat-list">
<a href="git_note.html"><strong>Git 筆記</strong></a>
<a href="javascript_note.html"><strong>JS 筆記</strong></a>
<a href="ES2015.html"><strong>ES2015</strong></a>
<a href="%E5%AF%A6%E7%BF%92%E7%AD%86%E8%A8%98.html"><strong>實習筆記</strong></a>
<a href="Babel.html"><strong>Babel</strong></a>
<a href="Best%20Practices.html"><strong>Best Practices</strong></a>
<a href="Webpack.html"><strong>Webpack</strong></a>
</p>
</div>
</div>
<div id="site-categories" class="side-item">
<div class="side-header">
<h2>Recent Posts</h2>
</div>
<div class="side-content">
<ul class="posts-list">
<li class="post">
<a href="14988019613600.html">Webpack 2 & 3: Input(Entry) and Output</a>
</li>
<li class="post">
<a href="14987956627714.html">[NodeJS] Path, URL 路徑相關問題整理</a>
</li>
<li class="post">
<a href="14987306644790.html">Webpack plugins and loaders I used</a>
</li>
<li class="post">
<a href="14987255686752.html">Babel plugins and loaders I used</a>
</li>
<li class="post">
<a href="14971940142851.html">Webpack 2: deploy</a>
</li>
</ul>
</div>
</div>
</div><!-- sidebar -->
</div><!-- hide for small -->
</div><!-- large 4 -->
</div><!-- row -->
<div class="page-bottom clearfix">
<div class="row">
<p class="copyright">Copyright © 2015
Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,
Theme used <a target="_blank" href="http://github.com">GitHub CSS</a>.</p>
</div>
</div>
</section>
</div>
</div>
<script src="asset/js/foundation.min.js"></script>
<script>
$(document).foundation();
function fixSidebarHeight(){
var w1 = $('.markdown-body').height();
var w2 = $('#sidebar').height();
if (w1 > w2) { $('#sidebar').height(w1); };
}
$(function(){
fixSidebarHeight();
})
$(window).load(function(){
fixSidebarHeight();
});
</script>
<script src="asset/chart/all-min.js"></script><script type="text/javascript">$(function(){ var mwebii=0; var mwebChartEleId = 'mweb-chart-ele-'; $('pre>code').each(function(){ mwebii++; var eleiid = mwebChartEleId+mwebii; if($(this).hasClass('language-sequence')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = Diagram.parse($(this).text()); diagram.drawSVG(eleiid,{theme: 'simple'}); }else if($(this).hasClass('language-flow')){ var ele = $(this).addClass('nohighlight').parent(); $('<div id="'+eleiid+'"></div>').insertAfter(ele); ele.hide(); var diagram = flowchart.parse($(this).text()); diagram.drawSVG(eleiid); } });});</script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><script type="text/x-mathjax-config">MathJax.Hub.Config({TeX: { equationNumbers: { autoNumber: "AMS" } }});</script>
</body>
</html>