nereuseng

重新出發


  • 首頁

  • 歸檔

如何正確的設置babel(Late 2018)

發表於 2018-11-27 | 更新於 2018-12-09

前言

之前用 Babel 轉換語法,都是靠框架和 cli轉換,沒試過從頭自己設定。直到最近,接了沒只用原生 Javascript 的小案。身爲 ES6++ 的愛好者,一定要使用 Babel 才能存活啊,最後只好自己從頭研究XD 查了一下,發現很多設定其實已經過時,因此這邊寫一份筆記整理,希望能減少其他人研究的時間。

Babel 做什麼

Babel 能把比較新的 Javascript (關鍵字: ES6,ES2015 等),轉換成目前 Browser 能看得懂的 Javascript (ES5),讓 Developers 能用新語法的同時,不會讓網頁壞掉。

Babel 的 2 種模式

  1. Babel 默認只轉換語法(Syntax), 像是:
    a. Arrow function ( () => {} )
    b. Spread ( [ …state, isOpen ] )
    c. Destructing ( const { data } = res )
    d. Default Parameters ( ( id = NaN ) => {})
  1. 而自帶的 Api 、原生內置 Methods,需要加上 polyfill 後才能使用,像是:
    a. Promise
    b. Symbol
    c. Array.from
    d. Object.assign
    e. fetch

1. 轉換語法(Syntax)

直接轉換語法是非常方便的,以下放了 Webpack 和 Gulp 的範例。

1.1 Webpack

Webpack:

1
npm install -D babel-loader @babel/core @babel/preset-env webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.config.js
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}

1.2 Gulp:

1
npm install --save-dev gulp-babel @babel/core @babel/preset-env
1
2
3
4
5
6
7
8
9
10
const gulp = require('gulp')
const babel = require('gulp-babel')

gulp.task('default', () =>
gulp.src('src/app.js')
.pipe(babel({
presets: ['@babel/env']
}))
.pipe(gulp.dest('dist'))
);

想要快的話,可在 Babel 網站的 Try It Out 直接轉換。(似乎沒有比較方便?)

2. Polyfill

相較於直接轉換 Syntax,Polyfill 才是讓人頭疼的地方。有時候需要支援的 Browser 太舊(eg: IE11),龐大的 Polyfill bundle 拖累了其他不需要 Polyfill 的 Browser。儘量用只需要的 Polyfill 變成是需要優化的目標。

2.1 Polyfill:用Webpack

2.1.1 entry 加入 @babel/polyfill

這是最早期的做法,就是讓 Webpack 也 bundle polyfill的檔案:

1
2
3
4
5
// webpack.config.js

module.exports = {
entry: ["@babel/polyfill", "./app/js"],
};

2.1.2 useBuiltIns

以前的做法是轉換語法(Syntax)和 Polyfill 是分開設定,現在在設定同一處統一用 useBuiltIns 設定,還有加上target(browserlist)的功能,讓設定方便很多。

useBuiltIns 有三種設定方式:
a. entry
b. usage
c. disable(就是不要加 polyfill 咯,就不解釋了)

2.1.2 a.) useBuiltIns : ‘entry’

在你的app入口加上這個:

1
2
// app.js
import '@babel/polyfill'

根據1.1和2.1.1的基礎做更動:

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
  // webpack.config.js
module: {
- entry: ["@babel/polyfill", "./app/js"],
+ entry: ["./app/js"],
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
- presets: ['@babel/preset-env']
+ presets: [
+ '@babel/preset-env',
+ {
+ "targets": {
+ "ie": "11"
+ },
+ "useBuiltIns": "entry"
+ }
+ ]
}
}
}
]
}
2.1.2 b.) useBuiltIns : ‘usage’

在app入口拿掉:

1
2
// app.js
- import '@babel/polyfill'

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
  // webpack.config.js
module: {
- entry: ["@babel/polyfill", "./app/js"],
+ entry: ["./app/js"],
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
- presets: ['@babel/preset-env']
+ presets: [
+ '@babel/preset-env',
+ {
+ "targets": {
+ "ie": "11"
+ },
- "useBuiltIns": "entry"
+ "useBuiltIns": "usage"
+ }
+ ]
}
}
}
]
}

上面設定完之後,就會根據你在寫 code 的過程中,加上需要的 polyfill。

但是由於 Babel 不能判斷你 code 裏面的 type,所以不能載入正確的 Polyfill。比方說:

1
2
import "core-js/modules/es7.array.includes";
a.includes // 預設 a 是一個 Array,但實際 a 也可能是 String

2.1.3 手動引入要的 core.js

1
2
import 'core-js/modules/es6.set.js'
import 'core-js/modules/es6.array.find-index.js'

但如果需要很多 Polyfill 的話,一個個載入就會變得很麻煩。

2.2 Polyfill:不用Webpack

2.2.1 Gulp、Browser 引用打包

在引入@babel/polyfill下的dist\polyfill.min.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// gulpfile.js
const polyfill = 'node_modules/babel-polyfill/dist/polyfill.min.js'

var jsfiles = [
'js/lib/otherJsLibrary.js',
polyfill,
'js/index.js'
];
gulp.task('default', function() {
return gulp.src(jsfile)
.pipe(concat('index.min.js')) //合併檔案
.pipe(uglify())
.pipe(gulp.dest('dist/')) //輸出
});

或者直接抓出檔案,在 Browser 直接引入:

1
<script src="polyfill.js"></script>

這樣的結果當然就是 Bundle 整個會變得非常肥,不需要 Polyfill 的 Browser 也被迫下載,造成資源的浪費。

2.2.2 Polyfill.io

這個由金融時報(Financial Times)提供的服務,根據你的 UA 來判斷需要的 Polyfill(當然你可以指定,點這裏可以看文檔),用法也很簡單,在你的網頁加上:

1
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>

在不同的 Browser 看就會發現很回傳的東西不太一樣:

這個是 Chrome 下看到的

這是 IE 11 下看到的

當然若有安全上的考量,可以到他們的github,下載然後自己 Deploy 在自己的 Server 上吧!

雖然在特定較舊的 Browser,還是有很肥的問題,但是卻拯救了其他 90% 的 Browser!

總結

總結那麼多方法,代表說我之前活動案被折磨很慘(誤),個人覺得最好的方法,是搭配 Webpack 的 Babel,加上自己架設的 Polyfill services,但是實際效果有待確定,有空再更新使用效果。

參考資料

  1. https://babeljs.io/docs/en/babel-polyfill
  2. https://github.com/Financial-Times/polyfill-service
  3. https://polyfill.io/v2/docs/
  4. https://babeljs.io/docs/en/babel-preset-env
  5. https://www.jianshu.com/p/0dc3bddb6da8
  6. https://juejin.im/post/5c09d6d35188256d9832df9d

