SOVMedCare Tech

@sovmedcare


  • 首頁

  • 關於

  • 作者群

  • 標籤

  • 分類

  • 歸檔

Webpack Migration 從 v1 到 v3

jackypan1989 發表於 2017-09-21

之前幾個專案都已經直接使用 webpack v2 以上, 但剛好這次手邊有一個專案還在使用 v1, 所以這邊就一起跟大家介紹如何無痛升級到最新版 (v3.6), 包括 loader / plugin 的一些改變,其實大部分都沒變,只有一些關鍵字跟配置調整。

更新方式

1
yarn add --dev babel-core babel-loader babel-preset-env webpack webpack-dev-server

版本差異

1. resolve 一律用 modules 來設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// v1
resolve: {
root: path.join(__dirname, "src"),
modulesDirectories: ...,
extensions: ...,
fallback: ...
}
// v3
resolve: {
modules: [
path.join(__dirname, 'src'),
'node_modules'
]
}

2. module.loaders 改成用 module.rules 以及 use 關鍵字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// v1
module: {
loaders: [
{
test: /\.css$/,
loader: "style!css"
}
]
}
// v3
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}

3. 利用 webpack-merge

如果你有 prod / dev / test / electron 各種 config
那我會建議使用 webpack-merge 把重要部份抽出來方便管理

4. 其他我覺得重要的改動

  • loader name 要寫完整,不能簡寫,除非還要另外使用 resolveLoader (舊選項)
  • 不用裝 json-loader / UglifyJsPlugin(免另外安裝) / OccurrenceOrderPlugin(直接內建) / DedupePlugin(已移除)
  • loader option 一律放在 rules 的每個 loader 下面
  • babel-preset 一律改用 env 不用使用 es201x

效率比較(未優化前)

  • v1.14.0

    build time: 39.517s
    size: 2.29MB

  • v3.6.0

    build time: 32.028s
    size: 2.54MB

Reference

  1. 實際範例
  2. webpack 官方

RxJs實現Redux功能

Tu-Szu-Chi 發表於 2017-09-21

Redux的出現是為了解決數據的管理方式,而RxJS是更進階地解決異步(Async)操作的困擾
關於上述兩者的文章已有相當多的資源可以參考,在此分享一個很不錯的演講,是由Netflix的資深工程師-Jay Phelps,對RxJs應用在React以及之於Redux的關係有很好的解釋(redux-observable)

開始

原本的React & Redux,我們會dispatch(action)去改變state,在Observable來看可以這樣表示:

state·····state··state-··········state·····>

當action被觸發都會回傳一個新的state,並且React會依照這新的state來決定是否re-render
在React中,Store即state儲存的地方,state又是由諸多的child-state(Component)組成,Component底下又會有自己的actions,action是經由reducer來回傳新的state….
以上看起來一層又一層的連帶關係其實沒那麼複雜,在Observable看來,每一個行為(action)都是一條流,我們可以這樣表示:

1
2
3
const store$ = action$
.startWith(initState)
.scan(reducer)

store$開始先有startWith初始化狀態,scan功能就是Rx版的Array.protype.reduce,這使我們每當有action發生,經過reducer後會回傳一個新的state
實際上我們的action$不會只有一種,可以試著用Merge來統合全部的action-stream到store裡;這部分的Design pattern有許多種,redux概念也沒有想像中複雜,可以動手試試,例如:

1
2
3
4
const store$ = initState$.merge(reducer$) // reducer$也是由好幾個reducer$合成
.scan((state, reducer) => state.merge(reducer(state)))
.publishReplay(1)
.refCount()

結語

Rx’Library真的是項很高效的工具,尤其有諸多語言版本,理解概念&熟悉操作可以用在許多平台上,在我看來是值得投資的
當然它和redux一樣都實踐了Flux style的管理方式,只是學習成本似乎更高,所以也不用盲目地一概都用RxJs,簡單的專案用redux或許是更高效開發的選擇

Reference

  • Redux in a single line of code with RxJS

5個RxJs有趣範例

Tu-Szu-Chi 發表於 2017-09-15

