2021年5月4日

從找衣服了解時間複雜度(Time Complexity)

剛剛用日常上班前挑衣服的例子和沒學過程式的 00 說明時間複雜度的概念很好理解耶~!
例子是這樣的...
一早要出門的時候,想要從衣櫃中找出紅色的上衣。
其中一種方式是像左圖一樣,這是掏寶上很熱門的「疊衣服褲子收納神器」,雖然看起來整理的很乾淨,但如果你要從中找到紅色的衣服,你就得要由上而下一件一件找,最糟的情況就是一直翻到最下面才能找到你要的紅色衣服。
另一種方式是像右圖一樣,把衣服用立起來的方式,一眼就可以看到紅色的衣服在哪,直接拿出來,幾乎不用找。
左圖的那種方式,時間複雜的就是 O(n),n 就是衣服的件數,雖然紅色的衣服有可能就放在最上面,一眼就可以看到,但在探討時間複雜度的時候都要考慮最差的情況,所以如果你有 n 件衣服,最差的情況就是要把 n 件衣服都翻過才會找到紅色那件。
右圖的方式它的時間複雜度是 O(1),在你沒有忘記其實衣服已經被丟到洗衣籃的前提下,你看一眼,翻都不用翻就可以把紅衣服直接取出(請先忽略掉人腦內建的視覺搜尋系統,那是另一個有趣的故事 XD)。這種不用一個一個找,就直接取出的,時間複雜度就是 O(1)
有了這個時間複雜度的概念後,是不是覺得左邊的那個商品實用性沒這麼高啦~ XDD
但我還是附一下購物連結(誤)
真的是沒想到學演算法還可以用在購物吧!

圖片來源

2021年4月26日

[心得] 2021 求職面試心得分享

由於過去求職時在 ptt 上或許多個人網誌中獲得了許多幫助,因此這次也來分享自己面試的心得,希望對於求職的大家們能夠有些幫助。
這次求職過程中,在和幾位不同的 Team Lead 或是 CTO 面談的過程中,真的讓我感受到多數厲害的人總是自信而謙虛的,他們不會透過問題來讓你覺得自己不懂,反倒是很 open-minded 讓你感受到雖然這個自己現在不懂,但沒關係,甚至會進一步透過提問來協助你進一步釐清自己的思路。
同樣地,我也期許對於自己的專業能夠是「自信而謙虛」的態度。
過去雖然常聽大神說,工作一陣子後,通常就不用自己找工作,而是靠別人介紹或挖角,但我可能還沒到這個階段,周圍沒什麼人介紹,更別說是挖角,所以還是只能靠自己 XDD。
下面是我這次有面試的幾間公司,主要找公司市場不侷限於台灣的公司,面試的職缺全部都是前端工程師。

Line Pay

首先 Line Pay 和 Line Taiwan、Line Bank 雖然都隸屬於 Line 集團底下,但在台灣是三間不同的公司。Line Pay 的面試過程較嚴謹,這次從投遞履歷到最終回覆的時間約需要一個多月的時間。
Line Pay 公司的地點是在大直美福大飯店的側邊,給人非常氣派豪華的感覺。

第零關:線上測試

給予連結,並透過線上測試的方式,題目主要是演算法的測驗,印象中是三題。

第一關:onsite-interview with Taiwan Engineer

面試的對象是 Taiwan Line Pay 的工程師們,會針對線上測試的作答進行討論,接著會根據過去實作過的專案進行問答,並可利用白板進行概念的解釋與說明。

第二關:onsite-interview with Korean Engineer

這關給我的經驗很特別,因為是透過視訊的方式和韓國的工程師們進行面試,原本以為會需要用英文會話,但面談現場直接就有一位中韓文的即時口譯,所以並不需要說到英文,和面試官的溝通會完全透過這位翻譯。(心裡 OS:大公司就是直接找翻譯這樣的氣派。)

第三關:onsite-interview with HR

最後會和 HR 進行面談,除了討論期待的薪資,也會針對個人或過去的工作經驗進行暸解。據 HR 表示,目前 Line 和 Line Bank 都搬到同一棟建築物,但 Line Pay 因為剛搬到美福大飯店這邊不久,因此暫時沒有再次搬遷的打算。
最後,HR 會與你進行基本的英文會話,確認有基本英文溝通能力。

KKStream

KKStream 則是隸屬於 KKBOX Group 的公司,做的是影音串流服務,可以想成是讓客戶能夠透過 KKStream 的服務建立 Netflix 或 MyVideo 這類影音平台。

第零關:線上測試

給予連結,並透過線上測試的方式,題目主要是演算法的測驗。

第一關:onsite-interview with Team Lead

完成線上測驗通過後,會有一份作業,作業內容是用 React 寫一個待有基本的 CRUD 以及搜尋功能的網站(可以簡單想成 TodoList 這類),作業需在此次面試前完成,並提交到 Github 私人的 repo。
KKStream 前端目前分成三個組別-Core Tech、BlendVision 和 Enterprise。各組各派一人前來面試,會針對作業內容進行討論,接著則根據過去開發過的專案進行討論。