土炮處理:api非同步時間差造成的response順序問題

發表於 2018-10-08 | 更新於 2018-12-08

問題——B頁面顯示A資料:

在 SPA 下,User 點了A頁面,送出 A request。但是頁面出來前,立馬切換到B頁面,最後頁面B顯示A的資料。

思路:

  1. 原生取消 request => 2. 用 UI Loading 擋 User => 3. 幫 request 加流水號,去最新的 respond

1. 原生取消request

一開始想要做 request cancel,但我們因爲是使用 fetch 的關係,原生不支援 abort 功能,另外要支援 IE 11 還要加多一包 polyfill,覺得太麻煩就放棄了。

2. 用UI Loading擋User

我的同事一直主張用這個方式,而我後來拒絕的原因,是因爲這比較適用在 Form 提交的情境下,若 User 誤點還要等資料回來才能點, User 應該會很惱火XD

註:適用在 Form Submit

3. request加流水號

基本上沒什麼問題,唯一的風險,大概就是 User 的人數多起來,Server 的負擔有點重,這個後期再處理好了。

其實這個 idea 是從 RxJS 得到靈感的XD 一開始是想到 huli 大大在 Modern Web講 RxJS 提到的 Switch Map,但案子快完成了根本不可能。我就想能不能自己土炮做出類似Switch Map,於是就有了加流水號的想法。具體想法是在 call api 之前,在全域加上變數,再來到 callApi() 裏面,用 timestamp 加上 Uid,在 Api 回傳之後比對是不是最新的 Uid。看不懂直接看下面的code

1
2
3
4
5
6
7
8
9
10
11
12
const variable = {
lastestUid: ''
}

function callApi() {
const apiUid = new Date().getTime()
variable.lastestUid = apiUid
return Api.fetchUserData().then( () => {
if (apiUid !== variable.lastestUid) return
// update api data here
})
}

這樣其實就解決了問題,還有可以優化的地方,把 new Date() 換成從0開始的 counter,效能會更快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const variable = {
--lastestUid: ''
++lastestUid: 0
}

function callApi() {
--const apiUid = new Date().getTime()
++const apiUid = variable.lastestUid + 1
variable.lastestUid = apiUid
return Api.fetchUserData().then( () => {
if (apiUid !== variable.lastestUid) return
// update api data here
})
}

後期處理

這樣的方法畢竟還是應急,之後會 User 亂點對 Server 負擔還是很大的。詢問群組的大大之後,覺得這樣的方法還不錯:

1
2
3
4
5
6
7
8
9
10
個人作法上會偏向用 UI Loading Modal 鎖使用者
因為要去 check 最後一次 update 這件事會比較容易出錯

Fetch Request 時,要把 Timeout 弄短一點,UI 擋 refresh,出現3秒之類的 loading,
Timeout 短,那使用者至少不會太生氣 (如果網路慢),然後用 loading 去鎖使用者亂點

然後晚回來的資料可以存在類似 redux 的資料中心,這樣下次還可以使用

沒及時回來:用 local 舊的資料
及時回來:用剛拿到的即時資料

但是這樣的方法好優化好多東西哦,除了 Fetch 沒有 Timeout 的功能,如果 Timeout 後晚回來的資料要怎麼拿來更新data而不被丟掉,目前也是個問題,加上可能要自幹改很多東西,可能下次再試試看好了XD

結論

這件事要做得漂亮,不加重 Server 的負擔或降低 UX 真的要花很多功夫,而且原生沒有支援很多,很多眉眉角角都要自己處理,下次是不是要開始使用RxJS了呢XD

參考資料

  1. https://stackoverflow.com/questions/46946380/fetch-api-request-timeout
  2. https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
  3. https://drive.google.com/file/d/1HS5uBGfWf8900HJE3otnKQIex9c-KEd7/view

專案回顧心得—筆記篇

發表於 2018-09-14 | 更新於 2018-09-16

前言

這次的心得比較偏向個人的筆記,提醒自己在下次專案應該注意的部分。

1. 和設計師溝通共有的設計元素(Style Guide)

確定相同的設計元素(字體、按鈕、標題style、間距、邊框),讓那些抽離出來變成組件或者是共用同個CSS。最佳的方法就是用Zeplin之類的軟體,增加溝通效率

2. RWD的版型考量因素

設計師通常只畫5個斷點(320px、640px、768px、1024px、1280px),

  • 要考量不同斷點之間的排版
  • 字很多要怎麼辦、字太少要怎麼辦
  • button變多或select只有一個怎麼辦
  • 儘量把內容在手機上做完整的顯示(RWD not AWD)
  • 圖片橫圖同時也有直圖

3. 網頁使用步驟與互動(UX)

和pm與設計師討論User使用的步驟,確定是否有animation。

4. Api格式考慮以視覺的Component爲主

跟後端要求api的資料和Component同步,若無法達到,則考慮自己fetch後用reduce重組資料。

5. 過早優化是萬惡之源

在上線之前(甚至上線之後),需求都沒定下來,要用比較彈性的做法,先求有再求好:

  • 用上比較多功能的套件,方便需求增刪
  • 用css class而不是html selector來寫css(eg: NOT div > a)

6. 別把1%看得太重

