Matsu

Nothing more than curiosity

Valid Parentheses in C

昨晚久違的回歸Leetcode,回歸的原因是因為推坑好友到Leetcode裡相約要一起開始一天一題,結果我過了一周才開始,有夠邪惡。但總而言之,希望今天會是一個好的開始。其實去年也才寫沒個幾題,真的可以說自己是個沒有毅力的人,現在也真的是不確定自己會不會堅持下去,畢竟光是React和Node就把自己搞的半死,現在想想大概就是解多少算多少了吧!

昨晚不知道為什麼會挑選Valid Parentheses這題,中途寫不出來還想說是不是要換題目,但仔細想想換題目大概只是遇到另外一個瓶頸,還是咬緊牙關寫下去,沉沒什麼的早已不在乎。礙於太久沒有用C,說實在想到要寫的語法都是JS…實在有點慚愧,看了題目的敘述後,想說用堆疊(Stack)去處理,結果搞個半天,還是沒有成功,想說是不是因為把Pop和Push方法都寫進去,就在網路上複習一會。結果昨晚還是沒解出來!!原本以為洗完澡會想出來,結果到早上腦袋變得清晰才解出來。來看看題目吧。

  • Valid Parentheses
    • 題目規則
    • 解法
  • 結語

Valid Parentheses

我們先看一下題目要求:
Given a string s containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ and ‘]’, determine if the input string is valid.
簡單來說,題目會輸入一個包含這三種符號的字串,字串是透過字元指標(Char Point)傳入,我們需要驗證字串的括號是否成對。就是一般在IDE中Coding打錯括號也會跑出錯誤訊息的括號檢驗。

題目規則

大致上分為以下幾種驗證標準:
Example1:
Input: s = "([{}])
Output: true

Example2:
Input: s = "({[}])"
Output: false

因為彼此交錯回傳false,我們可以得到事實是若某字串符合括號驗證,字串中的子字串會通過驗證。以上述Ex1來說,{}[{}]都屬於Ex1的子字串,它們同樣通過括號驗證。

解法

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
bool isValid(char *s){
int f = -1, len = strlen(s);
// 宣告儲存左邊括號的陣列
char stackVal[len];

// 當*s字串不為'\0'空白字元
while(*s){
// 先確認*s是否為某右括號
if(*s == ')'){
// 確認stackVal括號類別
if(f >= 0 && stackVal[f--] == '(');
else return 0;
} else if (*s == ']'){
if(f >= 0 && stackVal[f--] == '[');
else return 0;
} else if (*s == '}'){
if(f >= 0 && stackVal[f--] == '{');
else return 0;
} else {
// 推進不是右括號的所有符號
stackVal[++f] = *s;
}
// 移動位址空間到下一個字元(指標常有的存取方式) -> 也可使用陣列方式存取
s++;
}
// 確認最後stackVal是否清空(概念上)
return f == -1;
}

結語

主要的解題概念在於能不能追蹤堆疊的索引值,若能夠追蹤就能夠模仿堆疊的Pop和Push的概念,而不需要實際去實作這兩個方法。嚴格上來說,有點偷吃步的概念,因為stackVal這個陣列並沒有真的清空,而是透過旗標f去驗證個別的值。

Hooks家族 - useCallback

Beware of the man who works hard to learn something, learns it, and finds himself no wiser than before.
― Kurt Vonnegut, Cat’s Cradle

來到第六篇關於Hooks的文章,不能說自己真的很懂Hooks,但多多少少,不再對Hooks感到陌生。今天要來聊聊關於useCallback這個Hooks,光是在JS就可以常常看見回呼函數的使用,那麼這個useCallback又與平常我們認知的有什麼不同? 趕緊來看看。

  • useCallback
    • useCallback使用方法
  • 結語

useCallback

