iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
JavaScript

JavaScript為什麼筆記本系列 第 22 篇

Day 22|setCount(prev => prev + 1) 裡面為什麼可以塞 function?

  • 分享至 

  • xImage
  •  

Day 21 最後留下了一段我以前其實會寫,但沒有真的想過的程式:

setCount(prev => prev + 1)

如果把它看久一點,事情突然很怪。

以前我以為 setCount() 裡面應該放的是:

setCount(1)
setCount(2)
setCount(3)

也就是「下一個 state 的值」。

結果現在卻可以放:

prev => prev + 1

這根本不是數字。

它是一個 function。

所以真正的問題變成:

為什麼 function 可以像數字、字串一樣,被塞進另一個 function 裡?

而且更怪的是:

我沒有自己呼叫它,React 為什麼還可以晚一點拿它來執行?

這篇先不碰 Event Loop,也不挖 React 內部實作。

我只想把一件事搞清楚:

JavaScript 裡的 function,到底是不是一種「值」?


答案是:

在 JavaScript 裡,function 本身就是一種可以被保存、傳遞、再呼叫的值。

所以這段:

prev => prev + 1

不是「現在立刻算答案」。

它是在建立一個 function value。

接著:

setCount(prev => prev + 1)

是在把這個 function 交給 React。

React 之後處理 state 更新時,再用目前該輪更新拿到的 state 去呼叫它。

所以整件事比較像:

我建立一個 function
        ↓
把 function 交給 setCount
        ↓
React 保存這個 updater
        ↓
React 處理 state 更新
        ↓
把前一個 state 傳給 updater
        ↓
updater 回傳新的 state

我以前只看到一行箭頭函式。

現在才開始看到:

原來這裡其實發生了「把 function 當資料傳出去」這件事。


先把箭頭函式拆開

先看:

prev => prev + 1

這段其實可以改寫成:

function addOne(prev) {
  return prev + 1
}

所以:

setCount(prev => prev + 1)

也可以想成:

function addOne(prev) {
  return prev + 1
}

setCount(addOne)

這樣突然清楚很多。

我不是在呼叫:

addOne()

而是把:

addOne

這個 function 本身傳進去。

這跟 Day 17 的:

<button onClick={handleClick}>

其實是同一種思路。

當時最重要的差別是:

handleClick

代表:

把 function 交出去。

而:

handleClick()

代表:

現在立刻呼叫 function,把它的回傳值交出去。

所以 Day 17 和 Day 22 其實接起來了。


Function 真的可以像值一樣放進變數

先離開 React。

最普通的 JavaScript 就可以做:

const addOne = function (number) {
  return number + 1
}

也可以:

const addOne = number => number + 1

這裡右邊不是先算出一個數字。

右邊就是一個 function value。

所以:

const addOne = number => number + 1

和:

const name = 'Anthony'
const count = 3

雖然值的種類不同,但「把一個值放進變數」這個動作很像。

概念上:

name
↓
字串值

count
↓
數字值

addOne
↓
function 值

這也是為什麼可以:

console.log(typeof addOne)

得到:

function

我以前會把 function 想成:

一段特別的程式碼區塊。

這沒有錯,但還不夠。

在 JavaScript 裡,它同時也是:

一個可以被參考、保存和傳遞的值。


Function 可以當參數傳進另一個 Function

如果 function 是值,那它就可以被當成 argument。

先做一個完全不需要 React 的例子:

function runOperation(operation, value) {
  return operation(value)
}

function double(number) {
  return number * 2
}

const result = runOperation(double, 5)

console.log(result)

結果:

10

一開始我可能會只看到:

runOperation(double, 5)

但把流程拆開:

double
↓
是一個 function value
↓
傳進 runOperation
↓
在 runOperation 裡變成 operation
↓
operation(value)
↓
其實就是 double(5)
↓
10

這裡最重要的是:

runOperation 不需要事先知道你傳進來的一定叫 double。

它只知道:

我收到一個 function,等一下我會呼叫它。

這就讓 function 變得非常有彈性。


那 Callback 到底是什麼?

以前看到 callback,我腦袋常會把它和非同步綁在一起。

像:

setTimeout(() => {
  console.log('hello')
}, 1000)

所以很容易以為:

callback = 非同步。

其實 callback 更基本的意思是:

我把一個 function 交給另一段程式,讓對方在適合的時機呼叫它。

例如:

const numbers = [1, 2, 3]

numbers.map(number => number * 2)

這裡:

number => number * 2

也是把 function 傳給 map()。

map() 再一個一個拿 Array 裡的值呼叫它。

流程:

[1, 2, 3]
↓
map 收到一個 function
↓
function(1) → 2
function(2) → 4
function(3) → 6
↓
[2, 4, 6]

所以 Day 11 的 menuItems.map(),其實早就遇過「function 當值傳進去」了。

只是當時我的注意力都放在 Array 怎麼變成 JSX,沒有特別停下來看這件事。


setCount() 也是類似的模式

現在回到:

setCount(prev => prev + 1)

可以先改寫:

function increase(prev) {
  return prev + 1
}

setCount(increase)

我做的事情不是:

我先算出新的 count
↓
把數字交給 React

而是:

我把「怎麼從舊 state 算出新 state」的 function
交給 React

React 處理這個 state update 時,會把前一個 state 傳進 updater。

概念上像:

目前 pending state = 0
↓
increase(0)
↓
1
↓
下一個 updater 再拿到 1