之前提到過RxJs,這篇文章主要是從Rx’Library官方的練習題挑出5個來介紹,都蠻好理解的
有興趣的朋友也可以直接試試看,run起來對了才能解鎖下一題,挺有趣的


Retrieve id, title, and a 150x200 box art url for every video

首先要先介紹一下Function Programming編程精神,data的結構大概是以下這樣的Array

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
var movieLists = [
{
name: "Instant Queue",
videos: [
{
"id": 70111470,
"title": "Die Hard",
"boxarts": [
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" },
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard200.jpg" }
],
"url": "http://api.netflix.com/catalog/titles/movies/70111470",
"rating": 4.0,
"bookmark": []
},
{
"id": 654356453,
"title": "Bad Boys",
"boxarts": [
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys200.jpg" },
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys150.jpg" }
],
"url": "http://api.netflix.com/catalog/titles/movies/70111470",
"rating": 5.0,
"bookmark": [{ id: 432534, time: 65876586 }]
}
]
}
// ...

因要針對每一筆videos做檢索,所以先

1
2
3
4
5
movieLists.map(obj =>
obj.videos.map(video =>
// ...
)
)

到這一部已經可以拿到id,title了,接著要過濾width !== 150的boxarts

1
2
3
4
5
6
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts.filter(image => image.width === 150)
// ...
)
)

最後直接回傳我們要的Object形式

1
2
3
4
5
6
7
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts
.filter(image => image.width === 150)
.map(boxart => { return {id:video.id, title:video.title, boxart:boxart.url}})
)
)

到此階段回傳的Array會多了兩層

1
[[[{"boxart": '...', "id": 70111470, "title": "Die Hard"}], ...]]]

可以在外層兩個map各補上降維的Method

1
2
3
4
5
6
7
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts
.filter(image => image.width === 150)
.map(boxart => { return {id:video.id, title:video.title, boxart:boxart.url}})
).concatAll()
).concatAll()

雖然這是個簡單的FP範例,但可以感受出每個步驟都具有語意化

Retrieve url of the largest boxart

再來也是要介紹FP核心之一的reduce
data結構如下,要做比較的動作,找出Size最大的圖片(width * height)