第二關:onsite-interview with PM & Engineer Manager

第一關結束後,會根據各組人數的需求將面試者配到適合的組別,也就是一開始投的組別,不見得會是最後的組別,這個部分也可以再和 HR 或 Engineer Manager 進行了解。
PM 和 Engineer Manager 比較不是針對技術的部分進行發問,而是針對過去的經驗試著了解自己是個怎麼樣的人。在這次面試中和 Engineer Manager 聊了蠻久的時間,包括帶領 Team 的方式、對於前後端的想法、測試撰寫的想法等等,覺得有非常多的收穫。

第三關:online-interview

HR 會針對期待薪資進行了解,並試著了解自己過去的經歷。另外,會與一位主管進行面談,過程比較像是在聊天,互相分享彼此的經驗和價值觀。

OneDegree

OneDegree 的前端工程師還有分不同 team,分別是做 2C 和 2B,這裡我是面試 2B 的團隊。OneDegree 主要是開發保險系統,讓保險公司能透過此保險系統建立保險商品,並供一般消費者能夠以網路進行線上投保。

第零關:線上測試

給予連結,並透過線上測試的方式,題目包含演算法、React 和 Git 的問答題。

第一關:onsite-interview with Team Lead

主要是與 Frontend Team Lead 們進行面試,一開始會先請面試者以英文自我介紹,並且透過英文進行簡短的問答,主要也是確認面試者有基本的英文能力。接著會切換回中文,同樣是根據過去做的專案進行討論,並且分享彼此對於不同技術上的想法。

第二關:onsite-interview with Taiwan Director & HR

再來會與 OneDegree 台灣區的總監和 HR 進行面談,這次面談比較不會談到技術上的問題,比較像是互相了解彼此的聊天。

總結

OneDegree 的回應速度還蠻快的,對於面試者來說不會有太長時間的等待。

Privé Technologies

Privé Technologies 的 recruiter 主動聯繫,Privé Technologies 是一間立基於香港的 Fintech,工程師遍佈在世界不同的地方,目前工程師主力是在香港和台灣。

第零關:線上測試

給予連結,並透過線上測試的方式,題目主要是前端 JavaScript / React 的測試。

第一關:online-interview with Frontend Team Lead

透過 Online 的方式與位在香港的 Frontend Team Lead 進行面談,過程全程使用英語。主要是針對過去的專案進行提問,也有詢問到對撰寫測試的想法,並討論「怎麼樣算是好的程式碼」?

第二關:online-interview with Frontend Engineer

一樣是透過 online 的方式進行面談,面試官是在台灣的前端工程師(很巧的是過去和他還有短期合作過相同的專案)。一開始一樣會以英文進行自我介紹和簡短的問答,確認面試者有足夠的英文會話能力。接著會切換回中文,討論「怎麼樣算是好的程式碼」、還有聊到 OOP 和 Functional Programming 適合的時機、另外則是 JavaScript 有關的題目。
另外,也有進行 online 的 coding test,內容偏向基本的演算法和邏輯實作。

第三關:online-interview with CTO

印象沒錯的話 CTO 是澳洲人,但在香港待了蠻久的一段時間,目前和 Frontend Team Lead 一樣都在香港,他也曾在 LaLaMove 擔任過 CTO 的職位。
面試全程以英文進行,一樣會討論到「怎麼樣算是好的程式碼」,另外則是聊一些個人的經歷、和同事的相處、人格特質等等的。

總結

Privé Technologies 是回覆相當快速的公司,收到回覆後會立即安排下一場的時間,不論是 HR、Team Lead 到 CTO 都給人很和善親切的態度,可以讓人感受到是相當尊重且重視面試者的。

慧科訊業 Wisers

慧科是由 Headhunter 推薦,公司主要是做輿情分析的,會去爬各媒體或社群的資料、關鍵字,以此分析當前熱門的議題或輿論風向,市場主要是在中國。

第一關:online-interview

第一關會先以線上的方式進行面談,面試官來自台灣、香港和中國。這裡我有一點小烏龍的是,收到通知的時候,看到 email 寫的是「phone interview」,誤以為是面試官會打電話來...,接著等了又等,想說時間到了怎麼都沒打電話來,後來才知道 phone interview 指的是視訊面談 XDD
面試主要問過去實作過哪些專案,有沒有處理過複雜的圖表或大量資料需要 render 的經驗,怎麼樣優化和除錯等等。

第二關:onsite-interview

雖然說是 onsite-interview,但除了和台灣的工程師面試之外,同時也會透過視訊和香港以及中國的主管面談。中國的 Frontend 主管問了很多技術相關的問題,從 CSSOM、Web Component、micro-frontend、performance、如何避免瀏覽器被阻塞(block)都有問到。

總結

可以感覺的出來 Frontend Team Lead 的知識深度很深,我也並沒有全部都回答得出來,但 Team Lead 人非常有耐心,就像個 mentor 一樣,會給我一些思路讓我再去思考這個問題有沒有其他的可能性或答案,面試完後真的有一點「如沐春風」的感覺不誇張 XD。

常見問題