所以 Day 21 的:

setCount(prev => prev + 1)
setCount(prev => prev + 1)
setCount(prev => prev + 1)

才能形成:

0
↓
1
↓
2
↓
3

這裡真正的關鍵不只是 React。

而是 JavaScript 先允許:

function 可以被當成值傳遞。

React 才能設計出 updater function 這種 API。


「傳 function」和「傳 function 的結果」差在哪?

這個差異非常值得再看一次。

假設:

function addOne(number) {
  return number + 1
}

寫 addOne

someFunction(addOne)

代表:

把 function 本身交出去。

寫 addOne(3)

someFunction(addOne(3))

則是:

先執行 addOne(3)
↓
得到 4
↓
再把 4 傳給 someFunction

所以:

addOne
↓
function value

addOne(3)
↓
呼叫 function 之後得到的結果

這跟 React:

onClick={handleClick}

和:

onClick={handleClick()}

本質上是同一個差別。

我以前會覺得只是「有沒有括號」。

現在會比較想成:

我到底是在傳一個 function,還是現在就把它執行掉?


Function 還可以從另一個 Function 回傳

既然 function 是值,那它不只可以被傳進去,也可以被 return 出來。

例如:

function makeMultiplier(times) {
  return number => number * times
}

const double = makeMultiplier(2)

console.log(double(5))

結果:

10

先拆:

makeMultiplier(2)
↓
回傳一個 function
↓
const double = 那個 function
↓
double(5)
↓
10

這件事現在先看到就好。

因為如果繼續追:

為什麼回傳出去的 function 還記得 times = 2?

很快就會掉進另一個大洞:

Closure

這篇先不要。

但至少我開始知道為什麼 JavaScript 的 function 會一直出現在奇怪的位置。

因為它不只是「宣告完等著被呼叫」。

它本身就能在程式裡被搬來搬去。


一個最小實驗

可以直接在瀏覽器 Console 跑:

function useOperation(operation, value) {
  console.log('收到的 operation:', typeof operation)
  return operation(value)
}

const addTen = number => number + 10

console.log(useOperation(addTen, 5))

預期:

收到的 operation: function
15

接著故意改成:

console.log(useOperation(addTen(5), 5))

這次 addTen(5) 會先被執行,得到:

15

也就是 useOperation 收到的不再是 function,而是 number。

如果它後面還想:

operation(value)

就會出錯。

這個實驗很直接地把兩件事分開:

addTen
↓
function 本身

addTen(5)
↓
function 執行後的回傳值

待驗證: 這篇先保留成文章草稿;之後實際在 DevTools Console 跑一次,再把 observed output 補回 knowledge note。


原來很多 React 寫法都是同一件事

現在回頭看幾個已經遇過的 React 寫法。

onClick

<button onClick={handleClick}>

把 function 交給 React。

等使用者 click 時,React 再呼叫。

map()

menuItems.map(item => <li>{item.label}</li>)

把 function 交給 map()。

map() 每拿到一個 item,就呼叫一次。

setCount() updater

setCount(prev => prev + 1)

把 function 交給 React。

React 在處理 state update 時,把前一個 state 傳進去。

看起來三個 API 完全不一樣。

但 JavaScript 底層有一個共同點:

function 可以是一個值。

突然就沒那麼像三套獨立魔法了。


最容易搞錯的地方

誤解 1:Function 一定要寫名字才能傳

不是。

這就是為什麼可以直接寫:

number => number + 1

這是一個匿名的 arrow function,也是一個 function value。

誤解 2:Callback 一定是非同步

不是。

map() 的 callback 就可以同步被呼叫。

callback 描述的是:

把 function 交給別人,由對方呼叫。

至於是不是非同步,是另一個問題。

誤解 3:setCount(prev => prev + 1) 裡的 prev 是我自己傳進去的

不是。

我提供的是 updater function。

React 在處理更新時,會用前一個 state 呼叫這個 updater。

prev 只是我幫那個參數取的名字。

所以也可以寫:

setCount(x => x + 1)

只是 prev 比較容易看懂它的角色。

誤解 4:加上 () 只是語法小差異

不是。

handleClick

和:

handleClick()

代表的時間點完全不同。

一個是在傳 function。

另一個是在現在就執行 function。


如果今天只記得一件事

JavaScript 的 function 不只是可以被呼叫的程式碼,它本身也是一個值,所以可以被存進變數、當參數傳遞,也可以從其他 function 回傳。

這件事一懂,很多以前看起來很 React 的東西:

onClick={handleClick}
map(item => ...)
setCount(prev => prev + 1)

其實都可以先退回 JavaScript 來理解。


我現在能回答一開始的問題了嗎?

為什麼:

setCount(prev => prev + 1)

裡面可以塞 function?

因為:

prev => prev + 1

本身就是一個 function value。

我把它交給 setCount,React 之後再用前一個 state 去呼叫它。

所以不是:

setCount 只能收數字
但這裡很神奇地破例收 function

而是:

JavaScript 本來就能把 function 當值傳遞
↓
React 利用這個能力設計 updater function
↓
讓我描述「如何從前一個 state 算下一個 state」


上一篇
Day 21|連續寫三次 setCount(count + 1),為什麼不一定會加 3?
下一篇
Day 23|function、function expression、arrow function 為什麼三種?
系列文
JavaScript為什麼筆記本 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言