函數式編程看React Hooks(一)簡單React Hooks實現javascript
函數式編程看React Hooks(二)事件綁定反作用深度剖析html
函數式編程介紹(摘自基維百科)java
函數式編程(英語:functional programming)或稱函數程序設計、泛函編程,是一種編程範式,它將計算機運算視爲函數運算,而且避免使用程序狀態以及易變對象。其中,λ演算(lambda calculus)爲該語言最重要的基礎。並且,λ演算的函數能夠接受函數看成輸入(引數)和輸出(傳出值)。react
面向對象編程介紹(摘自基維百科)git
面向對象程序設計(英語:Object-oriented programming,縮寫:OOP)是種具備對象概念的程序編程典範,同時也是一種程序開發的抽象方針。它可能包含數據、屬性、代碼與方法。對象則指的是類的實例。它將對象做爲程序的基本單元,將程序和數據封裝其中,以提升軟件的重用性、靈活性和擴展性,對象裏的程序能夠訪問及常常修改對象相關連的數據。在面向對象程序編程裏,計算機程序會被設計成彼此相關的對象github
函數式強調在邏輯處理中不變性。面向對象經過消息傳遞改變每一個Object的內部狀態。二者是大相徑庭的編程思想,都具備本身的優點,也由於如此,才使得咱們從 class 組件
轉化到 函數組件式
,有一些費解。編程
從 react 的變化能夠看出,react 走的道路愈來愈接近於函數式編程,輸入輸出一致性。固然也不是憑空地去往這個方面,而是爲了可以解決更多的問題。如下 三點是 react 官網所提到的 hooks 的動機 zh-hans.reactjs.org/docs/hooks-…segmentfault
本文是爲了給後面一篇文章做爲鋪墊,由於在以後文章的講解過程當中,你若是了理解了 React Hooks 的原理,再加上一步一步地講解,你可能會對 React Hooks 中各類狀況會恍然大悟。數組
一開始的時候以爲 hooks 很是地神祕,寫慣了 class 式的組件後,咱們的思惟就會定格在那裏,生命週期,state,this等的使用。 所以會以 class 編寫的模式去寫函數式組件,致使咱們一次又一次地爬坑,接下來咱們就開始咱們的實現方式講解。(提示:如下是都只是一種簡單的模擬方法,與實際有一些差異,可是核心思想是一致的)緩存
咱們先寫一個簡單的 react 函數式組件。
function Counter(count) {
return (
<div> <div>{count}</div> <button> 點擊 </button> </div>
);
}
複製代碼
在 React Hooks 還未出現的時候,咱們的組件大多用來直接渲染,不含有狀態存儲,Function組件沒有state,因此也叫SFC(stateless functional component),如今更新叫作FC(functional component)。
爲了使得一個函數內有狀態,react 使用了一個特別的方法就是 hooks, 其實這是利用閉包實現的一個相似做用域的東西去存儲狀態,我第一想到的就是利用對象引用存儲數據,就像是面向對象同樣的方式,存在一個對象中中,經過引用的方式來進行獲取。可是 react 爲了可以儘量地分離狀態,精妙地採用了閉包。
讓咱們看看他是如何實現的。(爲了儘量簡化,我進行了改編)
let _state;
function useState(initialState) {
_state = _state || initialState; // 若是存在舊值則返回, 使得屢次渲染後的依然能保持狀態。
function setState(newState) {
_state = newState;
render(); // 從新渲染,將會從新執行 Counter
}
return [_state, setState];
}
複製代碼
function Counter() {
const [count, setCount] = useState(0);
return (
<div> <div>{count}</div> <button onClick={() => setCount(count + 1)}> 點擊 </button> </div>
);
}
複製代碼
演示地址: codesandbox.io/s/dawn-bash…
以上,無論 Counter 從新渲染多少次,經過閉包,依然可以訪問到最新的 state,從而達到了存儲狀態的效果。
再看看 useEffect, 先來看看使用方法。 useEffect(callback, dep?)
, 如下是一個很是簡單的使用例子。
useEffect(() => {
console.log(count);
}, [count]);
複製代碼
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
return (
<div> <div>{count}</div> <button onClick={() => setCount(count + 1)}> 點擊 </button> </div>
);
}
複製代碼
由於函數式不像 class 那樣有複雜的生命週期,已經對 hooks 已經熟悉使用的你,可能會知道 useEffect
能夠當作,componentdidmount
來使用。可是在這裏你直接將他按照順序執行。在 return
前他會執行。
let _deps = {
args: []
}; // _deps 記錄 useEffect 上一次的 依賴
function useEffect(callback, args) {
const hasChangedDeps = args.some((arg, index) => arg !== _deps.args[index]); // 兩次的 dependencies 是否徹底相等
// 若是 dependencies 不存在,或者 dependencies 有變化
if (!_deps.args || hasChangedDeps) {
callback();
_deps.args = args;
}
}
複製代碼
演示地址: codesandbox.io/s/ecstatic-…
至此,咱們也實現了單個 useEffect。
咱們再來看看, useMemo,其實他也以上實現的方式同樣,也是經過閉包來進行存儲數據, 從而達到緩存提升性能的做用。
function Counter() {
const [count, setCount] = useState(0);
const computed = () => {
console.log('我執行了');
return count * 10 - 2;
}
const sum = useMemo(computed, [count]);
return (
<div> <div>{count} * 10 - 2 = {sum}</div> <button onClick={() => setCount(count + 1)}> 點擊 </button> </div>
);
}
複製代碼
接下來咱們來進行實現
let _deps = {
args: []
}; // _deps 記錄 useMemo 上一次的 依賴
function useMemo(callback, args) {
const hasChangedDeps = args.some((arg, index) => arg !== _deps.args[index]); // 兩次的 dependencies 是否徹底相等
// 若是 dependencies 不存在,或者 dependencies 有變化
if (!_deps.args || hasChangedDeps) {
_deps.args = args;
_deps._callback = callback;
_deps.value = callback();
return _deps.value;
}
return _deps.value;
}
複製代碼
演示地址: codesandbox.io/s/festive-p…
那麼 useCallback
呢? 其實就是 useMemo
的一個包裝,畢竟你緩存函數的返回值,那麼我我讓返回值爲一個函數不就好了?
function useCallback(callback, args) {
return useMemo(() => callback, args);
}
複製代碼
能夠看到,以上咱們也輕鬆地實現了 useMemo 。可是有一個問題,以上只是單個函數使用方式,因此接下來咱們還須要處理一下多個函數的狀況。
咱們能夠按照 preact 的方法來實現。即用數組來實現多個函數的處理邏輯。
核心邏輯就是
第一次聲明的時候將 useState, useEffect, useMemo, useCallback 等鉤子函數的狀態依次存入數組。
更新的時候,將前一次的函數狀態值依次取出。
也能夠經過如下圖來理解
第一次渲染,將每一個狀態都緩存到數組中。
每次從新渲染,獲取數組中每一個的緩存狀態。
如下爲了可以清晰地讓你們明白原理,進行了一些刪減。可是核心邏輯不變。
let currentIndex = 0;
let currentComponent = {
__hooks: []
};
function getHookState(index) {
const hooks = currentComponent.__hooks;
if (index >= hooks.length) {
hooks.push({});
}
return hooks[index];
}
function argsChanged(oldArgs, newArgs) {
return !oldArgs || newArgs.some((arg, index) => arg !== oldArgs[index]);
}
function useState(initialState) {
const hookState = getHookState(currentIndex++);
hookState._value = [
hookState._value ? hookState._value[0] : initialState,
function setState(newState) {
hookState._value[0] = newState;
render(); // 從新渲染,將會從新執行 Counter
}
];
return hookState._value;
}
function useEffect(callback, args) {
const state = getHookState(currentIndex++);
if (argsChanged(state._args, args)) {
callback();
state._args = args;
render();
}
}
function useMemo(callback, args) {
const state = getHookState(currentIndex++);
if (argsChanged(state._args, args)) {
state._args = args;
state._callback = callback;
state.value = callback();
return state.value;
}
return state.value;
}
複製代碼
如今用以上 43 行代碼實現了一個簡易的 React Hooks。
咱們再經過一次總體的流程圖來說解完整版的實現。
function Counter() {
const [count, setCount] = useState(0);
const [firstName, setFirstName] = useState("Rudi");
const computed = () => {
return count * 10 - 2;
};
const sum = useMemo(computed, [count]);
useEffect(() => {
console.log("init");
}, []);
return (
<div> <div> {count} * 10 - 2 = {sum} </div> <button onClick={() => setCount(count + 1)}>點擊</button> <div>{firstName}</div> <button onClick={() => setFirstName("Fred")}>Fred</button> </div>
);
}
複製代碼
將全部的狀態都存進閉包中。
改變了第二個狀態的value值。
將全部狀態依次取出,進行渲染。
經過以上的實現,咱們也能夠明白一些 React Hooks 中看似有點奇怪的規定了。例如爲何不要在循環、條件判斷或者子函數中調用? 由於順序很重要,咱們將緩存(狀態)按必定地順序壓入數組,因此取出上一次狀態,也必須以一樣的順序去獲取。不然的話,會致使獲取不一致的狀況。。。固然咱們能夠試想一下,若是每一個狀態單元,能夠有惟一的名字,那麼將不會受到這些規定的約束。可是這樣會使得開發帶來額外的傳入參數,就是惟一的名字。也會帶來名字衝突等問題,所以採用這樣的方式來約定,必定程度上簡化了開發者的開發成本,而且也可以消除不一致性。(ps: 若是有人有興趣,能夠實現一版不依賴於順序,只依賴於名字的,當作小玩具~)
固然真實中的 react 是利用了單鏈表來代替數組的。略微有些不同,可是本質的思路是一致的,以及 useEffect 是每次渲染完成後運行的。
以上都是站在巨人的肩膀上(有不少優秀的文章,看參考),再加上查看一些源碼得出的整個過程。最後,留出一個小問題給你們,那麼每次 useEffect
中 return 函數
的邏輯又是怎麼樣的呢?歡迎評論區說出實現方式~ 若是文章有任何問題,也歡迎在評論區指出~
zh-hans.reactjs.org/docs/hooks-…
zh-hans.reactjs.org/docs/hooks-…