如果需要準備英文面試的話,也很推薦 Coursera 上這堂免費的課程 English for Career Development,若時間不夠的話,也可以根據自己的需要,直接跳著進度看,不用從頭慢慢看。
從技術面來說,JavaScript 面試的內容除了可以參考很久以前整理過的「JavaScript: Understanding the Weird Part(JavaScript 全攻略:克服JS 的奇怪部分)」筆記之外,網路上也可以搜尋到非常非常多;React 的話,基本的官方文件一定要看過。
有些東西雖然每天都在用,但若沒準備一時被問到的話,還是可能會沒辦法很快速且順暢的解釋出來。例如,請你解釋 event loop 是什麼。
在上述的面試中,撇除技術問題外,有一些共同常被問的,像是:
  • 自我介紹
  • 為什麼離開前一份工作
  • 上一份工作中覺得最困難或最具挑戰的是什麼?
  • 想像未來三年後你預期自己會是個什麼樣的人?
  • 有什麼問題要問我的嗎?
另外,也可以參考這部 3 分鐘的影片,裡面許多題目大家一定都聽過,但還是可以稍微想一下怎麼回答:

2021年4月20日

[心得] ALPHA Camp X 天下雜誌:那些年我們一起走過的數位轉型

ALPHACamp X 天下雜誌:那些年我們一起走過的數位轉型
這次很幸運有機會能夠擔任 ALPHA Camp 與天下雜誌合作舉辦的實體/線上活動,也是我自己第一次擔任活動主持人。
從小,我家就非常多與天下相關的雜誌,不論是天下、康健、Cheers 都有,因為我爸媽算是很早期的訂戶,所以一開始收到 ALPHA Camp 詢問能否的擔任活動主持人時,我內心是有些興奮的,是一種竟然可以有機會進到從小就一直在接觸的媒體的雀躍感,而且天下雜誌多數時候也給我相當正面的感受,算是台灣媒體界的清流之一。
除了天下雜誌相當知名的媒體之外,我也很好奇工程師在當今的媒體產業中究竟扮演了什麼角色。主力是在開發「內容管理系統」?或多在進行資料視覺化的專案?
在這次的活動中很幸運能夠聽到已經在天下雜誌 10 多年的資深產品經理-紹謙,和數位轉型過程中加入團隊,負責從專案發想到實際落地的資深工程經理-世彥,一起來聽他們分享天下雜誌的數位轉型。
ALPHACamp X 天下雜誌:那些年我們一起走過的數位轉型

數位轉型是什麼?

在聽演講前,其實我完全不知道「數位轉型」究竟是怎麼一回事,我單純地以為就是把紙本書變成電子書,或是推出幾個 App 後,就算是完成了數位轉型?

數位轉型不是事件的完成,而是持續且沒有終點的歷程

然而,在聽完兩位講者的分享後,我深切感受到數位轉型絕對不是「做完了 OO」就叫完成了數位轉型這麼簡單,並非把紙本雜誌電子化、推出幾個手機 App、開發幾個 Web 專案就叫完成了數位轉型。數位轉型的過程相當複雜,牽涉到的不只是使用到的技術、文章撰寫的工具、還包括整個組織架構的調整。
最早天下雜誌完全是紙本作業、編輯是以寫稿紙作業。過去上稿流程是先有紙本文章然後把文章變成電子檔後放在網路上,現在則轉變成線上文章先推出後,才有實際紙本內容;過去團隊中大部分都是編輯,現在則多了產品經理、軟體工程師、數據分析師等各種角色加入團隊;過去沒辦法從使用者的操作中取得大量的資料,現在則可以透過使用者使用 App 或閱讀文章等資料作為反饋,來協助指導後續的決策和產品修正。

數位轉型是不怕面對改變,敢於挑戰不熟悉的事物

如果要我說數位轉型是什麼?我認爲就是不怕潮流的變化,努力學習讓自己能夠跟上最新的事物,不怕面對自己不熟悉的事物,願意不斷嘗試與接受挑戰。這就好像一個永遠不會老的人,不斷地透過新陳代謝讓自己的身體和腦袋維持在年輕的狀態。
天下雜誌並沒有在完成了雜誌電子化,推出手機 App、建立網路內容平台後,就因為覺得「完成了數位轉型」而停下來。在近年 Youtube 的普遍和 Podcast 的竄起,天下雜誌也都沒有缺席,推出了 Youtube 頻道和 Podcast 節目,繼續跟著數位的潮流前進。因為數位轉型是一種不怕面對改變,敢於挑戰不熟悉事物的精神,因此它是沒有什麼叫做「已經完成的」

有價值的工程師

在這場活動中,兩位講者也分享到他們認為什麼樣的工程師是有價值的。

先釐清問題,再提出更多可能的解決方式

其中一點是當 PM 提出一個需求(或功能)時,好的工程師會試著向 PM 去了解這個需求(或功能)是想要解決什麼問題,先把實際想要解決的問題釐清,接著再和 PM 討論如果是想要解決這個問題的話,有哪些可行的做法,因為 PM 一開始提的功能或許只是其中一種解決方式,但工程師在釐清問題後,會多了工程面可行性的評估,進而有機會提出不同的解決方式供 PM 參考與選擇。