官方精闢解析
useCallback: Returns a memoized callback.
useCallback會回傳記憶化的回乎函數,這邊要注意Memoization不等同於Memorization。有機會我們再來詳細探討這兩者間的區別。

所謂的記憶化(Memoization)是一種用於提升電腦程式速度的優化技巧,主要的概念是儲存複雜函數結果並且當相同結果出現時透過快取(Cache)存取。講白就是犧牲儲存空間,去提升電腦性能。

我們實際來看如何使用useCallback於React中。

useCallback使用方式

useCallback定義如下:
const memoizedCallback = useCallback(() => { memoizedFn(a, b)}, [a, b])
透過useCallback這一個Hook,可以直接將要記憶化結果的函數存取在memoizedCallback中,並且只有在a, b會影響memoizedFn函數的時候才會重新執行。

我們實際看看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {useState, useEffect, useCallback} from 'react';

function App() {
const [isLoading, setIsLoading] = useState(false);

const fetchHandler = useCallback(() => {
setIsLoading(true);
fetch('http://dabaseFetch').then(res => console.log(res));
}, []);

useEffect(() => {
fetchHandler();
}, [fetchHandler]);
}

export default App;

首先看到App中的程式碼,我們總共引入三個Hooks,其中兩個先前都是有討論過的Hooks。在這一段程式碼當中,我主要想要去抓到某些資料,可以看到fetchHandler函數裡面有個JS內建的fetch方法(API),fetch就是拿來操作HTTP的各種請求,預設的情況下會是HTTP請求的GET。因此當fetchHandler執行時就會發送一個GET請求給伺服器,伺服器則會回傳某特定格式,而fetch這個API回傳的一定是Promise。

回到主題上,我們透過useEffect去更新fetchHandler,每當App渲染時,useEffect就會執行一次,接著再根據fetchHandler去執行,但是這邊就有一個很大的問題點是源自於React本身。

對於React來說,每當重新渲染時,所有的組件都會是全新的組件,不一樣的組件。什麼意思? 當我們實際執行React專案時,在我們重新載入的那一剎那,所有的組件都是新的組件,不同於先前所渲染的組件。換句話說,以我們fetchHandler來說,即便執行的名稱與內容都完全相同,React也會視這兩個fetchHandler為不一樣的函數,這一切都是源自於JS物件Call by Reference的本質而延伸出來。

因此當我們沒有透過useCallback去記憶化fetchHandler這個函數,就會發生不斷發出GET請求的慘劇。假如我們將程式碼改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {useState, useEffect, useCallback} from 'react';

function App() {
const [isLoading, setIsLoading] = useState(false);

const fetchHandler = () => {
setIsLoading(true);
fetch('http://dabaseFetch').then(res => console.log(res));
});

useEffect(() => {
fetchHandler();
}, [fetchHandler]);
}

export default App;

也就是將fetchHandler的useCallback刪去後,實際執行相似的程式碼就會得到類似於下方的結果:

infi

可以在開發者工具發現fetch會不斷的發送請求到伺服器,這是非常嚴重的問題。但這個嚴重的問題,可以簡單地透過useCallback去解決。我們再度回到fetchHandler這個函數。

1
2
3
4
const fetchHandler = useCallback(() => {
setIsLoading(true);
fetch('http://dabaseFetch').then(res => console.log(res));
}, []);

概念和useEffect一樣,[]代表組件渲染完成後會執行一次。useCallback則會將箭頭函數後的所有函數都記下來指派給fetchHandler。這時候,每當有任何組件重新渲染,fetchHandler不會重新被渲染一次,因為已經透過Hooks將fetchHandler記憶化,因此不會出現fetchHandler不斷重新執行的問題,因為對於React來說,現在的fetchHandler都是同一個函數。

最後我們只需要確保useEffect在處理fetchHandler時所產生的副作用,而副作用就是當資料更新時,必須重新將資料庫添加進去的資料再度透過GET方法去獲得資料就結束這一連串的動作。