也許還有1%的人還在用IE,但不能因爲他們就放棄新功能,應以大多數人爲主。被抓到再改吧(誤

後記

以上真的是濃縮再濃縮,精華中的精華,後期爲了這些而好痛苦,下次我不想犯下這些錯誤了(哭

專案回顧心得

發表於 2018-07-14 | 更新於 2018-07-15

從3月14號就開始推專案的第一個commit,到昨天我切完預訂設計提供的psd,算是完成了一個很大的里程碑,算上今天剛好4個月。中間切了30多個頁面,自幹了多少個元件,踩了很多css的雷(技術債活該lol),來回顧一下這幾個月的心得。

設計過於平面

設計在處理320的版型,是爲了小版特別更改版型,在640之後採用統一版型。而在321到639的版型直接套用320版型,則元件的比例就會變得又扁又瘦長。另外,在320被組合在一起的元件,到了1200就要被分開,要花很多時間和css調整。再者在1200的版型上,提供了非常多資訊,但在320卻一併塞進來,造成畫面太擁擠。

320的版型在639會很奇怪

See the Pen component strange in 639px by Nereus Eng Wei Xian (@nereuseng) on CodePen.

下面的按鈕在因爲小版型,而在中版型必須用position absolute固定,被迫與原本的結構分開

See the Pen Layout forced to restructured by Nereus Eng Wei Xian (@nereuseng) on CodePen.

雖然最後有達到設計的要求,但是卻花了太多心力和時間完全不划算。應在提供設計的時候,在討論的時候就要想想做的複雜度,並和設計師討論修改的方向。

Api格式不統一

整個專案到後期發現,很多時候元件都長得一樣,邏輯處理也一樣,但因爲api欄位名字不一樣,要寫一模一樣又長又臭的code,在這次的專案至少有四個這樣的頁面(能想像有什麼東西要改,就要做四次嗎?)。在開發之前,應要向後端統一格式,要不然後患無窮,後患無窮啊!!!

設計師溝通——Zeplin!

對工程師來說,Photoshop上的抓顏色測量工具太弱,無法很快抓取,尤其是在RWD版型之間測量,甚至產生量到天荒地老的錯覺。在前期和設計師溝通,但開發時間一長,難免還是會忘記,因此需要一些ui標註和溝通工具,像是Zeplin。只要點區塊,他就會顯示字體顏色、大小、行高,和別的元素區塊的間距等訊息,超省時間的!

Zeplin也有註記功能,能讓設計師在標註一些注意事項。他也有和Slack整合(雖然同事不愛用就對了

專案前要注意的地方

  • RWD之間的過渡版面
  • 在設計開工前溝通(使用工具,設計準則
  • RWD的內容是否能共用,而不是需要Display none或者position absolute(圖片儘量不要裁切
  • UX的互動方式(是否有動畫
  • User使用功能的流程(每一步的邏輯

設計基本準則

因爲當初CSS的方式是每一個元件獨立(Vue style scoped),一開始寫是非常美好,幾乎沒有什麼問題,也不會有CSS污染的問題。但後來發現一個很崩潰的事情:

兩三個禮拜前切的版有Button,於是我在那個Component的CSS中,定義他在RWD的各種大小顏色邊框動畫,但是卻發現現在切的Component也有一樣的Button,於是我又在那個Component裏面,寫了一模一樣的大小顏色邊框動畫。再來同個Component還有新的Button,hover之後邊框要有動畫
多兩個禮拜後,我又在新的設計圖看到這個按鈕。我知道他重複,想要提取出來,同時Button都要加上Border radius,但是我卻不知道他在哪裏了…

一直到我在FEDC聽到Atomic Design這個概念

1
We’re not designing pages, we’re designing systems of components.—Stephen Hay

他的概念是先從最抽象的地方,比方說顏色,字體,Hover顏色等開始設計,把這些抽象的東西組合起來變成一個小的元件,再各種小元件組合成大元件,就就能統一設計語言,也能減少重複css的問題,維護的時候就不會陷入痛苦。

之後可以嘗試和設計師溝通,確定一些小Component的顏色大小Hover動畫,並以這個爲基準來切版。

結論(與前面無關

這次的專案,做到讓人心力交瘁,好像身體某部分死掉了。但是也知道自己的精力,是一種開發資源,如果在開發資源有限的情況下,最有效率的做出最完美的網頁,變成是我下個案子的重點。聽同事說還蠻推薦我下個案子開始使用Grid和css variables,ie 11變成是最後才要解決的問題,這樣的想法讓我頓時豁然開朗。希望下次的專案讓我人生變得更美好XD

參考連接:

  1. http://bradfrost.com/blog/post/atomic-web-design/
  2. https://zeplin.io/

css實用使用技巧

發表於 2018-04-01 | 更新於 2018-06-14

前言

最近在支援一件大案子:要切快30頁同時也要顧及每一頁的RWD,同時使用vue來寫。原本要做的team的前端是一個新人,找我來幫忙切版,她就幫忙接api,設計與實作vue架構。在這些不斷切板的過程,同時也逛一個網站發現,作者分享了好多的css selectors的使用方式,發現幾個好用的小技巧,在這邊做筆記,也分享給大家。

counter()

配合僞元素:before使用,基本上就是更加靈活的List Element。功能非常強大非常推薦大家使用,除了可以自己設定在頁面的哪一角出現(只要還使用同一個.css檔,在SPA的時候甚至能跨頁使用),也能利用counters()創造多層級的序號。

See the Pen csstricks-counter() by Nereus Eng Wei Xian (@nereuseng) on CodePen.

Fluid Typography(流體排版)

先上網址:點我

在切版中,常會遇到設計圖在各自的斷點很漂亮,但是在兩者之間的版型,文字圖片大小比例失衡的情況(尤其是設計者只有平面的概念),這個網站的計算公式,提供了一個很好的工具,讓你無痕的從A斷點過渡到B斷點。而且我發現他除了文字,任何能吃calc屬性都能用,非常的方便,有使用預處理器(CSS Preprocesser)的話,甚至寫mixin直接套入數據。

var()或相同屬性獨立class

在一個版型當中,通常會使用相同的padding、字型大小,要一直重複寫的話非常繁冗,加上RWD不同的版型,設計稿突然要改的話,就要改好幾個地方。這時候非常需要統一管理,而同一管理的方式,一個是css自帶的功能var(),再來就是把相關的屬性獨立出來,像是.padding、.font-size

See the Pen csstricks-independent class by Nereus Eng Wei Xian (@nereuseng) on CodePen.

貓頭鷹選擇器 * + *

這個選擇器是在新加坡css.talk所分享的影片中,提到模組化css的一個技巧之一,+這個selector是The adjacent sibling selector,用意是用來選擇,前者同一層級中,往下算最靠近的元素。利用 + 的選擇器,可以把模組中從第二個開始的元素,加上margin-top等的style,這樣元素之間就有一定的空位。當然,如果使用:not(first-child)也會達到這樣的效果,但是若要真的實現,必須還要加上:not(first-child):not(:root)才不會選到body。但是這樣做的話,會造成權重太重語法改動的問題。

:not()

:not()的selector適合用在style元素之間橫線。傳統的做法,是在style完所有元素的橫線後,針對最後一個再加:last-child把橫線拿掉。使用:not()會很簡潔。

彈性處理Element內容的大小與排版

有一種情況,就是在同個尺寸下,元件的內容會很彈性,有時候多到每個Element塞不下,也會少到只有一個,固定字體大小的話,會非常難控制。想想有超多的Element,你每個放16px,空間超擠;但是只有一個元素的話,16px的字又太小了

:only-child

Child Element只有一個:only-child
因此利用:only-child的CSS selector,style只有一個Child Element的CSS。

:nth-last-child()

Child Element超過一定的數量:nth-last-child()
若想在超過6個Element的時候,做不同的style,可使用nth-last-child(n + 6)與nth-last-child(n + 6) ~ *,就能style超過6個Element。

參考連接:

  1. https://kevinyank.com/posts/css-selectors-redux
  2. https://websemantics.uk/tools/responsive-font-calculator/
  3. https://www.w3cplus.com/css/css-polyfluidsizing-using-calc-vw-breakpoints-and-linear-equations.html

跨網域控制iframe

發表於 2018-02-23 | 更新於 2018-12-08

前言

最近的業務需求,要在DFP上放視差廣告,就是原本比較小的圖片,滑到一定的高度之後,會自己變大。之前的做法是準備兩個iframe,需要的時候才顯示其中一個,但是需求方不想要投放兩個DFP廣告,並要直接控制同一個iframe的內容,因此才開始找尋如何跨網域控制iframe的方法。

困難

在HTML 5之前,對於iframe的控制,只能在於同個網域,這邊 完整的表示了同網域控制iframe的方法:

1
2
3
4
5
6
7
8
// 先抓iframe的ID
var iframe = document.getElementById("myIframe");

//抓你想要在iframe下抓的按鈕
var btn = iframe.contentWindow.document.getElementById('mybutton');

//或者直接呼叫iframe下的function
iframe.contentWindow.yourFunction();

這樣的話是還不錯,甚至在之前的案子中,我還利用它來播放iframe裡面的影片(當然現在已經不行了,參考鏈接),但是他不能用在DFP上。DFP廣告的素材都是自己上傳到DFP的Server,再從那邊的Server讀取,上面的方法就失效了。

方法

後來我查到在HTML 5有新的Api, Cross-document messaging,支援度也相當不錯,ie 11有支持。程式碼如下:

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
// Main Page
iframe.addEventListener('load', adsAction, false);

function adsAction() {
// distance between element with window top edge.
let dist = wrapper.getBoundingClientRect().top
if (dist > -250 && dist <= 100) {
// send msg to iframe to told iframe do smth
preview.contentWindow.postMessage('showComplete', 'https://iframeWebsite.dfp');
}

if (dist > 100 || dist < -600) {
preview.contentWindow.postMessage('showPreview', 'https://iframeWebsite.dfp');
}
}

// iframe
window.addEventListener('message', receiver, false);

function receiver(e) {
// Website filtering
if(e.origin === 'http://mainPageWebsite') {
if (e.data === 'showComplete') {
// do the things you want
} else if(e.data === 'showPreview') {
// do the things you want 2
}
}
}

在網頁中,抓了iframe後,對iframepostMassage,在postMessage中可以傳入三個函數,前面的兩個是你要傳的訊息,以及iframe的網域,第三個是可選的參數,我還沒研究。
在iframe中,通過addEventListener中的message,會讓iframe收到訊息,,並且通過傳送過來的data來控制iframe。但沒有過濾origin的話,會使得所有其他網域的網站,能控制這個iframe。最好的方法就是,在function中加上網址過濾的判斷。

遇到的問題

在一開始執行時,會出現

1
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://iframeWebsite.dfp') does not match the recipient window's origin ('http://mainPageWebsite').

一開始我以為是http和https的問題,後來查了一下,發現是javascipt很可愛的非同步bug,就是一開始網頁就會在節點載好後,開始執行js,但是因為非同步的關係,iframe的內容還沒有生成,自然postMessage過去就找不到相應的js啦,因此我在網頁js下加上了iframe.addEventListener(‘load’, adsAction, false); 確保iframe載好後重載一次,並且也把因為重載,而造成畫面閃一下的問題處理了。

結論

看到又能用的畫面真的很開心,希望之後能想辦法解決,iframe自動播放影片的問題(好像解決了,下次有就會再分享好了)。

利用Redux-actions 來減少Redux的Boilerplate Code(2018/04/01更新)

發表於 2018-01-17 | 更新於 2018-12-08

Why redux-actions?

最近一直在Redux卡關, 除了有點玄妙的邏輯以外, 更重要的是他的boilerplate code很繁瑣, 於是在Telegram群組的大大推薦下, 我使用了Redux-Actions的插件, 效果差了很多! 人生也變得美好XD 在這邊我講記錄我更改的過程.

背景提要:

我要在我的程式用HTML 5的Api取得使用者的座標, 再來更新使用者位置的天氣, 當中會用到兩個api, 在這邊我其實會有點疑問: 到底HTML5 GeoLocation的取得, 是不是Async的呢? 不過我不管啦, 先設定成是Async的好了, 那在這個範例就是有兩個Async的動作.(目前進度的關係, 先只有用到座標Api)

正常來說在傳統的React-Redux應該是這個樣子:


詳細的Code請點我
weather-actions.js

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
import { getUserLocation as getUserLocationFromApi } from 'component/userLocation.jsx';

// 先設定好每一個action的return

function startGetLocation() {
return {
type: 'LOCATION/START_GET_LOCATION',
}
}

function endGetLocation(lat, lng) {
return {
type: 'LOCATION/END_GET_LOCATION',
lat,
lng
}
}

//下面的function是利用redux thunk來進行async程式

export function location() {
return (dispatch, getState) => {
dispatch(startGetLocation());

return getUserLocationFromApi().then(res => {
const {latitude: lat, longitude: lng} = res.coords;

dispatch(endGetLocation(lat, lng));
}) catch (err) {
console.log('Error getting User Location', err);
}
}
}


weather-reducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const initLocationState = {
requestStatus: false,
lat: NaN,
lng: NaN
};

export function location( state= initLocationState, action) {
switch (action.type) {
case '@LOCATION/START_GET_LOCATION':
return {
...state,
};
case '@FORECAST/END_GET_LOCATION':
return {
...state,
lat,
lng
};
default:
return state;
}
}



createAction

這個是原本的code, 但是用上redux-actions之後code變成這樣:

Before:

1
2
3
4
5
6
7
8
9
10
11
12
13
function startGetUserLocation() {
return {
type: 'LOCATION/START_GET_LOCATION',
}
}

function endGetUserLocation(lat, lng) {
return {
type: 'LOCATION/END_GET_LOCATION',
lat,
lng,
}
}

After:

1
2
3
4
5
import { createAction } from 'redux-actions';
import { getUserLocation as getUserLocationFromApi } from 'component/userLocation.jsx';

const startGetUserLocation = createAction('START_GET_USER_LOCATION');
const endGetUserLocation = createAction('END_GET_USER_LOCATION');

handleActions

Before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const initLocationState = {
requestStatus: false,
lat: NaN,
lng: NaN
};

export function location( state= initLocationState, action) {
switch (action.type) {
case '@LOCATION/START_GET_LOCATION':
return {
...state,
requestStatus: false,
};
case '@FORECAST/END_GET_LOCATION':
return {
...state,
lat,
lng,
requestStatus: true,
};
default:
return state;
}
}

After:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { handleActions, combineActions } from 'redux-actions';

const initLocationState = {
requestStatus: false,
lat: NaN,
lng: NaN
};

export const location = handleActions({
GET_LOCATION: (state, action) => {
return {...state lat:action.payload.lat, lng: action.payload.lng}
},
GET_LOCATION_STATUS: (state, action) => {
return {...state, requestStatus: action.payload.requestStatus}
}
}, initLocationState);

Spread and Destructuring

但是我覺得action.payload.lat, action.payload.lng, res.coords.latitude很繁冗, 後來我利用了spread和解構的語法, 讓他看起來比較整潔:

Before:

1
2
3
4
5
6
7
8
export const location = handleActions({
GET_LOCATION: (state, action) => {
return {...state lat:action.payload.lat, lng: action.payload.lng}
},
GET_LOCATION_STATUS: (state, action) => {
return {...state, requestStatus: action.payload.requestStatus}
}
}, initLocationState);

After:

1
2
3
4
5
6
7
8
export const location = handleActions({  
GET_LOCATION: (state, action) => {
return {...state, ...action.payload}
},
GET_LOCATION_STATUS: (state, action) => {
return {...state, ...action.payload}
}
}, initLocationState);

combineActions:

再來我覺得原本handleActions那邊有點冗,

Before:

1
2
3
4
5
6
7
8
export const location = handleActions({
GET_LOCATION: (state, action) => {
return {...state, ...action.payload}
},
GET_LOCATION_STATUS: (state, action) => {
return {...state, ...action.payload}
}
}, initLocationState);

配合 Redux Actions 的 combineActions:

After:

1
2
3
4
5
export const location = handleActions({
[combineActions('GET_LOCATION', 'GET_LOCATION_STATUS')](state, action) {
return { ...state, ...action.payload}
}
}, initLocationState);

combineActions的小坑:

在官方的Api文檔範例中, 他使用的是return { …state, counter: state.counter + amount }; 讓人會誤以為, 只能單純為計算才能combineActions(還是只有我這樣覺得?). 而我發現只要在dispatch的時候, 用object({})的方式傳到reducer, 再把傳入的object使用spread action.payload, 他就會根據你之前的設定好的initState, 找出相應的key來覆蓋. 但是這邊有點問題: combineAction裡面的args會越來越多,變得超級無敵長, 真的很可怕, 除了多寫幾個handle actions來分隔以外, 還有其他方法嗎?

20180401 combineActions的理解錯誤

使用了以上的方法一段時間后,發現我什麽都能dispatch,甚至lat、lng的欄位,都能dispatch boolean值(預設值是數字),讓我維護變得更麻煩,更加難除錯。我發現了一個人,他寫了關於一系列相當清楚的教學,他不建議使用combineActions,畢竟很多時候每一個action都有自己的目的(不用到的幹嘛不簡化成一個XD),因此在某種程度上,combineActions是不被需要的。

下面將會新增利用這樣的概念寫好的code:

結論

簡單來說, Redux-Actions他簡化了一些模板code, 比方說action不用每一個都寫function, reducers不用寫switch case, 這樣的確少了很多麻煩. 今天的經驗分享就到這邊!

完整code

*20180401新增:


新版本的示範凑得請點我
todo-reducers.js

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
const initTodoState = {
todos: [],
todoLoading: false
}

export const todo = handleActions({
['SET_LIST_TODO']:
function(state, {payload}) {
console.log(payload);

return {
...state,
todos: payload
}
},
['SET_CREATE_TODO']:
function (state, {payload}) {
return state
},
['SET_CHECK_TODO']:
function (state, {paylaod}) {
return state
},
['FETCH_TODO_REQUEST']:
function (state, payload) {
return {
...state,
todoLoading: true
}
},
['FETCH_TODO_RESPONSE']:
function (state, {payload}) {
return {
...state,
todoLoading: false
}
}
}, initTodoState);


todo-actions.js

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
import { createAction, createActions } from 'redux-actions';

const {
// setlistTodo,
setCreateTodo,
setCheckTodo,
fetchTodoRequest,
fetchTodoResponse
} = createActions(
// 'SET_LIST_TODO',
'SET_CREATE_TODO',
'SET_CHECK_TODO',
'FETCH_TODO_REQUEST',
'FETCH_TODO_RESPONSE'
)

const setlistTodo = createAction('SET_LIST_TODO');

export function listTodo(searchText = '') {
return async (dispatch, getState) => {
dispatch(fetchTodoRequest());
try {
console.log(searchText);

const todos = await listTodoFromApi(searchText);
console.log(todos);

dispatch(setlistTodo(todos));
dispatch(fetchTodoResponse());
return todos;
} catch (error) {
console.error('Error getting todo', error);
};
}
}

export function createTodo(mood, text) {
return async (dispatch, getState) => {
dispatch(fetchTodoRequest());
try {
await createTodoFromApi(mood, text);
dispatch(setCreateTodo());
await dispatch(listTodo());
dispatch(fetchTodoResponse());
} catch (error) {
console.error('Error creating todo', error);
}
}
}

export function checkTodo(id) {
return async (dispatch, getState) => {
dispatch(fetchTodoRequest());
try {
await checkTodoFromApi(id);
dispatch(setCheckTodo());
await dispatch(listTodo());
dispatch(fetchTodoResponse());
} catch (error) {

}
}
}



完整的code請點我
weather-reducers.js

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
import { handleActions, combineActions } from 'redux-actions';

const initLocationState = {
requestStatus: false,
lat: NaN,
lng: NaN
};


export const location = handleActions({
[combineActions('GET_LOCATION', 'GET_LOCATION_STATUS')](state, action) {
return { ...state, ...action.payload}
}
}, initLocationState);

const initWeatherState = {
city: 'na',
code: -1,
group:'na',
description: 'N/A',
temp: NaN,
weatherLoading: false,
masking: false,
lat: NaN,
lng: NaN,
};

export const weather = handleActions ({
[combineActions('START_GET_USER_LOCATION', 'END_GET_USER_LOCATION', 'GET_LOCATION_WEATHER', 'START_GET_WEATHER', 'END_GET_WEATHER', 'RESET_WEATHER', 'MASK_TODAY_BG', 'UNMASK_TODAY_BG')](state, action) {
console.log(`action.payload:`,action.payload);

return { ...state, ...action.payload, }
}
}, initWeatherState)


weather-actions.js

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
import { getUserLocation as getUserLocationFromApi } from 'component/userLocation.jsx';
import { getLocationWeatherToday as getLocationWeatherTodayFromApi } from 'Api/openWeatherMapApi.js';

/* Today */

const startGetWeather = createAction('START_GET_WEATHER');
const endGetWeather = createAction('END_GET_WEATHER');
const resetWeather = createAction('RESET_WEATHER');
const maskTodayBg = createAction('MASK_TODAY_BG');
const unmaskTodayBg = createAction('UNMASK_TODAY_BG');
const getUserLocation = createAction('GET_LOCATION');
const startGetUserLocation = createAction('START_GET_USER_LOCATION');
const endGetUserLocation = createAction('END_GET_USER_LOCATION');
const getWeatherLocation = createAction('GET_LOCATION_WEATHER');
const getWeatherLocationStatus = createAction('GET_LOCATION_WEATHER_STATUS');

export function getWeather(city, unit) {
return (dispatch, getState) => {
dispatch(startGetWeather({city, unit}));
dispatch(maskTodayBg({masking: true}));

return getWeatherFromApi(city, unit).then(weather =>{
console.log(weather);
const {city, code, group , description, temp, unit} = weather;
dispatch(endGetWeather({city, code, group, description, temp}));
dispatch(setUnit(unit));
}).then( () => {
setTimeout(() => {
dispatch(unmaskTodayBg({masking: false}));
}, 600);
}).catch(err => {
console.error('Error getting weather', err);
dispatch(resetWeather());
setTimeout(() => {
dispatch(unmaskTodayBg({masking: false}));
}, 600);
});
};
};

/**
* location
*/

export const getLocationWeather = function(unit){
return async (dispatch, getState) => {
dispatch(startGetUserLocation({requestStatus: true}));
dispatch(maskTodayBg({masking: true}));

try {
const res = await getUserLocationFromApi();
const {latitude: lat, longitude: lng} = res.coords;
console.log(lat, lng);
dispatch(getUserLocation({lat, lng}));

const weather = await getLocationWeatherTodayFromApi(lat, lng, unit);
console.log(weather);
const {city, code, group , description, temp} = weather;
dispatch(getWeatherLocation({city, code, group , description, temp}));

dispatch(endGetUserLocation({requestStatus: false}));
setTimeout(() => {
dispatch(unmaskTodayBg({masking: false}));
}, 600);
} catch (err) {
console.log('Error getting User Location', err);
setTimeout(() => {
dispatch(unmaskTodayBg({masking: false}));
}, 600);
// TODO: some reset action?
}
}
}


參考資料

  1. https://github.com/dc198689/orange/blob/master/src/actions/posts/getPosts.js
  2. https://github.com/ecmadao/Coding-Guide/blob/master/Notes/React/Redux/%E4%BD%BF%E7%94%A8%20Redux%20%E6%89%93%E9%80%A0%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8%20%E2%80%94%E2%80%94%20redux-actions.md
  3. https://redux-actions.js.org/docs/introduction/Tutorial.html
  4. https://ithelp.ithome.com.tw/articles/10187009
  5. https://www.jianshu.com/p/6ba5cd795077

近期工作心得小結

發表於 2017-12-10 | 更新於 2018-06-10

前言

找了新工作後,進入一陣蜜月期,開始接手專案。這邊先說一下,我目前在一家報社當前端工程師,雖然說職稱很怪,但是做的東西的確是前端的內容。這邊的性質是接案型,前端的主管會委派你負責一個案子,主管前期會和你一起開會,之後就全權給你負責了。通常在一個案子,會有一個後端、設計、PM的人一起負責,而之後就要和他們協調可行性與技術問題,有點像是大學時期分組報告的感覺。由於前端完成時間只有一個禮拜,基本上架構並沒有很好,而且也沒有用很多有效率的工具,因此在這邊記錄,希望下次不要犯下面提到的錯誤。

工具

我自己用的是vscode直接切版,css都是全手工,這樣會讓我的css結構鬆散,到了開發後期非常難維護,幸好這個是活動網頁,要不然我會把自己殺死哈哈。開發之後需要各種文件的壓縮,像是HTML JS CSS的檔案,但是到了這邊我有點糾結,公司的人都在用gulp,但是感覺Webpack很潮,還能async loading,react也會用到。但後來決定先學著gulp,再來回頭學Webpack。總結了一下,下次需要用到的工具:

  1. stylus
  2. gulp
    2.1 HTML JS CSS壓縮
    npm install gulp gulp-html-replace gulp-minify-html gulp-minify-css gulp-uglify gulp-concat gulp-rename -save-dev
    2.2 圖片的壓縮
    npm install –save-dev gulp-imagemin
    2.3 SSI(Server Side Includes)
    這個能夠讓你一些重複的模板集中起來管理,更加方便開發,不需要每一個地方逐一修改
    npm install browsersync-ssi

架構

當初主管和pm討論出來的結果是做一個entry page,再加上面六個幾乎一樣的活動頁面,和一個結束頁面,但是後來開發讓我覺得,這樣其實開發非常難維護,雖然後來用了SSI讓維護的工作稍微減輕,但是想到其實能用React做會更快。只要把我要的Header寫好,把不同的圖片當成參數傳進去當props,就能很快的維護了。但是後來想到這個活動會連動到後台的驗證,需要把全部換成JSP,應要開一個api驗證的話,會很浪費大家的時間。只能等下次有機會,練習用框架寫。

溝通

後端雖然很資深,但是技術能力偏弱,甚至活動頁面要驗證的事情,也是我到最後提醒才想起(這段是鳥事,唉),和能力無法成正比,因此發現留意到這樣的問題,就要先幫忙想,要不然之後要幫忙收爛攤子。

一開始拿到設計圖後,沒有和設計和PM確認使用流程,導致後來要更改版型,甚至發現遺漏頁面。

結論

溝通不足,缺少工具的運用,希望下次不要遇到這樣的問題了!

Windows 10上整合Vs code的Oh-my-zsh(20180119更新)

發表於 2017-11-08 | 更新於 2018-06-10

前言

看到線上讀書會的有一個講者用的Terminal[1],發現原來Terminal有那麼方便的功能,讓我立馬生火XD 花了一點時間研究怎麼安裝,踩了一下雷並在這邊分享心得。說到踩雷,我發現關於Oh-my-zsh繁體中文的心得分享資源,非常的少啊,於是激起我想要寫這篇文章的動力。

流程

啟動Linux子系統 -> 安裝Zsh -> Oh-my-zsh安裝與設定 -> Vs code整合設定 ->結束!

#啟動Linux子系統

如果你沒有進行秋季創作者更新(Fall Creators Update)的話,請你跟著我的做法:

  • 20180119更新:Windows已經正式開始推送更新了,有空我再更新有更新的做法

設定 -> 更新與安全性 -> 開發人員專用 -> 點擊“開發人員模式”

開啟CMD或者Powershell,輸入OptionalFeatures.exe,跳出來的目錄下選擇“適用於Linux的Windows子系統(搶先版(Beta))”,並重啟電腦。電腦重啟後,打開CMD或者Powershell,輸入bash就能開始安裝Linux子系統。

安裝Zsh

在安裝Zsh之前,要在Bash環境下設定:

安裝 cURL
sudo apt-get install curl

安裝 git
sudo apt-add-repository ppa:git-core/ppa sudo apt-get update sudo apt-get install git

最後安裝 Zsh
sudo apt-get install zsh

安裝完畢之後,你可以先在bash裡面先試試看輸入zsh,如果他變成比較短的開頭,代表你成功安裝zsh!

Oh-my-zsh安裝與設定

這邊提醒大家,

一切的改動請在Terminal完成!![2]
一切的改動請在Terminal完成!!
一切的改動請在Terminal完成!!

千萬不要在其他的Windows編輯器像是Sublime Notepad Vscode等來編輯bashrc、zshrc等文檔
做錯的話就要前功盡棄了!

安裝Oh-my-zsh
curl -L https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash

複製設定檔,通常在安裝zsh之後,需要我們自行設置~/.zshrc,這邊為了防止出錯,直接先套用Oh-my-zsh的預設模板檔案:
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

目前來說,你的Oh-my-zsh已經成功安裝了!如果要啟動他,你要先打開Powershell -> bash -> zsh,但是這樣還是不夠方便,下面將預設開啟bash將會直接打開zsh:

在Bash Terminal輸入
nano ~/.bashrc

在Terminal將會跳出.bashrc的編輯畫面,在第一段與第二段的#號段落之間,輸入:
bash -c zsh

建議直接手打,減少前功盡棄的機會。範本參考鏈接

再次提醒,上面的段落,
不要用Vscode、Sublime Notepad等編輯器編輯!!
不要用Vscode、Sublime Notepad等編輯器編輯!!
不要用Vscode、Sublime Notepad等編輯器編輯!!

可選的設定

主題字型

因為Oh-my-zsh的主題大部分都用Powerline字型,所以我會推薦大家安裝,這邊的環境不需要用bash,一般的powershell或者cmd就好了,目錄在隨便一個都可以(照指示安裝後會刪除檔案)

git clone https://github.com/powerline/fonts.git --depth=1 cd fonts ./install.sh cd .. rm -rf fonts

Random主題

除此之外我也會讓zsh的主題random,在Bash環境下:

nano ~/.zshrc

如果有成功把Oh-my-zsh的設定模板複製,就能看到文檔中會有ZSH_THEME,把他改成
ZSH_THEME="random"
再按Ctrl+X,按保存

如果有一天random到你喜歡的主題,可以輸入這個指令查看:
print $RANDOM_THEME
到時候就能自己固定你喜歡的主題

安裝插件

和上面一樣,在Bash環境下:

nano ~/.zshrc

一直按向下鍵,找到沒有#號的plugins,理論上一開始括號裡面會有git,你可以參考我的設定:
plugins=(git wd autojump zsh-syntax-highlighting zsh-autosuggestions)

Vs code整合設定

到了這邊,其實你就能好好使用Oh-my-zsh了,但是我習慣在Vs code上使用Terminal,因此另外加上設置:

Vs code -> 檔案 -> 喜好設定 -> 設定

就會開出你原本的設定Json檔,在右邊的下面加上

"terminal.integrated.shell.windows": "C:\\Windows\\sysnative\\bash.exe", "terminal.integrated.shellArgs.windows": ["-c", "zsh"] "terminal.integrated.fontFamily": "Anonymous Pro for Powerline",

如果你用的是Vs code 64bit版本,請改成

"terminal.integrated.shell.windows": "C:\\Windows\\sysnative\\bash.exe", "terminal.integrated.shellArgs.windows": ["-c", "zsh"] "terminal.integrated.fontFamily": "Anonymous Pro for Powerline",

記得要在前面加上逗號哦!不過後來我發現字體還是有點怪怪的,便移除了最後一行fontFamily的設定
之後在你的文件下,按Ctrl+`就能開啟你文件目錄下的Terminal了!

設定自己喜歡的主題(2018/01/19)

自己用了一段時期,發現BUllet Train的顏色很文青很療愈,就沒有換主題了XD 在這邊分享一下踩坑經過:
https://github.com/caiogondim/bullet-train.zsh/issues/252
基本上設定完後,發現自己Terminal的背景怪怪的,原來是還要加上一些設定,在.zshrc要加上這個:

BULLETTRAIN_CONTEXT_BG=red

顏色隨你喜歡,儲存後就大功告成了!

其他

如果你遇到Input/output error,個人的建議是,你把Bash刪除再重新安裝問題參考鏈接參考鏈接

參考連結:

  1. https://youtu.be/rsaqfyDLzQM?t=1m13s
  2. https://blogs.msdn.microsoft.com/commandline/2016/11/17/do-not-change-linux-files-using-windows-apps-and-tools/
  3. https://gist.github.com/evdokimovm/67e4fcd938af98528aa108574626e522#file-bashrc-L5
  4. http://www.jianshu.com/p/fe244b1c7737
  5. https://github.com/robbyrussell/oh-my-zsh/issues/5725
  6. https://www.jazz321254.com/visual-studio-code-zsh/
  7. https://www.zhihu.com/question/49284484
  8. https://winsmarts.com/run-oh-my-zsh-as-integrated-shell-in-vscode-on-windows-7d69f72bafa3
  9. https://evdokimovm.github.io/windows/zsh/shell/syntax/highlighting/ohmyzsh/hyper/terminal/2017/02/24/how-to-install-zsh-and-oh-my-zsh-on-windows-10.html
  10. https://github.com/robbyrussell/oh-my-zsh#manual-installation
  11. http://icarus4.logdown.com/posts/177661-from-bash-to-zsh-setup-tips
  12. https://github.com/Microsoft/WSL/issues/904

Windows 10上整合Vs code的Oh-my-zsh

發表於 2017-11-08 | 更新於 2018-10-08

2018/10更新:

Windows 10新版本,已無需啓動Linux子系統,另外下面新增字體、主題安裝與Nodejs版本提升。

前言

看到線上讀書會的有一個講者用的Terminal[1],發現原來Terminal有那麼方便的功能,讓我立馬生火XD 花了一點時間研究怎麼安裝,踩了一下雷並在這邊分享心得。說到踩雷,我發現關於Oh-my-zsh繁體中文的心得分享資源,非常的少啊,於是激起我想要寫這篇文章的動力。

流程

啟動Linux子系統 -> 安裝Zsh -> Oh-my-zsh安裝與設定 -> Vs code整合設定 ->結束!

啟動Linux子系統

如果你沒有進行秋季創作者更新(Fall Creators Update)的話,請你跟著我的做法:

設定 -> 更新與安全性 -> 開發人員專用 -> 點擊“開發人員模式”

開啟CMD或者Powershell,輸入OptionalFeatures.exe,跳出來的目錄下選擇“適用於Linux的Windows子系統(搶先版(Beta))”,並重啟電腦。電腦重啟後,打開CMD或者Powershell,輸入bash就能開始安裝Linux子系統。

2018/10 月更新:

目前更新到最新版本的windows後,不需要這樣特地開啓,只要到Microsoft Store搜尋『Ubuntu』下載安裝,下載後開啓按照他的指示安裝就行了。

安裝Zsh

在安裝Zsh之前,要在Bash環境下設定:

安裝 cURL
sudo apt-get install curl

安裝 git
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

最後安裝 Zsh
sudo apt-get install zsh

安裝完畢之後,你可以先在bash裡面先試試看輸入zsh,如果他變成比較短的開頭,代表你成功安裝zsh!

Oh-my-zsh安裝與設定

這邊提醒大家,

一切的改動請在Terminal完成!![2]
一切的改動請在Terminal完成!!
一切的改動請在Terminal完成!!

千萬不要在其他的Windows編輯器像是Sublime Notepad Vscode等來編輯bashrc、zshrc等文檔
做錯的話就要前功盡棄了!

安裝Oh-my-zsh
curl -L https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash

複製設定檔,通常在安裝zsh之後,需要我們自行設置~/.zshrc,這邊為了防止出錯,直接先套用Oh-my-zsh的預設模板檔案:
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

目前來說,你的Oh-my-zsh已經成功安裝了!如果要啟動他,你要先打開Powershell -> bash -> zsh,但是這樣還是不夠方便,下面將預設開啟bash將會直接打開zsh:

在Bash Terminal輸入
nano ~/.bashrc

在Terminal將會跳出.bashrc的編輯畫面,在第一段與第二段的#號段落之間,輸入:
bash -c zsh

建議直接手打,減少前功盡棄的機會。範本參考鏈接

再次提醒,上面的段落,
不要用Vscode、Sublime Notepad等編輯器編輯!!
不要用Vscode、Sublime Notepad等編輯器編輯!!
不要用Vscode、Sublime Notepad等編輯器編輯!!

可選的設定

主題字型

因為Oh-my-zsh的主題大部分都用Powerline字型,所以我會推薦大家安裝,這邊的環境不需要用bash,一般的powershell或者cmd就好了,目錄在隨便一個都可以(照指示安裝後會刪除檔案)

git clone https://github.com/powerline/fonts.git --depth=1
cd fonts
./install.sh
cd ..
rm -rf fonts

2018/10 月更新:

目前我會選擇我想要的字體,下載ttf檔案,直接用window安裝。
設置字體:開啓Ubuntu -> 點擊左上角Icon -> Properties -> Font -> 安裝好的檔案。
如果找不到的話請嘗試重啓和重裝。

Random主題

除此之外我也會讓zsh的主題random,在Bash環境下:

nano ~/.zshrc

如果有成功把Oh-my-zsh的設定模板複製,就能看到文檔中會有ZSH_THEME,把他改成
ZSH_THEME="random"
再按Ctrl+X,按保存

如果有一天random到你喜歡的主題,可以輸入這個指令查看:
print $RANDOM_THEME
到時候就能自己固定你喜歡的主題

2018/10 月更新:

目前會固定使用bullet-train,配合的字型爲Menlo for Powerline,推薦大家使用,大家可以試試看XD

安裝插件

和上面一樣,在Bash環境下:

nano ~/.zshrc

一直按向下鍵,找到沒有#號的plugins,理論上一開始括號裡面會有git,你可以參考我的設定:
plugins=(git wd autojump zsh-syntax-highlighting zsh-autosuggestions)

Vs code整合設定

到了這邊,其實你就能好好使用Oh-my-zsh了,但是我習慣在Vs code上使用Terminal,因此另外加上設置:

Vs code -> 檔案 -> 喜好設定 -> 設定

就會開出你原本的設定Json檔,在右邊的下面加上

"terminal.integrated.shell.windows": "C:\\Windows\\sysnative\\bash.exe",
"terminal.integrated.shellArgs.windows": ["-c", "zsh"]
"terminal.integrated.fontFamily": "Anonymous Pro for Powerline",

如果你用的是Vs code 64bit版本,請改成

"terminal.integrated.shell.windows": "C:\\Windows\\sysnative\\bash.exe",
"terminal.integrated.shellArgs.windows": ["-c", "zsh"]
"terminal.integrated.fontFamily": "Anonymous Pro for Powerline",

記得要在前面加上逗號哦!不過後來我發現字體還是有點怪怪的,便移除了最後一行fontFamily的設定
之後在你的文件下,按Ctrl+`就能開啟你文件目錄下的Terminal了!

安裝npm

之前在Windows下安裝的npm是無法被Linux的Bash抓到,因此要另外在Bash安裝npm:

curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo npm install -g npm
npm --version

並在~/.zshrc第一段的#號段落下,加上這些:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

障礙排除

如果你遇到Input/output error,個人的建議是,你把Ubuntu刪除再重新安裝問題參考鏈接參考鏈接

參考連結:

  1. https://youtu.be/rsaqfyDLzQM?t=1m13s
  2. https://blogs.msdn.microsoft.com/commandline/2016/11/17/do-not-change-linux-files-using-windows-apps-and-tools/
  3. https://gist.github.com/evdokimovm/67e4fcd938af98528aa108574626e522#file-bashrc-L5
  4. http://www.jianshu.com/p/fe244b1c7737
  5. https://github.com/robbyrussell/oh-my-zsh/issues/5725
  6. https://www.jazz321254.com/visual-studio-code-zsh/
  7. https://www.zhihu.com/question/49284484
  8. https://winsmarts.com/run-oh-my-zsh-as-integrated-shell-in-vscode-on-windows-7d69f72bafa3
  9. https://evdokimovm.github.io/windows/zsh/shell/syntax/highlighting/ohmyzsh/hyper/terminal/2017/02/24/how-to-install-zsh-and-oh-my-zsh-on-windows-10.html
  10. https://github.com/robbyrussell/oh-my-zsh#manual-installation
  11. http://icarus4.logdown.com/posts/177661-from-bash-to-zsh-setup-tips
  12. https://github.com/Microsoft/WSL/issues/904
  13. https://blog.miniasp.com/post/2018/06/15/My-Windows-Subsystem-for-Linux-WSL-Setup-2018.aspx
12

nereuseng

這是我記錄我程式能力成長的地方,目前集中在React, JavaScript等,歡迎來交流!

16 文章
1 標籤
© 2018 nereuseng
由 Hexo 強力驅動 v3.4.4
|
主題 — NexT.Muse v6.3.0