1
2
3
4
5
6
var boxarts = [
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture200.jpg" },
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture150.jpg" },
{ width: 300, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture300.jpg" },
{ width: 425, height: 150, url: "http://cdn-0.nflximg.com/images/2891/Fracture425.jpg" }
]

reduce第一個參數要傳入處理動作的Function,第二個參數是初始值,預設為Array中第一個元素,所以我們不用特別傳入

1
2
3
4
5
boxarts.reduce((currentImg, current) =>
(currentImg.width * currentImg.height > current)
? currentImg.width * currentImg.height > current
: current)
.map(item => item.url)

以上為簡單的「比較」邏輯,reduce第一個參數中的Function會帶有兩個參數,第一個為當前的元素,第二個為當前的值
因reduce會回傳一個Array,而在這裡我們的Array中只會有一個元素,即Size最大的boxart
map同樣會回傳一個Array,裡面也只有一個url字串

Completing sequences with takeUntil()

takeUntil() 我覺得是個很好用的功能,可以預先註冊好什麼時候停止取資料

1
2
3
4
const stopButtonClicks = Observable.fromEvent(stopButton,'click')
microsoftPrices = pricesNASDAQ
.filter(priceRecord => priceRecord.name === "MSFT")
.takeUntil(stopButtonClicks)

我們假設pricesNASDAQ是一個NASDAQ股市報價的stream,而我們想從中取得微軟的報價所以利用了filter
並註冊了當stopButton按下時就停止取stream資料,可以在這JS Bin試試
會發現當stopButton按下後,會觸發Observer中的complete,其餘還有takeLast、takeWhile可以試試

Throttle Input

接下來要介紹Rx’Library中非常實用的功能之一 - throttleTime
我們可以設定一段時間,例如throttleTime(1000),表示一秒內所有的數據,我們只截取第一筆
這剛好可以解決我們針對某些API或DOM Event要避免短時間內重複觸發的問題,最典型的就是搜尋框(search box)

1
2
3
const input = Rx.Observable
.fromEvent(document.getElementById('input'),'keydown')
.throttleTime(1000)

與之相對的是debounceTime,可以在JS Bin試試

Distinct Until Changed Input

這是最後一個要介紹的Method,只取和前一個相比有變化的值,這概念是否有點熟悉?
在React中我們也是希望Component要有改變到state才render,故有了shouldcomponentupdate這生命週期
那如果有方法能幫我們確保拿到的一定是有變化的數據,是否就能少寫這shouldcomponentupdate了呢
一樣附上JS Bin給大家試試

結語

Rx’Library是個功能很實用的工具,也因為功能很多,所以需要花時間去摸索,當碰到問題時才會發現可以用幾個Method輕鬆的解決
Rx實做多了,也會發現自己Function Programming精進了不少

RxJs Observable - HOT & COLD

Tu-Szu-Chi 發表於 2017-09-13

Observable Hot & Cold

Rx’Library Observable可大致分為兩種類型 - Hot & Cold
在往下介紹前,可以先將Hot想成廣播電台(Radio),Cold是CD
Hot Observable的訂閱者,會讀取同樣的source;Cold Observable則每個訂閱者的source都是獨立的


Cold Observable

只有當訂閱者訂閱的時候,Cold Observable才會推送資料,且每個訂閱者都是獨立的流,就像是CD只有當你放進去按播放後才會有音樂
Observable.interval就是種Cold類型的流

1
const source = new Observable.interval(100).take(3)

可以在這JS Bin看到observerA & observerB是各自的數據流

Hot Observable

不管有沒有被訂閱,只要創建後就開始有數據流,Mouse Click Event就是最清楚的範例
Click事件一直在發生,但只有當你訂閱時你才收得到,且收到的是訂閱後的「觸發事件」,之前的不會收到
當然,如果你取消訂閱了,數據流依然會持續產生
這類的情境像是「股市最新報價」,你只會收到最新的那一個報價,不會收到五分鐘前的那筆
Hot Observable原理其實是我們和數據流間還有一個「中間人」,會一直監聽數據流,我們再跟他取資訊就好(真是忙碌的傢伙)
中間人會有一份訂閱者的清單,這樣才知道推播時要推送給哪些人

Subject

先附上JS Bin的Code
之前有提到訂閱者(Observer)會有三個Method - next、error、complete,理所當然Subject作為中間人他也該具備這些特質
並且有一份訂閱者的清單,有興趣的可以看官方的Source Code
Subject可以訂閱也可以被訂閱,他既是Observable也是Observer
他還有其他三個變形 - BehaviorSubject、ReplaySubject、AsyncSubject,能在我們訂閱時有不同效果

Operator

上一段的Code中,我們先新建Subject,再一一訂閱,這讓程式碼變的冗長,Rx’Library有提供些Operator讓我們寫的更簡潔點

multicast

1
2
3
4
const source = new Observable
.interval(300)
.take(3)
.multicast(new Rx.Subject())

multicast會回傳可連結(connect)的Observable,要真的執行connect後,Subject才會去訂閱source,且開始推送數據

1
const realObserver = source.connect()

connect會回傳subscription,將此unsubscribe才會真正退掉Observable,將observerA/observerB退訂中間人還是會收到數據流

1
realObserver.unsubscribe()

以下這種寫法可以更簡潔

1
2
3
4
const source = new Observable
.interval(300)
.take(3)
.publish()

refCount

但要多寫一段connect也是很麻煩,我們希望一有人訂閱就開始推送(observers.length > 0),refCount可幫助我們

1
2
3
4
5
const source = new Observable
.interval(300)
.take(3)
.publish()
.refCount()

要退訂不讓數據流推送的話,只要讓訂閱人數變成0即可(範例中就是讓observerA & observerB unsubscribe即可)

再給一個簡潔的寫法,refCount + publish

1
2
3
4
const source = new Observable
.interval(300)
.take(3)
.share()

結語

Rx’Library是一個很實用但也相對需要花時間學習的工具,畢竟要去習慣「Everything is stream」的思維
在此附上RxJs的核心開發者之一 - Andre Staltz所寫的應用範例,推薦大家去看看


Reference

30 天精通 RxJS(22): 什麼是 Subject?
Hot vs Cold Observables

RxJs Observable 介紹

Tu-Szu-Chi 發表於 2017-09-13

Everthing is stream

關於RxJs

RxJs是ReactiveX的一個JS Library(還有其他平台 ex.RxJava、RxSwift…)

「An API for asynchronous programming with observable streams」

以上是ReactiveX官網標題,也響應了文章頂部的圖片,將任何動作、事件都看成是「流」,我們去監聽、觀察,經過轉換、合併、過濾,再給予對應的Callback

Observable

Observable是Rx’Library的核心,它的概念是由兩個Design Pattern融合起來 - Observer & Iterator

Observer

其實跟我們平常用到的addEventListener概念一樣,註冊一個監聽事件,當觸發時執行clickHandler

1
document.body.addEventListener('click', clickHandler)
當然,一個事件可以有多個Listener,這在後續會解釋

Iterator

想像成有一個指針指向一個資料序列(ex.Array),我們想要得到這序列的資料必須透過Iterator的方法 - next()
當我們呼叫next()後,Iterator才會丟出一筆資料出來

1
2
3
4
arr = [1,2,3,4] //假設arr是Iterator
arr.next() // 1
arr.next() // 2
// ...

總結來說Observable這個可觀察的流(stream),上面會有資料隨著時間推送,它可以經過序列的方式處理資料(map、filter、reduce…),甚至也可以合併兩個不同的流,來讓Listener得到資料

Observer

Observer是較會頻繁用到的功能(其餘還有Subject、Schedulers…),他也是之前比喻的Listener
我們用實例來講解這些,首先建立一個要觀察的流(Observable)

1
2
3
4
5
6
7
let observable = Rx.Observable
.create(observer => {
observer.next('S');
observer.next('O');
observer.next('V');
observer.complete();
})

實務上我們的Observable比較不會這樣建立,通常會直接用fromEvent、fromPromise之類的,而不是create,針對某個事件來做
在此只是範例演練而已

這個observable會依序推送S->O->V給observer,接著我們要有一個Observer來監聽這個流

1
2
3
4
5
observable.subscribe({
next: (val) => console.log(val),
error: (err) => console.log(err),
complete: () => console.log('Complete !')
})

.subscribe(next, error, complete),Observer就是由三個Method組成,負責處理接收到資料後、發生錯誤後和整個observable結束後

當這個流被訂閱後馬上就會送出資訊給監聽的人,可以在這個JS Bin試試

Operator

有了流&監聽者後,之前也有提到Observable可以像序列一樣處理資料,所以這裡加上Function Programming的核心進去

1
2
3
4
5
6
7
8
9
10
11
const observable = Rx.Observable
.create(observer => {
observer.next('S')
observer.next('O')
observer.next('V')
})
.map(str => str + '!')
// ...subscribe
// 'S!'
// 'O!'
// 'V!'

再舉一個例子

1
2
3
4
5
6
7
8
9
10
11
12
javascript
const observable = Rx.Observable
.create(observer => {
observer.next('S')
observer.next('O')
observer.next('V')
})
.scan((x, y) => x + y);
// ...subscribe
// 'S'
// 'SO'
// 'SOV'

其實scan就是Observable版的reduce,兩者的差別,前者必會回傳一個Observable物件,後者就是回傳Value(Int、Array、Function…)
所以讀者可以把observable印出來,就可以看見滿滿的Observable Method

結語

這篇文章只概述了Rx’Library核心,其餘還有很多強大的功能,能幫助我們寫出漂亮的Function Reactive Programming
有興趣的讀者可以去RxJS Marbles自己拖拉體驗一下


Reference

30天精通Rx JS
TB-Reactive Programming 簡介與教學(以 RxJS 為例)
Rx JS Marbles

漫談 FRP - ReactJS vs CycleJS

jackypan1989 發表於 2017-09-08

2014年,CycleJS 作者 André Staltz 寫了一篇 Don’t React ,大力批評 ReactJS 並不 reactive。這篇文章在當時引起軒然大波,雖然在三年後的今天已經被移除,但我們依然可以在 youtube 上面看到古老的戰神影片 André Medeiros: Don’t React (Webbisauna 2014)。這三年期間,我們都可以看到兩個 lib 的變化,還有社群推行方向,以下會針對兩個 lib,並討論 FRP (響應風格的函數式編程)。

FRP (響應風格的函數式編程)

  • FP (函數式編程)
    所有東西都是函數,在語言中是第一公民,函數可以當作參數外 (x, func) => func(x),而函數也可以回傳另一個函數 x => y => x + y,因此可以創造出各種高階函數(Higher Order Function),也可以進行各種合成 (compose) 來完成各式各樣的功能,不用像物件導向,必須用很多種設計模式去優化代碼,我必須說,整個開發者世代花了好多時間導入物件導向,最後得到『多用合成,少用繼承』的結論,但其實在函數式編程中,合成是多麼渾然天成,一招就能打天下 :p

  • Reactive (響應風格)
    函數中不會直接去操作外部的函數,取而代之的是都是去反應外部函數(參數)做了什麼事,而去改變,在物件導向風格常見於觀察者模式 (Observer pattern),好處是分工非常明確,也容易追蹤代碼潛在問題。

兩者比較

  • CycleJS
    Cycle 更強調 reactive,因此你不會在裡面看到 this 關鍵字,但因為你不可能全部都沒有 side effect。所以這部份 cycle 利用 driver,把他認為髒的 side effect 抽出來處理。另一方面 Cycle 本身全部都是使用 RxJS / xstream,去管理所有的資料流,user input 會轉換成流,state 與 state 改變也會轉變成流,甚至連 render dom 也是流,因此整個架構就是多個流的合成與串接,action -> state -> render -> action 形成一個超級流的大循環,我想這也是為什麼叫做 Cycle 的原因。

  • ReactJS
    原本 lib 本身其實談不上太多 reactive,因為 reactive 的部分只有 render() 這個函數,Component 內的其他函數都是 active,因此會看到 this & this.setState 這種東西,反觀 CycleJS 就不會看到 this 這個 JS 一直以來常常會出包的關鍵字,當然在 redux 出來後,以及 rx-react, redux-rx, 甚至 redux-observable 引進後,user input 與 state 改變才開始真正一步步 reactive 化 / stream 化。

結論

我個人覺得之所以 fb 會大力推動 react 的原因,有一個很大重點在於想要推函數式編程 (functional programming),但不可能一開始就推個 Haskell 大家都嚇死,等到大家慢慢熟悉這個體系,開始 componenet base programming,寫一段時間後,就會開始寫高階元件 (High Order Component,也就是一個函數會傳進一個 component 再吐一個 component) ,然後又開始學習 render 永遠只根據 prop 改變的 stateless component (pure functional component)。你就會發現你入坑了(你也可以看到 fb 釋出這些想法的進程),開始感受 FP 的奧妙,原來 component 的各種組成合成,就跟 FP 中的函數合成一樣。

Cycle: 天生全部 reactive,架構上更好更美
React: 需要第三方協助(rx-react…),但有大量社群支持

總歸一句,在前端這麼吃重 ui 操作與呈現的情況下,社群發展到最後一定是慢慢往類似或是相同方向前進。
至少三年前的爭議已經平息,接下來就看社群是不是可以往更函數式編程前進了(ELM, Purescript … etc)。

Reference

  1. CycleJS
  2. rx-react
  3. redux-rx
  4. redux-observable

Immutable.js 簡介

Tu-Szu-Chi 發表於 2017-09-08

Javascript中的對象是可變的(Mutable)

1
2
3
let task = { id : 1, status : "Load" };
let task2 = task
task2.id = 2 // task.id 也是 2

我們可以輕易地修改對象,這在專案複雜後是種隱患
雖然可以用Object.assign()來解決,但當assign的對象比數不小時,效能會變差
Immutable.js很好的解決這些問題


原理

Immutable.js背後有兩個重要的原理

  • Persistent Data Structure

經由old-data創建new-data時,old-data可用且不可變,這也是Functional Programming重要概念-沒有副作用(Side Effect)

  • Structural Sharing

當有某個節點Update時,不會整個Copy一份,只會新建需要的變動的部分,其餘參照原本的
這大幅的優化效能且也節省了記憶體
Sharing

優點

降低Mutable帶來的複雜度

1
2
3
4
const mutableFn = data => {
doSomething(data); // data = {id:1,value:2}
console.log(data.value) // data.value is ?
}

在有Mutable的可能時,我們無法保證data不變,但用Immutable的話就可以確認依然為2了

節省記憶體

因為有Structural Sharing,許多對象可以被重複使用,沒用到的也會自動回收
以下截選camsong的文章中的一段Code,能清楚表達Structural Sharing優點

1
2
3
4
5
6
7
8
9
import { Map } from 'immutable';
let a = Map({
select: 'users',
filter: Map({ name: 'Cam' })
})
let b = a.set('select', 'people');
a === b; // false
a.get('filter') === b.get('filter'); // true

結語

在專案日漸複雜龐大時,Immutable.js能有效地提升效能和維護性,但專案較小且不複雜時或許就不一定要用它了
經由Immutable.js建的對象並不是原生Javascript對象,要特別注意,所以要操作幾乎都是要用Immutable.js的Method


Reference

Immutable.js, persistent data structures and structural sharing
Immutable詳解及React中實踐

Function Currying

Tu-Szu-Chi 發表於 2017-09-08

Haskell Curry
函數「柯里化」這單看字面上意思真的猜不出想表達什麼,因為Curry一詞其實是取自人名
Haskell Curry-偉大的邏輯學家,Haskell這語言命名也是取自於他
名字有了含意後我們來瞭解其中


概念

經過柯里化的Function會回傳 Function||Value
維基百科是這麼解釋

如果你固定某些參數,你將得到接受餘下參數的一個函數

解釋的有點饒口,所以我們往下來看點範例

範例

接下來會使用到Ramda來協助我們進行Curry

1
2
const addNumber = (x,y) => x + y;
addNumber(1,2) // 3

我們要呼叫addNumber就是傳入兩個參數進去,那如果想要一個Function固定會將傳進來的值加100呢?以下

1
2
3
const addNumber = R.curry( (x,y) => x + y ); // 柯里化
const add100 = addNumber(100)
add100(1) // 101

那如果參數不只兩個呢?

1
2
3
const addNumber = R.curry( (x,y,z) => x + y + z );
const add100 = addNumber(100)
add100(1)(2) // 103

以上是比較簡單的應用,我從ScottSauyet的文章截選幾段程式來演示Curry的好處

原始

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
getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(function(data) {
return data.tasks;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (tasks[i].username == membername) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (!tasks[i].complete) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [], task;
for (var i = 0, len = tasks.length; i < len; i++) {
task = tasks[i];
results.push({
id: task.id,
dueDate: task.dueDate,
title: task.title,
priority: task.priority
})
}
return results;
})
.then(function(tasks) {
tasks.sort(function(first, second) {
var a = first.dueDate, b = second.dueDate;
return a < b ? -1 : a > b ? 1 : 0;
});
return tasks;
});
};

