2020年10月27日 星期二

Maven - 使用 profile 功能為不同環境設置不同內容的檔案 - 以 web.xml, proxool.xml 為例

 Maven 可以使用 profile 的功能來設定多個環境,

並對不同環境使用不同的設定,非常方便,

舉個例來說:

通常程式專案可能會有幾個開發階段,

例如開發、測試、正式上線等,

而在不同的階段,我們可能會想使用不同的設定,

例如 Java EE Web 專案的 web.xml 可能在不同的階段會想使用不同的內容設定、

或者是 DB connection pool, Proxool 的 proxool.xml 可能在開發階段想連本地端或測試機的資料庫 (Database),但在正式上線時想連正式機的資料庫等。


這篇文主要是來紀錄使用 Maven 的 profile 功能,來為一個 Jave EE Web 專案使用

不同的 web.xml 和 proxool.xml。

我們希望在使用 maven 產生 war 檔

(命令列指令:

mvn clean install -P{active 的 profile},例如:

mvn clean install -Pdev)

時,能夠依不同的 profile 使用不同的設定。


在這篇文的例子中,先說明幾個比較特別的地方:

  1. 此篇文的 Java EE Web 專案,是由沒有使用 Maven 的舊 Java EE Web 專案轉成的有使用 Maven 的新專案,故檔案結構跟一開始就使用 Maven + webapp archetype 不一樣。
  2. 此篇文使用 Maven 的 maven-war-plugin 來處理 war 檔的發佈,其中 web.xml 可以用 <webXml> 參數來設置。
    而 proxool.xml 用 <webResource> 參數來設置。


如果舊專案是用 Eclipse 產生出來的,可以對專案按右鍵,選 

Configure --> Convert to Maven Project

來將其轉成 Maven 專案,如下圖:



檔案結構如圖所示,其中我們在專案的根目錄下 (也是 pom.xml 所在的位置),建立了一個 config 資料夾,並在其中建立了三個不同環境的資料夾 (dev, demo, production),在各個環境的資料夾中都放置了一份 web.xml 和 proxool.xml,如下圖所示:






首先在 pom.xml 中設定 profile,例如在這我們分三個階段,開發 (dev)、測試 (demo)、正式上線 (production):

<profiles>
  	<profile>			
		<id>dev</id> 
		<properties>
			<profiles.active>dev</profiles.active>	
			<jdk>11</jdk>			
		</properties>
		<activation>
			<activeByDefault>true</activeByDefault>
		</activation>
	</profile>
	<profile>			
		<id>demo</id>
		<properties>
			<profiles.active>demo</profiles.active>			
			<jdk>11</jdk>			
		</properties>		
	</profile>
	<profile>			
		<id>production</id>
		<properties>
			<profiles.active>production</profiles.active>			
			<jdk>11</jdk>			
		</properties>		
	</profile>
</profiles>

接著來設定 web.xml 和 proxool.xml,我們使用了 maven-war-plugin (詳細參數) 來處理,
首先設定 warSourceDirectory 到 WebContent 資料夾 (平常預設是 ${basedir}/src/main/webapp),
然後用 webXml 設定 web.xml 所在的位置 (相對 pom.xml 所在的路徑),
最後用 webResource 設定 proxool.xml :
<plugin>
	<artifactId>maven-war-plugin</artifactId>
	<version>3.2.1</version>
	<configuration>
		<warSourceDirectory>WebContent</warSourceDirectory>
		<webXml>config/${profiles.active}/web.xml</webXml>
		<webResources>
			<webResource>
 				<directory>config/${profiles.active}</directory>
			  	<includes>
			  		<include>proxool.xml</include>
			  	</includes>
			  	<targetPath>WEB-INF</targetPath>
			</webResource>
		</webResources>
	</configuration>
</plugin>
在這邊,${profiles.active} 代表要發佈時所要使用的 profile,即 dev, demo 或 production,
profile 可以在
mvn clean install -P{要的 profile} 
指令中指定,例如如要使用 demo,指令就為:
mvn clean install -Pdemo,
如果沒有在命令中指定 profile (即 -P 參數),就會使用 profile 裡有設定 activeByDefault = true 的 profile (例如此篇文就是設定 dev)
<activation>
	<activeByDefault>true</activeByDefault>