對於開發的產品有 Ownership

另外一個特質則是對產品有強烈的 Ownership,工程師不單純只是把 PM 所交付的功能完成,同時他也喜歡自己完成的產品、會好奇使用者使用時的體驗、會去思考怎麼樣能把這個產品做得更好。

不怕面對改變,勇於嘗試新事物、敢於挑戰自己的態度

作為軟體工程師,總是有新的技術、沒用過的工具、沒聽過的詞彙,但許多時候我也會怠惰,覺得學新的好累、現有的東西就夠用了吧?聽完這場活動後,我最大的反思在於,我想有價值的工程師絕對不止是學會了某些技術或工具後,就成為了一個優秀的工程師,反倒是一種態度或精神-一種不怕面對改變,勇於嘗試新事物的精神;一種不怕碰到沒解過的問題,敢於挑戰自己的態度,而這點也正好呼應了這場活動談到的「數位轉型」。

總結

很開心初次主持活動就能和天下雜誌合作,而且又能夠聽到兩位非常有經驗的產品經理與工程經理進行分享。
ALPHACamp X 天下雜誌:那些年我們一起走過的數位轉型
ALPHACamp X 天下雜誌:那些年我們一起走過的數位轉型

2021年3月26日

gRPC 是什麼?以 Golang 進行示範與說明

gRPC 說明影片 @ BESG

:::tip source code
對應的程式碼可檢視 besg-grpc 的 repository。
:::

gRPC 是什麼:以 Golang 說明與實作

RPC 的全名是 remote procedure call,主要是作為電腦和電腦間溝通使用。A 電腦可以呼叫 B 電腦執行某些程式,B 電腦會將結果回傳給 A 電腦,A 電腦在收到回應後會再繼續處理其他任務。RPC 的好處在於,雖然 A 電腦是發送請求去請 B 電腦做事,但其呼叫的方式,就很像是 A 電腦直接在呼叫自己內部的函式一般。
gRPC 也是基於這樣的概念,讓想要呼叫 server 處理請求的 client,在使用這支 API 時就好像是呼叫自己內部的函式一樣簡單自然。從功能面來說,gRPC 就像 Web 常用的 Restful API 一樣,都是在處理請求和回應,並且進行資料交換,但 gRPC 還多了其他的功能和特色。
gRPC 是由 Google 開發的開源框架,它快速有效、奠基在 HTTP/2 上提供低延遲(low latency),支援串流,更容易做到權限驗證(authentication)。在下面的文章中,將會對於 gRPC 能提供的特色有更多說明。

Protocol Buffers 是什麼

在學習 gRPC 時,需要同時了解什麼是 Protocol Buffers。在傳統的 Restful API 中,最常使用的資料交換格式通常是 JSON;但到了 gRPC 中,資料交換的格式則是使用名為 Protocol Buffers 的規範/語言。
Protocol Buffers vs JSON
也就是說,當我們想要使用 gRPC 的服務來交換資料前,必須先把資料「格式」和「方法」都定義清楚。
:::tip
使用 gRPC 前,不只需要先把資料交換的格式定義清楚,同時也需要把資料交換的方法定義清楚。
:::
這裡要稍微釐清一點很重要的是,Protocol Buffers 可以獨立使用,不一定要搭配 gRPC;但使用 gRPC 一定要搭配 Protocol Buffers

實作將 Protocol Buffers 編譯成在 Golang 中可使用的檔案

對應的程式碼可檢視 besg-grpc repository 中的 proto 資料夾。

STEP 1:撰寫 Protocol Buffers 檔案

  • 使用 message 定義資料交換的格式
  • 使用 service 定義呼叫 API 的方法名稱
syntax = "proto3";  // 定義要使用的 protocol buffer 版本

package calculator;  // for name space
option go_package = "proto/calculator";  // generated code 的 full Go import path

message CalculatorRequest {
  int64 a = 1;
  int64 b = 2;
}

message CalculatorResponse {
  int64 result = 1;
}

service CalculatorService {
  rpc Sum(CalculatorRequest) returns (CalculatorResponse) {};
}

STEP 2:安裝編譯 Protocol Buffer 所需的套件

此部份可參考 編譯 Protocol Buffers(Compiling) 段落。

安裝 compiler

# 安裝 compiler,安裝完後就會有 protoc CLI 工具
$ brew install protobuf
$ protoc --version  # Ensure compiler version is 3+

# 安裝 protoc-gen-go 後可以將 proto buffer 編譯成 Golang 可使用的檔案
$ go get github.com/golang/protobuf/protoc-gen-go

# 安裝 grpc-go 後,可以在 Golang 中使用 gRPC
$ go get -u google.golang.org/grpc

STEP 3:編譯 Protocol Buffer 檔案

進到放有 .proto 檔的資料夾後,在終端機輸入下述指令:
$ protoc *.proto --go_out=plugins=grpc:. --go_opt=paths=source_relative
在成功編譯好後,應該會看到同樣的資料夾位置出現 *.pb.go 的檔案,這就是編譯好後可以在 Golang 中使用 Protocol Buffer 和 gRPC 的檔案。