處理後

1
2
3
4
5
6
7
8
var getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(R.get('tasks'))
.then(R.filter(R.propEq('username', membername)))
.then(R.reject(R.propEq('complete', true)))
.then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
.then(R.sortBy(R.get('dueDate')));
};

這差異就很明顯了,這也是為何柯里化在Function Programming如此重要

總結

Currying是促使Function Programming簡潔的原因之一
我們也可以經由上面「處理後」的程式碼發現這樣更語意化,明白每一步在做的事情


Reference

Favoring Curry
Curry化 - Javascript Functional Programming 指南

Pointfree Javascript

Tu-Szu-Chi 發表於 2017-09-08

截自Haskell wiki

Pointfree Style
It is very common for functional programmers to write functions as a composition of other functions, never mentioning the actual arguments they will be applied to

Pointfree這種風格在中文也譯作「無值」、「隱含式」
以下範例多會使用 Ramda 這函式庫來協助我們寫成Functional Programming
p.s. Ramda中的方法多是Curry化,還不懂Curry化的可以參考這篇文章


程式的本質

即是在解決問題,而最常碰到的問題形式多為
–>輸入–>運算–>輸出–>
運算我們可以先看作是個fn(Function)
–>a–>fn–>b–>

1
fn = a => b

fn這Function接收一個a參數(Input),並回傳b(Output)
當運算可能比較複雜時,流程會變成
–>a–>fn1–>(m)–>fn2–>(n)–>fn3–>b
m,n分別表示經過fn1,fn2出來的結果,我們可以用Ramda來寫成