1
2
3
useEffect(() => {
fetchHandler();
}, [fetchHandler]);

特別注意到useEffect的依賴只需要指向fetchHandler函數而不需要(),如果加上()代表要立刻在這一行執行,這又是另外一齣慘劇了…


-重點回顧-

  • React每次渲染都會是不一樣的程式碼,即便長得一模一樣
  • useCallback用於記憶化函數

結語

今天簡單說一下useCallback,我想概念上還算挺好理解。不過如何正確使用就又是另外一門學問,不過先搞清楚它們各自的目的才不會摔的滿頭包。

這幾天心情一直很糟,不論是學習上還是生活上,真的可以說諸事不順,也不清楚要怎麼說出來比較好,只好像現在一樣打成文章,或是寫在日記上。但即便如此,好像還是悶在心裡一樣,可能是因為自己能力太差吧,覺得自己什麼都做不好,而現在又因為急於能夠找到一個落腳處,而更加心急如焚。現在都會回想大學不好好學習才會落的這個下場也算是自作自受,也有可能只是我一直往死裡鑽吧。

其實我還是不清楚程式是不是自己想走的一條路,我覺得我自己沒有天分,可是仔細一想,自己到目前為止,也不過一個月不中斷地在練習Coding,但是昨晚看完龍櫻2,卻又覺得自己都沒有好好思考ものごと的本質,慚愧不已。說真的,別人都在進步,為什麼我好像一直在原地踏步。心好累。

Route in React

How did it get so late so soon?
― Dr. Seuss

每一個人都有喜歡的事情,只是有時候會忘記。若那件事情,使時間飛逝,那無庸置疑是你喜歡的事,至於它的意義,並不需要由他人來決定。

從Hooks抽身,今天想要來說說關於Route的基本概念,當然往後會再來說說關於其他Hooks。人生就像在一個又一個的岔路上,React也不例外。想到世界的盡頭,就勢必要懂Route。(笑)

  • Route
    • Route的基本使用
      • NavLink
      • Switch
      • Link
      • Redirect
  • 結語

Route

提到React就會聯想到SPA(Single-Page-Appliaction),但是即便是React有時候也需要導向其他的頁面,以Netflix來說,總不可能所有影片都在同一個URL執行對吧? 不過若導向其他頁面,那麼還可以稱作SPA嗎? 當然可以,最簡單的判別方式就在於資料流是否與傳統的HTML網站相同。

經過磨練,我們都理解到React網站僅會在第一次載入網站時發出所有請求,在後續網頁的變動上,僅會更動需要更動的組件,這就是React相較於傳統網站最大的優勢。大幅減輕網站渲染的時間,降低使用者等待時間,達到提升網站使用的舒適度,仔細一想React比喻成跑車也不為過。因此只要每一個頁面都符合React的風格,那每一個頁面都可以稱做SPA,只不過搭配Route後,網站就會升級成Multi-SPA。廢話不多說,我們趕緊來看!

首先任何一個React專案下,我們只需要額外安裝react-router-dom第三方函式庫即可。在專案終端機上下npm install react-router-dom的指令,這裡要稍微注意react-router與react-router-dom兩個函式庫有些小差別。

安裝完成後,分別在components資料夾下建立Welcome.js和Travel.js兩個組件。接著App分別匯入兩個檔案,並且匯入react-router-dom的組件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Route } from 'react-router-dom';

import Welcome from './components/Welcome';
import Travel from './components/Travel';

function App() {
return (
<div>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/travel">
<Travel />
</Route>
</div>
);
}

export default App;

在這裡我們將Route組件匯入後就可以使用,使用Route組件方式和其他組件一樣,只不過它有專屬於自己的屬性。在Route組件的path可以用於設定URL的路徑,接著我們將Welcome和Travel兩個組件分別包在Route裡面,就可以告訴React在這個Url下,我們想要渲染的組件有哪些。但是僅只於此還不夠,因為我們必須要告知React如何去解析Route組件的path,我們還必須在index.js下裝上發動機制。

