正在找工作,有沒有大佬帶帶我的?

前言

本人是個 React 小白,用了快一個月時間 React 了,感覺還挺好用的。最近剛好看到了新的 React Hooks API 貌似討論挺激烈,就順便看了一眼,簡單說下感覺。

方便了很多

更方便元件拆得更小

更方便把資料、業務邏輯拆到元件外面

基本上就是真香預定了。

不過呢,最讓我驚訝的是,我一直以為 React 以及其生態是標榜「純」的,居然還能把副作用玩那麼6,這是我沒有想到的。

所以呢,我當然是自己手擼了一個出來:

先講講副作用

「純」是什麼,React 系的小夥伴並不會陌生,但與「純」相對的副作用,很多小夥伴就開始犯迷糊了。為什麼會有「純」與「不純」的區別,為什麼在「函式式」的領域裡面,大家會那麼在乎純呢?

其實吧,道理很簡單。以一個函式為例,想想我們執行一個函式的過程。

你以為的函式執行過的公式是:

函式 + 引數 = 返回值

但上面這個公式是錯誤的,只有純函式的情況下才滿足上面公式,一般情況下都是如下公式:

函式 + 引數 + 環境 = 返回值 + 環境'

一個純函式意味著,這個函式的執行既不會被外部的環境所改變,也無法改變外部的環境。

透過副作用執行時注入狀態

副作用這東西,就跟 goto,eval 等等各種規範裡面嚴禁使用的東西一樣,具有很強的自由度和很強不可控屬性,用的好可以實現各種奇技淫巧,用的不好坑死人不償命。我們這裡講的就是副作用的奇技淫巧之一,如何透過副作用,給函式注入狀態。

我們先拋開 React 獨立從一個函式的角度來看這件事情,我們要現在有一個 useState函式和一個render 函式,現在要給 render 函式用 useState 注入狀態,讓他每一次執行都不一樣,怎麼做呢?

let

state

function

useState

defaultState

{

function

setState

newState

{

state

=

newState

}

if

state

{

state

=

defaultState

}

return

state

setState

}

function

render

()

{

const

state

setState

=

useState

0

console

log

state

setState

state

+

1

}

這時候我們只需要在外面存在一個變數,就能記錄這個函式的狀態了,第一次執行初始化狀態,第二次執行就已經有狀態了。

嘗試多個狀態

上面我們實現的函式只能設定一個狀態,我們如何設定多個狀態,同時還能在函式執行的時候恢復狀態呢?

這時候情況稍微就複雜一些了,我們需要多加一個計數器,用於記錄多個狀態的序列,並且每次執行結束或者執行開始需要把計數器置為0。

useState的順序是一個序列,我們用一個 key 為 number 的 map 來記錄 useState的序列。

最後我們寫一個函式包裝一下我們具體的render 函式。

let

states

=

{}

let

currentNu

=

0

function

useState

defaultState

{

const

nu

=

currentNu

++

function

setState

newState

{

states

nu

=

newState

}

if

states

nu

])

{

states

nu

=

defaultState

}

return

states

nu

],

setState

}

function

withState

func

{

return

(。。。

args

=>

{

currentNu

=

0

return

func

(。。。

args

}

}

const

render

=

withState

function

render

()

{

const

state

setState

=

useState

0

const

state1

setState1

=

useState

1

const

state2

setState2

=

useState

2

console

log

state

state1

state2

setState

state

+

1

setState1

state1

+

2

setState2

state2

+

3

}

狀態堆與上下文棧

一個函式的例子我們解決了,那多個函式怎麼辦呢?多個函式怎麼辦呢?多個函式最複雜的情況,是相互呼叫,那麼相互呼叫就會打亂我們記錄的useState順序,怎麼辦?

我們先看看函式互相呼叫為什麼就沒有打亂計算機裡面的狀態呢?原因是函式呼叫的時候會變生一個函式棧,那我們就用函式該有的解決方式——棧來解決問題。

同樣的,在函式呼叫的時候,函式會把狀態等東西存在堆空間裡,我們也建一個堆來儲存狀態,這個堆直接用函式閉包存起來就行了。

let

contextStack

=

[]

function

useState

defaultState

{

const

context

=

contextStack

contextStack

length

-

1

const

nu

=

context

nu

++

const

{

states

}

=

context

function

setState

newState

{

states

nu

=

newState

}

if

states

nu

])

{

states

nu

=

defaultState

}

return

states

nu

],

setState

}

function

withState

func

{

const

states

=

{}

return

(。。。

args

=>

{

contextStack

push

({

nu

0

states

})

const

result

=

func

(。。。

args

contextStack

pop

()

return

result

}

}

const

render

=

withState

function

render

()

{

const

state

setState

=

useState

0

render1

()

console

log

‘render’

state

setState

state

+

1

}

const

render1

=

withState

function

render1

()

{

const

state

setState

=

useState

0

console

log

‘render1’

state

setState

state

+

2

}

這樣,我們哪怕遞迴,都不會搞亂我們程式應有的狀態了。

結合React

上面,我已經講完了實現的基本原理,那麼最後就是結合 React 。不過,我們包裝函式生成的不再是另一個函式,而是一個 React 元件。因為 React 元件自身能夠儲存個管理狀態,我們可以直接用,避免了我們要手動管理狀態的麻煩。這裡的React 元件,和之前分配一個堆的意義是一樣的。

程式碼的話就直接看下面吧,我就就不一一寫了。

到這裡,大家對於給一個函式注入狀態的技巧大家學會了嗎?