1
fn = R.pipe(fn1, fn2, fn3)

R.pipe會將參數先傳入到fn1,得出的Output在傳入fn2依序下去,與之相對的是R.compose

Pointfree概念

經過以上例子的演示,我們可以用更語意化的例子會比較清楚

1
2
3
4
const addOne = x => x + 1;
const square = x => x * x;
const addOneThenSquare = R.pipe(addOne,square);
addOneThenSquare(2) // 9

先將要處理的流程拆解出來,並先定義好,再組合起來,這就是Pointfree的核心概念

定義的時候不使用所要處理的值,只合成運算過程

addOneThenSquare也可以寫成這樣,但就是不是Pointfree Style且也沒那麼簡潔&易讀

1
2
3
const addOneThenSquare = x => R.pipe(addOne,square)(x);
// 或是
const addOneThenSquare = x => square(addOne(x));

更多的範例可以參考阮一峰老師的文章

結語

Pointfree這種風格剛開始會不太習慣,無法直接看到Function本身的參數,但大部分情況可以通過註解來輔佐
畢竟Pointfree不單只是讓我們的Code更簡潔、語意化
我們會從原本的「面向對象」(data.id,data.name…)轉而更專注於「處理」的動作(map,reduce,filter)
這樣會更瞭解整個流程在做什麼,取代用許多for-loop,if-else的嵌套
而且拆成各個「單一職責」的Function也較好做Testing & Debug