1
2
3
4
5
6
7
8
9
10
11
import ReactDOM from 'react-dom';
import App from './App';

import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
document.getElementById("root")
);

在index中,我們匯入BrowserRouter這個組件,緊接著將App組件包覆在其中,如此一來,Route就可以正式發動! 現在我們實際執行npm run start後,分別在URL/(Slash)後方加上welcome或者是travel就可以切換到對應的組件。不過我們這裡會有一個相當大的問題,那就是在切換URL的時候,網站都會重新載入,這是一個大問題! 因為這樣就不能稱作SPA跑車!

方便起見,我們多加入一個NavHeader.js的檔案,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NavLink } from 'react-router-dom';

const NavHeader = () => {
return (
<header>
<nav>
<ul>
<li>
<NavLink activeClassName="active" to="/welcome">Welcome</NavLink>
</li>
<li>
<NavLink activeClassName="active" to="/travel">Travel</NavLink>
</li>
</ul>
</nav>
</header>
)
};

export default NavHeader;

此處多出導引列(Nav),匯入NavLink的組件後,我們用一樣的方式去使用它。這邊注意到NavLink路徑屬性是to,務必不要跟Route的path搞混。NavLink很重要的一點就在於它能夠防止網頁重新載入,也就是每一個NavLink都有event.preventDefault這個函數功能。

NavLink還有activeClassName供我們使用,它的功能主要在於讓開發者為當下的Nav套上CSS的效果。為了方便觀看,我們在index.css直接加入下方的CSS,雖然這不是一個很好的做法,一般來說,都會為有各自的CSS檔案或者是CSS Module的檔案。

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
header {
height: 3rem;
width: 100%;
background: #eee;
}

ul {
list-style: none;
display: flex;
padding: 0;
margin: 0;
align-items: center;
justify-content: center;
}

ul li {
padding: 10px;
}

a {
color: black;
text-decoration: none;
margin: 0 15px;
}

a.active {
color:crimson;
}

h2 {
text-align: center;
}

實際執行的畫面會如下:
nav
點擊Welcome或Travel,可以發現網頁並不會重新載入,Chrome開發者工具的Network也確實沒有傳輸的動作,成功解決剛才的問題。

Switch

Switch的用法很簡單,基本上想成是JS的Switch也完全沒有問題。我們將App修改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Route, Switch } from 'react-router-dom';

import Welcome from './components/Welcome';
import Travel from './components/Travel';
import NavHeader from './components/NavHeader';

function App() {
return (
<div>
<NavHeader />
<Switch>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/travel">
<Travel />
</Route>
</Switch>
</div>
);
}

export default App;

直接用Switch包覆住整個Route,Switch會選擇其中一個Route執行,只要URL符合就不會再繼續執行。比方說我們多出新的一個Route是關於旅遊國家:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 同上

function App() {
return (
<div>
<NavHeader />
<Switch>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/travel">
<Travel />
</Route>
<Route path="/travel/:country">
</Switch>
</div>
);
}

這個時候,如果想要存取的URL是/travel/japan就無法成功存取,因為會在/travel就被Switch攔截下來。要解決這個問題,則必須多給Route屬性exact,確保完全一樣才執行特定的Route。因此需要在travel後面加上exact,變成<Route path="/travel" exact>,才能夠執行country的頁面。

現在要來講講關於Link,其實原本想著應該先講Link,但發現似乎要先講完前面的NavLink和Switch才比較不會搞混。眼尖的讀者應該有注意到在最後一個Route裡面有 : 的符號,這個符號的意思其實就是變數,將後方的文字視為變數,可以用來動態更動網址。我們將Travel改成下方的樣子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Travel = () => {
return <div>
<h2>Travel</h2>;
<ul>
<li>
<Link to="/travel/japan">Japan</Link>
</li>
<li>
<Link to="/travel/uk">UK</Link>
</li>
<li>
<Link to="/travel/us">US</Link>
</li>
</ul>
</div>;
};