</activation>

在 webResource 設定裡, targetPath 的位置是相對於 warSourceDirectory 的位置,
在這篇文中,就是 WebContent 資料夾

因為在這篇文中,我們沒有把 web.xml 和 proxool.xml 放在專案的 resource directory 之中 (config 資料夾不在 resource directory 裡),
也沒有放在 warSourceDirectory 的設定資料夾中,
也就是 web.xml 和 proxool.xml 不會被發佈到 war 檔裡面,
所以不需要特別對其做 resource excludes 的動作,
例如 resource 的 exclude 
或 <warSourceExcludes> (值為相對於 warSourceDirectory 設定的位置)


參考資料:

Maven - 引用本地端的 jar

這篇文主要來紀錄我使用 Maven 引用本地端 jar 檔的經驗。

在使用 Maven 時,如果想要加入到 library 的 jar 檔在 Maven 的 Repository 找不到、

例如:

  1. 因為某些授權的原因,jar 檔的作者不願意將之放到 Maven Repository 上面,
    或就是沒有人把其 jar 檔放到 Maven Repository 上面。
  2. 可能 jar 檔是來自於合作的第三方,例如跟公司合作的夥伴提供的專屬 jar 檔。
  3. 或是本來原本官方的 jar 檔,有被自己以手動的方式做二次修改的 jar 檔 ,可能是為了相容問題或修正某些官方 bug,例如:Proxool + MS SQL Server DB Driver的相容问题 (參考:
    JAVA+ Proxool+ SQLserver 2008 “signer information does not match signer information of other classe...
    Proxool+SQLserver2008(sqljdbc4.jar) integration problem
  4. 自己寫的,例如公司內部用的 jar 檔,或是想要把舊專案轉成 Maven 時,因年代已久無法考證 jar 檔來原,所以乾脆直接繼續拿來用的 jar 檔。

這時我們就必須要引用本地端 (local) 的 jar 檔,

Maven 大概有幾種作法:

  1. 在 pom.xml 使用 <scope>system</scope> 和 <systemPath> 來引用本地 jar 檔檔案路徑
  2. 將本地 jar 檔放到 local repository (使用命令列指令)
  3. 將本地 jar 檔放到 local repository (在 pom.xml 中加入設定)


首先先講我在實務上希望的需求是:

  1. 能夠在 pom.xml 中容易地看出哪些是自訂的 local jar dependency。
  2. 能夠看得出及容易找出 local jar 檔放在哪裡。
  3. 能夠在不下命令列指令的情況下成功地匯入 jar 檔到 local repository。
  4. 能夠跟 IDE 配合,例如: Eclipse Maven Plugin + Tomcat 

再來在以下分述這三種作法及我實務上的例子,及我最後的選擇偏好:


1. 在 pom.xml 使用 <scope>system</scope> 和 <systemPath> 來引用本地 jar 檔檔案路徑

第一種方法 (稱為 System Dependencies) 在使用上非常簡單,直接在 pom.xml 上指定 local jar 檔的檔案路徑,

例如:

<dependency>
        <groupId>myGroupId</groupId>
        <artifactId>myArtifactId</artifactId>
        <version>1.0.0</version>
	<systemPath>D:\myJar.jar</systemPath>
	<scope>system</scope>
</dependency>

其中 <systemPath> 中也可使用用一些 Maven 自己的參數,例如表示專案路徑的 ${basedir}

此方法有幾個缺點:

  1. Maven 官網說 <scope>system</scope> 的用法 (System Dependencies)已經 deprecated 了 (來源)
  2. 在要使用 maven 指令部屬專案時,例如要產生 war 檔時, mvn clean install 產生出的 war 檔裡並不會把用 System Dependencies 設定的 local jar 檔包進生產的專案中。
  3. 如果使用 Eclipse Maven plugin + Tomcat 來開發,用 System Dependencies 設定的 local jar 檔不會被部屬至 server 上 (例如:  C:\Users\xxxUser\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\xxProject\WEB-INF\lib)


2. 將本地 jar 檔放到 local repository (使用命令列指令)

此方法是使用 Maven 的指令直接將本地端的 jar 檔放進 local repository 中,命令如下 (大括弧 {} 中的值可自由填寫):

mvn install:install-file -Dfile={本地端 jar 檔的檔案位置} -DgroupId={自訂的 group id} -DartifactId={自訂的 artifact id} -Dversion={自訂的 version}

例如:

mvn install:install-file -Dfile=D:\myJar-1.0.0.jar -DgroupId=myGroupId -DartifactId=myJar

-Dversion=1.0.0

注意 jar 檔的檔名要改成符合 maven 格式,就是 artifactId-version 這種格式。

執行完命令後,可以到自己本地上的 local respository (通常位置在C:/Users/xxxUser/.m2/repository) 去看是否有成功匯入 jar 檔。

最後在 pom.xml 中加入引用 jar 檔的設定如下:

<dependency>
          <groupId>myGroupId</groupId>
          <artifactId>myArtifactId</artifactId>
          <version>1.0.0</version>
</dependency>


不過個人認為這種方法有一個缺點,

就是如果今天我們換了一台電腦或換了一個開發者要開發時,

必須要對 local jar 檔再一次執行上述的命令例指令以匯入 jar 檔 local repository,

並且單看 pom.xml 很難辨認出哪個 dependency 是使用了自訂的 local jar 檔。

對比我上述的需求,此方法:

  1. 無法在 pom.xml 中容易地看出哪些是自訂的 local jar dependency (除非把命令列指令存起來,再對照去找 local jar 檔位置)。
  2. 單看 pom.xml 看不出 local jar 檔放在哪裡 (除非例如把要下的命令列指令放到 project 中的某一個 file 上)。
  3. 需下命令列指令來匯入 jar 檔到 local repository。
  4. 能夠跟 IDE 配合,例如: Eclipse Maven Plugin + Tomcat 


3. 將本地 jar 檔放到 local repository (在 pom.xml 中加入設定)
補充:不確定是不是 Maven 3 版本以上才需要,發現除了本地 jar 檔,還需要再放一個本地 pom 檔 (除了附檔名,檔名跟本地 jar 檔一樣)

此方法是我目前最喜歡的方法,此方法跟第二種方法一樣,

都是把本地 jar 檔放到 local repository 中,不過差在不是執行命令列指令,

而是把要的設定寫在 pom.xml 中。

首先我們要在 pom.xml 中,設定自訂的 repository,例如:

<repository>
	<id>localLibRepository</id>
	<name>localLibRepository</name>
	<url>file:${basedir}/localLibRepository</url>
</repository>

其中 ${basedir} 為 Maven 的參數,代表專案的根目錄位置,

localLibRepository 為我自己建立的資料夾,用來存放 local jar。


設定好後,接著要來把 local jar 檔放到相應的位置去,

首先像上述第二種方法一樣,把 local jar 檔命名成 maven 的格式,例如: 

myArtifactId-1.0.0.jar

然後仿照 Maven Repository 的檔案路徑格式,即 

{repositoryPath}/{groupId}/{artifactId}/{version}/{jarFileName}

的規則建立資料夾 (如同 java 的 package 一樣,如 com.xxx.yyyy,應為 com, xxx, yyy 三個資料夾),並將 local jar 檔放到正確的位置上,例如:

D:\myProject\localLibRepository\myGroupId\myArtifactId\1.0.0\myArtifactId-1.0.0.jar

接著在同一個位置增加 pom 檔,例如:

D:\myProject\localLibRepository\myGroupId\myArtifactId\1.0.0\myArtifactId-1.0.0.pom

內容範例如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>myGroupId</groupId>
  <artifactId>myArtifactId</artifactId>
  <version>1.0.0</version>
</project>

最後如第二種方法一樣,在 pom.xml中引入 local jar 檔的 dependency,例如:

<dependency>
          <groupId>myGroupId</groupId>
          <artifactId>myArtifactId</artifactId>
          <version>1.0.0</version>
</dependency>

之所以我最後選擇這個方法的原因是,它完全符合我的需求:

  1. 比較能夠在 pom.xml 中容易地看出哪些是自訂的 local jar dependency
    (對照 pom.xml 中自訂 repository 的設定,打開 repository 的資料夾即可得知)。
  2. 能夠看得出及容易找出 local jar 檔放在哪裡 (看 pom.xml 中即可得知自訂 repository 的位置)。
  3. 能夠在不下命令列指令的情況下成功地匯入 jar 檔到 local repository。
  4. 能夠跟 IDE 配合,例如: Eclipse Maven Plugin + Tomcat 


參考資料:

  1. 3 ways to add local jar to maven project
  2. Adding dependencies to local .jar files in pom.xml
  3. How to include local jar files in Maven project [duplicate]
  4. Maven私服上传pom和jar实操

2020年10月21日 星期三

React - Redux + Hook 練習 - ToDo List (待辦事項) 簡易實作

Hook 是 React v16.8 的新功能,讓我們在撰寫 React Component 時,

可以讓我們用 function 的寫法來取代 Class 的寫法,

來避免在 Class 中常會碰到的問題,例如:

1. Class 中的 this 意義常讓人無法容易地理解。

2. Component 上的事件 (event) 綁定,常須要多寫 bind() 的步驟,並且涵義也容易讓人不容易了解,例如:

class TextInput extends React.Component{

	constructor(props){

		super(props);

		this.onHandleTextChanged = this.onHandleTextChanged.bind(this);

	}

	onHandleTextChanged(e){

		this.props.handleTextChanged(e.target.value);

	}



	render(){

		return (

			<div>

					<input type="text" value={this.props.text} onChange={this.onHandleTextChanged} />

			</div>

		);

	}

}

而因為 function 不像 Class 可以儲存保存自己的成員變數,

為了可以實現像 Class 那樣保存、修改依賴於 Component 的成員變數,

React 使用了 useState() 這個 function,其可以以一個初始值做為輸入,

並返回一個包含 變數、修改變數的 function 的 array,

Ex:

import { useState } from "react"

const [text, setText] = useState("initial value");


React Redux 也推出了使用 React Hook 的版本,

其提供了 useSelector() 和 useDispatch() ,

useSelector() 可以讓我們方便得取得 Reducer Storde 裡 state 的值,

而不用像以前一樣使用

connect(mapStateToProps, actionCreators)

this.props.XXX

等的方法來取得 state 值,

Ex:

import {useSelector} from 'react-redux'

const toDoList = useSelector(state => state.toDoListReducer.toDoList);


useDispatch() 可以讓我們方便得送入要傳給 Reducer 的 Action 來觸發 Component render 元件繪製的行為,

而不用像以前一樣使用

connect(mapStateToProps, actionCreators)

this.props.xxxAction()

等的方法來傳遞 Action 給 Reducer,

Ex:

import {useDispatch} from 'react-redux'

import * as actionCreators from './action'

const dispatch = useDispatch();

function handleDeleteToDo(){

dispatch(actionCreators.deleteToDo(toDo.id));

}


這篇文要用 React Hook 來改寫上一篇,

React - Redux 練習 - ToDo List (待辦事項) 簡易實作

做為練習,並比較沒用 Hook 及有用 Hook 兩者程式碼的差異。


跟之前的例子比,有改動的檔案只有:

Main.jsx

TextDisplayer.jsx

TextInpupt.jsx

ToDoList.jsx

ToDoRow.jsx

如下圖所示


接著來看程式碼的部份:

Main.jsx :

import React, { useState } from "react"
import {TextDisplayer} from "./TextDisplayer.jsx"
import TextInput from "./TextInput.jsx"
import ToDoList from "./ToDoList.jsx"

function Main(){
	const [text, setText] = useState("");	

	return (
		<React.Fragment>
			<TextInput text={text} handleTextChanged={setText}/>
			<TextInput text={text} handleTextChanged={setText}/>
			<TextDisplayer text={text}/>
			<ToDoList/>
		</React.Fragment>
	);
}

export default Main

說明:

可以看到原本的 Main 不用 Class ,而改用了 function 去改寫,

因為只是 function ,所以連 constructor() 也不用了。

原本在用 Class Component 時,Component 所管理的 text 這個成員屬性,

是用 this.state = {text : ""} 的方式指定初始值的。

改用 Function Component 時,我們使用了 useState(初始值) 的方式來獲得設定了初始值的 text 變數,並且還得到了一個可以改變 text 的 function,即 setText() ,

const [text, setText] = useState("");

中的 text 及 setText() ,就跟以前一樣,

可以使用在 Main Component 中做UI的顯示,

也可以傳遞給 Child Component ,讓下層元件可以顯示及修改 text。

例如在 Main Component 中,我們把 text 及  setText() 傳遞給 TextInput、TextDisplayer 及 ToDoList。


TextDisplayer.jsx :

import React from "react"

function TextDisplayer({text}){
    return (<div>Text you typed : {text}</div>);
}

export {TextDisplayer}

說明:

TextDisplayer Component  也寫成了 Hook 的 Function Component 的形式,

此時接收上層元件的值改由從 Function Component 的輸入值傳進來,

輸入會是一個 Object, key 及 value 由上層元件傳進來的時候指定,

例如 在 Main.jsx 中用 text={text} 的方式傳 text 進 TextDisplayer 元件,

TextDisplayer 的 function component 輸入就會是:

{text : text的值} 


TextInpupt.jsx :

import React from "react"

import {useDispatch} from 'react-redux'
import * as actionCreators from './action'

function TextInput({text, handleTextChanged}){	
	const dispatch = useDispatch();

	function onHandleTextChanged(e){
		handleTextChanged(e.target.value);
	}

	function onHandleAddToDo(e){
		dispatch(actionCreators.addToDo(new Date().getTime(), text));
		handleTextChanged("");
		e.preventDefault();
	}

	return (
		<div>
			<form onSubmit={onHandleAddToDo}>
				<input type="text" value={text} onChange={onHandleTextChanged} />
				<input type="submit" value="addToDo"/>
			</form>
		</div>
	);
}

export default TextInput

說明:

TextInput Component 也被改寫成了 Hook 的 Function Component 形式,

此時不像 Class Component 時使用 this.props 的方式來取得上層元件傳進來的值,

而是直接從 Function Component 的 Input 輸入取得,

輸入會是一個 Object,其中會包括由上層元件傳進來的值,例如此例就是 Main.jsx 傳進來的

text 及 handleTextChanged 。

可以注意到的是, onChane 上面綁定的 onHandleTextChanged(),

不用再像以前 Class Component 那樣須要使用 bind() ,例如以前的寫法:

this.onHandleAddToDo = this.onHandleAddToDo.bind(this);

另一個可以注意到的是,我們沒有使用到如

export default connect(mapStateToProps, actionCreators)(TextInput)

的語法去得到 Reducer Store 裡的變數及函式,

那我們要怎麼把 Action 傳遞給 Reducer 呢?

答案是使用 Redux Hook 提供的 useDispatch() ,

其他傳回一個函式 dispatch(要送給 Reducer 的 Action) ,

我們可以直接把要傳遞的 Actoin 傳給 Reducer。


ToDoList.jsx :

import React from "react"
import ToDoRow from "./ToDoRow.jsx"

import {useSelector} from 'react-redux'

function ToDoList(){
	const toDoList = useSelector(state => state.toDoListReducer.toDoList);

	return (
		<ul>
			{	
				toDoList.map(function(toDo){
					return (
						<ToDoRow toDo={toDo} key={toDo.id}/>
					);
				})
			}
		</ul>
	);
}

export default ToDoList

說明:

ToDoList 元件跟 TextInput 元件一樣,沒有使用 connect() 來從 Reducer Store 取得資料,

那要怎麼取得 Reducer Store 中取得資料呢? 例如此例是要取得 toDoList。

答案是使用 Redux Hook 提供的 useSelector(Reducer 的 state) ,useSelector()會有 Redcuer 的 state 作為 callback 輸入,這樣我們就能取得 Reducer State 裡的資料,

在這裡因為我們之前有在 reducer.js 裡使用了 combineReducers() 去 combine toDoListReducer,所以取得 toDoList 的方式為 state.toDoListReducer.toDoList ,

如果沒有用 combineReducers() 的話,就會改成用

state.toDoList 直接取得值。


ToDoRow.jsx :

import React from "react"

import {useDispatch} from 'react-redux'
import * as actionCreators from './action'

function ToDoRow({toDo}){
	const dispatch = useDispatch();

	function handleDeleteToDo(){
		dispatch(actionCreators.deleteToDo(toDo.id));
	}

	return (
		<li>
			{toDo.toDoText}
			<button onClick={handleDeleteToDo}>Delete</button>
		</li>
	);
}

export default ToDoRow

說明:

ToDoRow 元件從本身 Function Component 的輸入中取得由 ToDoList 元件來的 toDo,

並且跟 TextInput 元件一樣使用了 dispatch() 來傳遞要送給 Reducer 的 Action,此例為 actionCreators.deleteToDo(toDo.id)

-------------------------------------------------------------------------------------

最後我們可以大概總結一下在這篇改用 React Hook 的文中,跟

React - Redux 練習 - ToDo List (待辦事項) 簡易實作

這篇沒使用 React Hook 文不一樣的地方:

  1. Component 元件全部由 Class 寫法改成 Function 寫法,
    故少了 Class 特性的語法,例如: constructor()、 this 的用法、 bind() 的用法。
  2. Component 自己管理的 state (例如 this.state = {text}),因為無法使用 Class 的 this 寫法,
    所以改成用 Hook 來管理,使用方法為 [變數名, 可修改變數的 function] = useState(初始值)。
  3. 上層把變數傳給下層的方法跟以前一樣,只是下層 Hook Component 因為不是 Class,
    所以不用 this.props ,改用從 Function Component 本身的輸入去取得,例如: 
    function ChildComponent({inputValue1, inputValue2, ...})
  4. 在取得 Reducer 管理的 state 方面,不使用 connect() 的方法來將 reducer state 及 action 綁到 this.props 上 ,而是使用 Redux Hook 所提供的 useSelector() 來取得,例如:
    var 要取得的 state value 變數 = useSelector(function(reducer的state){return 要的 value 變數});
  5. 在傳遞 Action 給 Reducer 方面,不使用 connect() 的方法來將 reducer state 及 action 綁到 this.props 上,而是使用 Redux Hook 所提供的 useDispatch() ,
    useDispatch() 可以回傳一個 dispatch(),
    dispatch() 可以輸入一個 action,其會將 action 傳遞給 Reducer,例如:
    var dispatch = useDispatch();
    dispatch(要傳遞的 action)。


最後要注意的地方是,

useState() 、useSelector()、useDispatch() 等

都要在 Function Component 的最上層呼叫,

不要在迴圈、條件式或是巢狀的 function 內呼叫 Hook (參考)。

要理解這個,我們可以從 Hook 實作的原理去理解。


首先是,為什麼元件寫成 function ,卻可以如 Class 搬擁有自己可管理的 state (例如 text),

因為 function 裡的所有變數應該都只是區域變數而己 (local variable),

應該無法長期保存其值 (不管有沒有被修改)。


React Hook 的實作方式可以簡單理解為 (**不代表真實實作的細節,只是類似,可以從這想法大致理解 Hook 的概念),

使用了兩個(或多個,端看 React Hook 的詳細實作)陣列 (或鍵結串列等類似有序串列的資料結構) 來儲存了所有使用

useState() 設定的 值 (暫且稱作 state value) 及 可修改值的function (暫且稱作 setState function),例如:

[value1, value2, value3, ...]  (暫且稱作 state array)

[setValu1, setValu2, setValu3, ...]  (暫且稱作 setState array)

在 React 要重新繪製 (Render) 所有元件時,就會依序執行所有function component 的 function,

如果是第一次繪製,呼叫 useState() 時,各 state value 會用初始值儲存在 state array 中,

並且在相應的 index 中存放可修改 value 的函式在 setState array 中 (所以函式會知道它要去修改哪個 state value)。

如果是之後的繪製,呼叫 useState() 時,各 state value 會以同樣順序的方式從 state array 中取出,

因為 state array 只是個陣列,所以每次取出時必須要照順序,因為它並不知道要取出的 state value 是給哪個 Component 用的,

這樣就能理解,為什麼在 Component 中,只能一次的,在最上層呼叫 useState(),

因為這樣才能確保取出 state value 時,每次都是照一樣的順序



源碼分享下載:
ReactReduxTest.7z

本Blog的其他相關文章:



參考資料:

  1. 使用 State Hook
  2. 打造你自己的 Hook
  3. React Redux | 小孩子才做選擇! Hooks 和 Redux 我全都要!
  4. 原始碼解析 React Hook 構建過程:沒有設計就是最好的設計
  5. React Hooks 不是黑魔法,只是陣列
  6. [译] 深入 React Hook 系统的原理
  7. Hooks
  8. How to pass a React Hook to a child component