Reference

Pointfree編成風格指南 by 阮一峰
Thinking in Ramda (Pointfree Style)
Pointfree Javascript

什麼是 High Order Component

Tu-Szu-Chi 發表於 2017-09-06
1
hoc :: ReactComponent -> ReactComponent

HOC(High Order Component)帶有點Function Programming概念
它本質上就是一個Function,傳入一個(或多個)React.Component,再回傳新的React.Component

HOC在做什麼

如果有接觸過Redux應該有聽過Presentational / Container Component
Container Component主要負責「怎麼做事情」
Presentational / Container Component
Tom Coleman提到一項Container Component很重要的職責
就是負責將Global State分派給底下的Child Component
而Redux中的connect()就是HOC的一種,通常我們都會用

  • mapStateToProps 將需要的state轉換成Presentational的props
  • mapDispatchToProps 注入需要的callback行為(ex. onClick、onHover時)
1
2
3
4
const mapStateToProps = (state, ownProps) => {
}
const mapDispatchToProps = (dispatch, ownProps) => {
}

當各個Container都負責整個Application的一塊,大家有各自的職責並做好本份就會讓整個架構很有效率

HOC為何特別

A recent trend in React is towards functional stateless components.
These simplest “pure” components only ever transform their props into HTML and call callback props on user interaction.

以上截自Tom Coleman文章中的一段話,可表達出為何要分出Presentational / Container
當將「複雜的活兒」交給特定的Container做,Presentational只管「顯示」&「拿取」,這樣碰上Bug時我們就知道該解決誰找誰來解決了

Component幾乎都需要存取Global State中的某一部分,但不能隨意地讓每個Component都可以調用,所以HOC形成了一種約束,由它來分配

而Container也取代了原生的mixins,可在網路上找到許多比較兩者的文章

結語

High Order Component的出現也間接表明React團隊往Functional Programming靠攏
Javascript也有些Library可幫我們的Code更Functional ex.Ramda.js
搞懂Functional Programming的思維,以現在來看是件值得投資的事


Reference

Understanding Higher Order Components
Presentational and Container Components
初識React中的High Order Component

1234
SOVMedCare

SOVMedCare

Full Stack JS #FRP #RxJS #Redux #React

31 文章
19 標籤
GitHub E-Mail FB Page Instagram
© 2017 — 2020 SOVMedCare
由 Hexo 強力驅動
|
主題 — NexT.Gemini v5.1.2
0%