實作 gRPC Server

對應的程式碼可檢視 besg-grpc repository 中的 server 資料夾。

STEP 1:建立 gRPC server

type Server struct {}

func main() {
 fmt.Println("starting gRPC server...")

 lis, err := net.Listen("tcp", "localhost:50051")
 if err != nil {
  log.Fatalf("failed to listen: %v \n", err)
 }

 grpcServer := grpc.NewServer()
 calculatorPB.RegisterCalculatorServiceServer(grpcServer, &Server{})

 if err := grpcServer.Serve(lis); err != nil {
  log.Fatalf("failed to serve: %v \n", err)
 }
}

STEP 2:實作 Protocol Buffer 中的 service

func (*Server) Sum(ctx context.Context, req *calculatorPB.CalculatorRequest) (*calculatorPB.CalculatorResponse, error) {
 fmt.Printf("Sum function is invoked with %v \n", req)

 a := req.GetA()
 b := req.GetB()

 res := &calculatorPB.CalculatorResponse{
  Result: a + b,
 }

 return res, nil
}

STEP 3:啟動 server

在終端機中輸入:
$ go run server/server.go
即可啟動 gRPC server。

補充:使用 Bloom RPC 進行測試

在只有 server 的情況下,可以使用BloomRPC 這套工具來模擬 Client 對 gRPC server 發送請求,功能就類似在 Restful 中使用的 Postman。
使用時只需要匯入 proto 檔後,即可看到對應可呼叫的方法和可帶入的參數,能這麼方便也是因為在 protocol buffer 中已經把傳輸的資料格式和能對應呼叫的方法都定好的緣故。
Bloom RPC

建立 gRPC Client

完整程式碼可檢視 besg-grpc repository 中的 client 資料夾。

STEP 1:與 gRPC server 建立連線

func main() {
 conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
 if err != nil {
  log.Fatalf("failed to dial: %v", err)
 }

 defer conn.Close()

 client := calculatorPB.NewCalculatorServiceClient(conn)

 doUnary(client)
}

STEP 2:使用 Protocol Buffers 中定義好的 Service

func doUnary(client calculatorPB.CalculatorServiceClient) {
 fmt.Println("Staring to do a Unary RPC")
 req := &calculatorPB.CalculatorRequest{
  A: 3,
  B: 10,
 }

 res, err := client.Sum(context.Background(), req)
 if err != nil {
  log.Fatalf("error while calling CalculatorService: %v \n", err)
 }

 log.Printf("Response from CalculatorService: %v", res.Result)
}

STEP 3:向 server 發送請求

在終端機中輸入:
$ go run client/client.go
即可執行 client.go 並向剛剛起動好的 server 發送請求。

gRPC 解決了什麼

gRPC 和 REST API 的比較

簡單來說,gRPC 在效能上比起 REST API 好非常多:
項目 gRPC Restful API
資料傳輸格式(Payload) Protocol Buffer - 更快且更小 JSON, XML, formData - 較慢且較大
通訊協定 HTTP/2 HTTP
傳輸方式 支援一般的「請求-回應」、伺服器端串流、Client 端串流、與雙向串流(streaming) 僅能透過 Client 發送請求、Server 給予回應
API 方法命名 沒有限制,一般會直接描述該方法要做的事,例如 createUser, getUser。不需要思考路由命名。 使用動詞(GET, POST, PUT, PATCH, DELETE)搭配資源來命名。需要根據不同的行為來定義不同的路由。
Client 呼叫 API 的方式 就像呼叫一般的函式 透過特定的 Endpoint,給予符合的資料型別
Server 建立 API 的方式 根據文件(Protocol Buffer)實作功能,不需要額外檢查資料型別與方法正確性。 根據文件(Swagger)實作功能,但須額外檢查資料型別。
根據文件產生程式碼 gRPC OpenAPI / Swagger
此外,gRPC 的 server,預設就是非同步的,因此不會阻塞任何進來的請求,並可以平行處理多個請求。gRPC Client 則可以選擇要用同步(阻塞)或非同步的方式處理。

使用 Protocol Buffers 的好處

  • 節省網路傳輸量:速度更快、檔案更小
  • 節省 CPU 消耗:Parse JSON 本身是 CPU intensive 的任務;Parse Protocol Buffer(binary format)因為更接近底層機器表徵資料的方式,消耗的 CPU 資源較低
  • 跨程式語言:Protocol Buffer 可以根據不同的程式語言編譯出不同的檔案
  • 可以寫註解、型別清楚明確
:::tip
節省網路傳輸量和 CPU 消耗在行動裝置上的影響可能更重要。
:::

跨程式語言的好處

透過 Protocol Buffer 定義好資料的傳輸欄位(message)和呼叫的方法(service)後,gRPC 即可在不同程式語言上運行,這非常適合微服務(micro-services)的應用情境,只要雙方一起定義好 schema 後,就可以用不同的程式語言進行開發。

使用 HTTP/2 的好處