這裡透過Link去連接,同樣跟NavLink一樣都有防止網頁重載的功能。Link和Route的區別在於Route符合目前URL會渲染裡面的組件,而Link用於切換當下URL頁面的組件,但不會影響到上層的組件,以這個例子來說就是Travel。接著我們還得再多新增一個Country.js的組件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useParams } from 'react-router-dom';

const Country = () => {
const params = useParams();

return (
<div>
<h2>Here is {params.country}.</h2>
</div>
)
};

export default Country;

特別注意這裡要引入useParams這個Hook,它的功用主要就是用來抓取URL的動態變數的部分,在這裡就是指:country。實際執行過後,大致如下:
japan

Redirect

最後說說Redirect,其實就是為了避免跑到一些奇怪的頁面,自動轉跳到正常頁面,我們修改一下App:

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
import { Route, Switch, Redirect } from "react-router-dom";

import Welcome from './components/Welcome';
import Travel from './components/Travel';
import Country from './components/Country';
import NavHeader from './components/NavHeader';

function App() {
return <div>
<NavHeader />
<Switch>
<Route path="/" exact>
<Redirect to="/welcome" />
</Route>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="/travel" exact>
<Travel />
</Route>
<Route path="/travel/:country">
<Country />
</Route>
</Switch>
</div>;
};

<Route path="/" exact>的情況下轉跳回/welcome的頁面,沒有太大問題,只是一樣要有exact才不會導致其他Route都無法使用。


結語

今天講述一下關於React的Route,稍微釐清一下基本概念以及一些小要點。相信Route並不有這些功能,但至少今天會是一個好的開始,先打穩根基再去研究更複雜的部分,才不會失焦。希望這篇文章能夠幫到需要的人。

謝謝各位看到最後。

Hooks家族 - useRef

We write to taste life twice, in the moment and in retrospect.
― Anais Nin

昨晚提及useReducer,說說它其實就是useState的強化版。今天要來講述另外一個不錯用的Hook,useRef。不學不會怎樣,但學了useRef,Code都變得簡潔俐落。我們趕緊進入正題!

  • useRef
    • useRef使用方式
  • 結語

useRef

同樣地,我們一樣先來看看官方的文件敘述:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
從Ref這個英文縮寫來看,可以簡單猜測出這個Hook可以用來參照某樣東西。上述官方解釋useRef會回傳一個傳入參數後初始化current屬性的變動參照物件,這個物件將持續在整個組件的生命期。換句話說,useRef會去對應到DOM的節點,只要節點一變動就會跟著變動。useRef特別的地方在於它所擁有的屬性current是可變動的值,但current變動並不會造成React重新渲染組件。

useRef使用方式

useRef使用方式:
const inputRef = useRef()
在某些情況下,我們會想要直接去存取某些DOM的元素,以此直接控制這些DOM元素本身的一些屬性。再者,我們或許會希望能夠直接在父組件控制子組件的DOM元素,這時候就會需要使用到useRef。先從最簡單的範例開始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Ref = () => {
const inputName = useRef();

const [name, setName] = useState('');

const nameSet = () => {
setName(inputName.current.value);
console.log(inputName.current.value);
}

return (
<div>
<label htmlFor="name">Name</label>
<input type="text" id="name" ref={inputName} value={name} onChange={nameSet}/>
</div>
)
}

在上述的例子當中,我們透過useRef建立一個用於儲存參照物件的常數變數。為了讀取input元素的值,我們將inputName設為input的ref屬性值。目前我們已經可以直接透過current屬性物件裡的value去抓取input這個欄位的值,同時我們設定一個state去抓取使用者變動過後欄位的值,當然我們也可以抓取name這個狀態的值,但useState並不是本篇的主角,就留給各位自行嘗試。