傳統的 HTTP/1.1 在每個 TCP 連線中只允許向 server 發送單一個請求,但當網頁載入時,往往會需要向同一個伺服器發送多個請求(例如、圖檔、CSS、靜態檔、JS 等),因此為了要避開這樣的限制、加快載入的速度,瀏覽器會實作多個平行的(parallel) TPC 連線(每個瀏覽器實作不同,因此數量的上限也不同),以處理同時向伺服器發出的多個請求。
在 HTTP/2 中則可在同一個 TCP 連線中進行多個請求和回應,並且可以由 server 主動推送資源給 client,而並非一定要透過 client 主動請求;此外支援 HTTP Header 的壓縮,減少資料傳數量;HTTP/2 也是使用 binary 的方式在傳輸資料。
HTTP2

gRPC 的四種類型

  • Unary:類似傳統 API,client 發送 request 而 server 回傳 response
  • Server Streaming:透過 HTTP/2,client 發送一次 request,而 server 可以回傳多次資料
  • Client Streaming:client 發送多次資料,直到告知 server 資料傳完後,server 再給予 response
  • Bi Directional Streaming:兩邊都用串流的方式傳送資料
gRPC
service GreetService {
  // Unary
  rpc Greet(GreetRequest) returns (GreetResponse) {};

  // Streaming Server
  rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse) {};

  // Streaming Client
  rpc LongGreet(stream LongGreetRequest) returns (LongGreetResponse) {};

  // Bi-directional Streaming
  rpc GreetEveryone(stream GreetEveryoneRequest) returns (stream GreetEveryoneResponse) {};
}

gRPC 的缺點

  • Protocol Buffer 不像 JSON 是 Human Readable。
  • 需要額外的學習時間和導入成本。
  • 瀏覽器原生目前還不支援,須透過套件 grpc-web 來處理。

其他

推薦工具

  • BloomRPC:方便用來模擬 Client 對 gRPC server 發送請求,功能就類似在 Restful 中使用的 Postman。

錯誤排除

protoc-gen-go: program not found or is not executable

# 需要把 $GOPATH/bin 加到 .zshrc/.bashrc 等
$ echo 'export PATH=$PATH:$GOPATH/bin' >> $HOME/.zshrc

參考資料

2021年3月25日

限時優惠再臨!辦公軟體(微軟 Windows & Offices)限時下殺 4.5 折

先前曾為大家介紹過的 Gofunco 線上軟體銷售平台又突然帶來限時優惠囉!工作上一定會用到的辦公軟體(Office)、作業系統(Windows)都一次提供超優惠的價格,可以說是補貨的最佳時機!
此次限定的優惠活動,主打了各類型的辦公軟體與作業系統等工具,包括大家熟悉的 Office 2016、Office 2019 與 office 365 家庭版 - 6 個用戶 - 6 個月,還有 Windows 10 與綑綁 Office 系列的合購版本,價格當然也是很優惠,其中四個主打產品直接提供超狂的 1 折起特價,而 Office 系列的 Office 2016 與 Office 2019 也直接下殺 4.5 折,Windows 與 Office 365 也同樣有超優的 5 折好康!
至於 Gofunco 上販售的軟體金鑰是否合法?經過實測購買的 Office 或 Windows 序號都可以通過微軟官方的線上驗證並完成安裝,理論上屬於合法金鑰,但這裡還是提醒大家,在這類軟體平台上購買時,還是要確認來源是否為大量授權或 OEM 序號,若有疑慮,也建議可參考微軟官方管道或 PCHome 等電商發行的版本

限時特惠,5 款主打秒殺爆款免折扣碼就便宜帶走

超值 5 折!作業系統現在就買(使用折扣碼 EBF50)

折扣碼: EBF50
image-20210325232019140

全年最低 4.5 折!Office 2019 與 Office 2016 都好康(使用折扣碼 EBF55)

折扣碼: EBF55
image-20210325232524425

Office 365 與其他爆品全部 5 折!(使用折扣碼 EBF50)

折扣碼:EBF50

購買後有問題?

1.下單超過 12 小時沒收到或缺序號怎麼辦?

注意垃圾收件匣看看,如果還是沒有請直接寫信給網站客服,客服 Email:support@gofunco.com,可以寫中文,請不要找我們網站哦。

2.序號無法順利啟用或授權次數已達限制?

請直接寫信給網站客服更換序號,客服 Email:support@gofunco.com,可以寫中文。

3.使用 Office 2019&office2016 限制有哪些?

Office 2019 只能使用在 Windows 10 以上,如果你是 Windows 7 作業系統的話,請購買 Office 2016 版本。

4.關於軟體的語系?

不論 Windows 或 Office 全版本都是多國語系,當然包括繁體中文,所以不用擔心這個問題。
若是在購買方面有任何問題,也可以透過官方的客服信箱來詢問哦!
客服信箱:support@gofunco.com

2021年3月8日

[React] 讓父層可以取得子層的 DOM 元素:ForwardRef 的使用

imgur

目的

有些時候父層的元件希望能夠取得子層的 DOM 元素(例如,buttoninput),以便能夠在父層控制子層 DOM 元素的 focus, selection 或 animation 的效果。這時就可以使用 Ref forwarding 來讓父層取得子層 DOM 元素,以便控制和操作它。
:::note
通常需要被 forwardRef 的子層元件會是封裝好的元件(例如,套件),其他使用它的開發者無法直接修改,因此才需要透過 forwardRef 把控制權交給父層元件,讓其他開發者可以直接控制。
:::

forwardRef 基本使用

舉例來說,我們建立一個 AwesomeInput 元件:
const AwesomeInput = (props) => <input type="text" />;
接著我們在父層 <App> 元件中使用 <AwesomeInput /> 元件:
const App = () => {
  return <AwesomeInput />;
};
這時候如果想要在 App 元件得到 AwesomeInput 中 <input /> 的 value 或者是對它進行 focus 的動作,就需要透過 forwardRef 把這個 <input /> 傳到父層以供使用。
要讓 <App /> 可以操作到 <AwesomeInput /> 中的 <input /> 元素,需要:

STEP 1:在父層元件建立 ref

  • 先在父層元件透過 useRefcreateRef 建立一個 ref,這裡取名作 awesomeInputRef
  • 把建立好的 awesomeInputRef 透過 ref 屬性傳到 <AwesomeInput /> 元件內
const App = () => {
  const awesomeInputRef = React.useRef(null);

  return <AwesomeInput ref={awesomeInputRef} />;
};

STEP 2:在 AwesomeInput 使用 forwardRef

接著在 <AwesomeInput /> 元件中,可以使用 React.forwardRef() 來把 <AwesomeInput /> 內的 <input /> 傳出去:
  • 一般的 React Component 是不會取得 ref 的屬性,需要被 React.forwardRef() 包起來的才會有 ref 屬性:
const AwesomeInput = React.forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

STEP 3:在 App 可以使用 AwesomeInput 中的 input DOM 元素

這時候,就可以直接在 App 中操作 AwesomeInput 中的 <input /> 元素,舉例來說,我們希望做到 autoFocus 的效果,就可以在 <App /> 元件中,透過 awesomeInputRef 取得裡面的 <input /> 元素:
const App = () => {
  const awesomeInputRef = React.useRef(null);

  // App mounted 的時候讓 AwesomeInput 中的 input 元素 focus
  React.useEffect(() => {
    console.log(awesomeInputRef.current); // <input type="text">...</input>
    awesomeInputRef.current.focus(); // 對 AwesomeInput 中的 <input /> 進行操作
  }, []);

  return <AwesomeInput ref={awesomeInputRef} />;
};

在 HOC 中使用 forwardRef

現在假設建立一個名為 logPropsHOC 的 Higher Order Component(HOC),單純是把元件的 props 有改變時 console 出來的作用:
const logPropsHOC = (WrappedComponent) => {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props: ', prevProps);
      console.log('new props: ', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
};
這時候,如果我們把上面寫過的 AwesomeInput 元件用此 HOC 包起來後再使用:
const AwesomeInput = React.forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

const AwesomeInputWithLogProps = logPropsHOC(AwesomeInput);
接著在 App 中使用 AwesomeInputWithLogProps
const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    console.log(awesomeInputRef.current); // <input type="text">...</input>
    // awesomeInputRef.current.focus(); // 對 AwesomeInput 中的 <input /> 進行操作
  }, []);

  return <AwesomeInputWithLogProps ref={awesomeInputRef} />;
};
這時候會發現 awesomeInputRef.current 的對象變成是該 HOC 元件,也就是 LogProps,而不像原本一樣能夠指稱到 AwesomeInput 元件中的 <input /> 元素。
為什麼會這樣呢?這是因為,雖然我們有在 LogProps 中有試著用:
return <WrappedComponent {...this.props} />;
把所有的 props 都帶回到原本的元件中,但因為 ref 並不是 prop,所以在 props 中並沒有 ref 的內容,進而不會傳遞到 AwesomeInput 元素中
即使在 AwesomeInput 元件包了 HOC 後,為了要讓 App 元件還是能夠取得 AwesomeInput 元件中的 <input />,我們需要在 HOC 中一樣先透過 React.forwardRef 取出 ref 後,再把它傳遞到被 HOC 包覆起來的 WrappedComponent 中,具體來說可以這樣做:

STEP 1:將 HOC 回傳的元件也用 React.forwardRef

首先將 HOC 回傳的元件也用 React.forwardRef 取出 ref,接著再把這個 ref 傳到 LogProps 元件內:
const logPropsHOC = (WrappedComponent) => {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props: ', prevProps);
      console.log('new props: ', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return React.forwardRef((props, ref) => <LogProps {...props} forwardedRef={ref} />);
};
:::caution
要留意的是 ref 是 React 元件的關鍵字,這裡如果單純只是要把 ref 當成 props 往下傳的話,不能用 ref 當名稱,因此這裡使用 forwardedRef 作為 props 的名稱。
:::

STEP 2:從 props 中取出 forwardRef 並帶到下層的 ref

接著就可以從 props 中取出 forwardedRef 後,透過 ref 把它帶進去:
const logPropsHOC = (WrappedComponent) => {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props: ', prevProps);
      console.log('new props: ', this.props);
    }

    render() {
      const { forwardedRef, ...rest } = this.props;
      return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => <LogProps {...props} ref={ref} />);
};
:::tip
留意 Component 使用 ref 時,是要使用 ref 的功能,還是只是要把父層的 ref 當層 props 往下傳遞,如果是要把 ref 當成 props 往下傳遞,就不能使用 ref 當作屬性名稱,而要換名字,例如 forwardedRef={ref}
:::