現在展示完最簡單的範例,我們接下來討論如何從父組件,去操作子組件的DOM。
Step1: 修改Ref.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Ref = React.forwardRef((props, ref) => {

const [name, setName] = useState('App Mount Start');

const nameChange = () => {
setName(ref.current.value);
console.log(ref.current.value);
};

return <div>
<label htmlFor="name">Name</label>
<input type="text" id="name" ref={ref} value={name} onChange={nameChange} />
</div>;
});

我們先將原先的程式碼修改成上方這樣,特別注意在組件函式新增的React.forwardRef,這段程式碼可以告知React將Ref這個組件視為可參照DOM的組件,包覆裡面的組件可以經由傳入ref供其他組件去參照定位。接著我們來到根組件。
Step2: 修改App.js(父組件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const App = () {
const inputRefByApp = useRef();

useEffect(() => {

// console.log(inputRefByApp.current); -> <input type="text" id="name" value="App Mount Start">
// console.log(inputRefByApp.current.value); -> 'App Mount Start'
inputRefByApp.current.focus();
}, [])

return (
<div>
<Ref ref={inputRefByApp}/>
</div>
);

匯入Hooks的部分就不再贅述。我們在根組件新增一個ref,這個ref基本上和一開始在Ref.js建立的概念是一樣。只是要特別注意必須將inputRefByApp傳入Ref這個組件。Ref組件必須是React.forwardRef型式的組件,才會擁有ref的屬性,這也是為什麼我們必須特地宣告的用途。測試過後,我們發現App根組件確實可以操作Ref組件的DOM,這裡指的是input元素。至於App,我們緊緊只是透過useEffect去幫我們自動在組件載入完成後(參考React系列-useEffect),focus在Ref組件的input元素上,不妨試試看列印出inputRefByApp看看,看看究竟裡頭藏著那些秘密!!


結語

今天講解useRef,因為我自已真的想要對這個Hook有更深的理解。一開始學習到的時候,真的是一頭霧水,或者說現在也還是半頭霧水吧?(笑)。有時候都會想自己是不是真的瞭解一件事情,總是在打文章的過程中甚至舉範例的過程中,反覆去仔細思考自己打出來的這段話是否正確。既擔心自己不是真的懂,但真正擔心的是誤導他人,或許在不遠的將來,會回來嘲笑現在的自己也說不定。我確實很擔心自己弄錯,但是在Coding的路上一定是跌跌撞撞的吧,你們相信有人完全沒有出現Bug就成功打造出一個產品嗎?也許吧!但,那肯定不是我。我並不是想要貶低、否定自己,而只是很單純單純地,認清自己。

今天的天空好藍,澄澈的天空多久沒有看見了呢? 但也是有點擔心不再下雨的日子,祈禱在不久的未來會下雨,不用多,剛剛好就好,也希望疫情趨緩下來。

謝謝看到最後的你/妳。

Hooks家族 - useReducer

Don't be pushed around by the fears in your mind. Be led by the dreams in your heart.
― Roy T. Bennett

上一次講述useContext的性質主要是管理變數。今天則是要來介紹useState的強化版,useReducer!最近這幾天在學習使用Hooks,雖然我還是沒有開竅,但卻對於Hooks所帶來的改變深深著迷,像是useRef、useCallback,甚至是客製化useHooks,我都覺得相當有趣。好的,廢話不多說,我們馬上來認識useReducer吧!

  • useReducer
    • useReducer使用方式
  • 結語

useReducer

為什麼我會說useReducer是useState的強化版? 我們來看一段官方文件所述的話:
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
大致上的意思是說當我們有複雜的狀態邏輯或者是狀態依賴先前的狀態時,我們就可以考慮使用useReducer,這句話聽起來就像是useState的強化版。我們一般處理狀態時會為各自的狀態寫一個useState:

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 Counter = () => {
const [count, setCount] = useState(0);
const [addText, setAddText] = useState('');
const [minusText, setMinusText] = useState('');

const addCountHandler = () => {
setCount(count+1);
setAddText("I am adding!")
}

const minusCountHandler = () => {
setCount(count-1);
setMinusText("I am minusing!");
}

return (
<div className="counter">
<button onClick={addCountHandler}>Add</button>
<p>{addText} to {count} </p>
<button onClick={minusCountHandler}>Minus</button>
<p>{minusText} to {count}</p>
</div>
)
}

其實這邊寫得相當的繁瑣,有點不符合實際情況。不過沒關係,這邊只是單純展示使用useReducer的時機。從上述程式碼,我們可以看見當我們要設定增減按鈕時,需要為增減的行為設定一個useState,若我們想再加上文字的變更,必須再多新增文字各自的狀態。透過useState,已經減輕不少工作,但,礙於人類的天性,我們還是覺得這樣過於繁瑣。

這時候useReducer的就派上用場了!
useReducer往往會搭配useContext使用,但我們這邊先專注在useReducer本身。

useReducer使用方式

useReducer是useState的一種替換寫法。它的長相大概如下所示:
const [state, dispatch] = useReducer(reducer, initialArg)

主要的概念是透過useReducer將reducer這個函數串接進來,reducer則會根據dispatch去決定要執行的動作。state會根據initialArg去決定型態,若initialArg是物件,則state也會是物件。同樣地,dispatch也會是物件的型態,裡頭會儲存各種方法,以這邊的例子來說,就是增減的方法。我們來實際看看如何改寫上述的計時器:

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
const defaultCountState = {
count: 0,
text: ""
};

const countReducer = (state, action) => {
switch(action.type){
case 'ADD':
return {
count: state.count + 1,
text: "I am adding."
};
case 'MINUS':
return {
count: state.count - 1}
text: "I am minusing."
};
default:
throw new Error();
}
}

const Counter = () => {
const [countState, dispatchCount] = useReducer(countReducer, defaultCountState);

const addCountHandler = () => {
dispatchCount({type: 'ADD'});
};

const minusCountHandler = () => {
dispatchCount({type: 'MINUS'});
}

return (
<div className="counter">
<p>Count: {countState.text} {countState.count} </p>
<button onClick={addCountHandler}>Add</button>
<button onClick={minusCountHandler}>Minus</button>
</div>
)
}

寫好count的reducer,裡面包含增減對應的方法,接著再透過useReducer將方法以及我們預設的變數傳入Counter組件。最後當我們實際執行上述的程式碼的時候,就可以發現,透過useReducer,我們成功同時控制文字與數字的狀態。好像有點小興奮!沒錯,這就是我們useReducer的Power所在!
count

-重點回顧-

  • useReducer可以控管複雜的狀態
    • reducer是一組函數
    • state、dispatch一般都是物件

結語

今天簡單講解useReducer這個Hook,熟悉Redux的各位大大一定不陌生這個Hook,但心中可能會有疑惑為何有Redux又需要useReducer呢? 這就因人而異,只要能達到目的,我想用哪種方法都沒有對錯的分別,全取決於個人。在這篇章,我們還沒提到global變數的概念,一般來說useReducer都會搭配useContext的使用去處理資料,接下來的篇章也許會討論這塊也說不定。

useReducer並不是要完全取代useState,它只有當狀態變得複雜時才適合使用,換句話說在處理簡單的狀態時,就不需要去使用它,畢竟殺雞焉用牛刀是吧?

題外話: 今天一直在想要怎麼去寫useReducer,最後坐在馬桶上還是覺得簡單用Count去解釋好了,原先考慮用自己實作的訂餐網站來講解,但似乎不適合當作介紹篇章的範例。

最後,謝謝各位大大看到最後!

0%