針對 Class Component 使用 forwardRef

針對 Class Component 使用 forwardRef 的方式和 Function Component 非常相似。首先將原本的 AwesomeInput 元件改成 class component:
class AwesomeInput extends React.Component {
  render() {
    return <input type="text" />;
  }
}

STEP 1:透過 props 接受父層傳進來的 ref

由於要把 ref 當成 props 傳遞時,props 的名稱不能直接使用 ref,因此改名為 forwardedRef,並從 props 中取出:
class AwesomeInput extends React.Component {
  render() {
    const { forwardedRef } = this.props;
    return <input type="text" ref={forwardedRef} />;
  }
}
:::note
Class Component 雖然能直接使用在元素的使用上使用 ref,但它會指稱到的是該 class 的 instance 而非 <input />
:::

STEP 2:使用 React.forwardRef 取出父層傳進來的 Ref,並以 props 往下傳遞

接著要使用 React.forwardRef 先取出父層傳入的 ref,並改名為 forwardedRef 後以 props 傳入 AwesomeInput 元件中:
const AwesomeInputWithForwardRef = React.forwardRef((props, ref) => {
  // 把父層的 ref 透過 props 往下傳
  return <AwesomeInput forwardedRef={ref} {...props} />;
});

STEP 3:在父層中可直接取用到 AwesomeInput 元件中的 input 元素

App 元素並不需要做什麼更改,即可取得 <AwesomeInput /> 元件中的 <input /> 元素:
const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    console.log(awesomeInputRef.current); // <input type="text">...</input>
    awesomeInputRef.current.focus(); // 對 AwesomeInput 中的 <input /> 進行操作
  }, []);

  return <AwesomeInputWithForwardRef ref={awesomeInputRef} />;
};

幾種不同的變化型

若有需要在父層取得子層的 ref,React 在官方文件中的 Exposing DOM Refs to Parent Components 建議使用 React.forwardRef 的做法,但若你使用的是 React 16.2 以前的版本,或需要更彈性的用法,則直接把 ref 當成 props 直接傳到子層也是可行的,但 props 的名稱不能用 ref,需另外取名。

Function Component:使用 React.forwardRef

  • 直接在 JSX 的 Function Component 中使用 ref
const AwesomeInput = React.forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    console.log(awesomeInputRef.current); // <input type="text">...</input>
    awesomeInputRef.current.focus(); // 對 AwesomeInput 中的 <input /> 進行操作
  }, []);

  // 直接在 JSX 的 Function Component 中使用 ref
  return <AwesomeInput ref={awesomeInputRef} />;
};

Function Component:直接將 ref 當成 props 往下傳

  • 在 JSX 的 Function Component 中,不用 ref 當屬性名稱,換成其他名稱後以 props 的方式往下傳(例如,forwardedRef
const AwesomeInput = (props) => {
  // 取用的是 forwardedRef
  return <input type="text" ref={props.forwardedRef} />;
};

const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    console.log(awesomeInputRef.current); // <input type="text">...</input>
    awesomeInputRef.current.focus(); // 對 AwesomeInput 中的 <input /> 進行操作
  }, []);

  // 將 ref 當成 props(不使用 ref 作為屬性名稱)往下傳
  return <AwesomeInput forwardedRef={awesomeInputRef} />;
};

Class Component:直接使用 ref 往下傳

錯誤寫法

  • 雖然 class component 可以直接接收 ref 做為參數,但它會指稱到的是該 Component 的 instance 而不是 Component 內的 DOM 元素
// 錯誤寫法:ref 會是 AwesomeInput 元件,而不是內部的 input 元素
class AwesomeInput extends React.Component {
  render() {
    const { ref } = this.props;
    return <input type="text" ref={ref} />;
  }
}

const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    // 這裡指稱到的會是 `<AwesomeInput />`
    console.log(awesomeInputRef.current); // <AwesomeInput />
  }, []);

  // 直接透過 ref 往下傳
  return <AwesomeInput ref={awesomeInputRef} />;
};

使用 forwardRef

見上方段落「針對 Class Component 使用 forwardRef」的說明。

可行寫法

// 在 AwesomeInput 建立另一個 ref 以指稱到 input 元素
class AwesomeInput extends React.Component {
  innerRef = React.createRef();

  render() {
    return <input type="text" ref={this.innerRef} />;
  }
}

const App = () => {
  const awesomeInputRef = React.useRef(null);

  React.useEffect(() => {
    // 先指稱到 AwesomeInput 元件,再進去裡面找到 input 元素
    console.log(awesomeInputRef.current.innerRef.current);
  }, []);

  return <AwesomeInput ref={awesomeInputRef} />;
};

其他參考