1. 為什麼需要React Hooks ? React Hooks 是 React16.7.0-alpha版本推出的新特性。在Hoooks發布之前React定義組件有兩種方法,一種是類組件,一種是函數式組件。之前這兩種定義組件的方式有什麼不妥呢?為啥還需要Hooks?為什麼需要類組件,只有函數式組件不行嗎? 因為我們組件中需要 狀態 然後我們還需要生命周期來執行不同階段的事物,我們還需要有一些副作用的操作,例如交互請求。所以我們一定要需要類組件,函數式組件中沒有。 在我們在做分離業務邏輯,拆分組件,以達到更多業務邏輯復用時候,當我們遇到一個相對複雜的組件時候,因為此組件有很多狀態,我們無法將其提取到函數式組件中,因為函數式組件,遵循簽名是UI=f(state),函數式組件是無狀態的,也無生命周期的,所以hook應運而生,一個既有狀態又有生命周期的函數式組件,它讓我們重新思考了函數式組件的發展形態,正如TNT重新定義了下一個十年(bad smile),react也在重新定義函數式組件該有的樣子。 上面說的是第一點,個人覺得第二點就是Hooks體現了React在組件內部進行邏輯隔離,之前只是在組件與組件之間擁有天然隔離,明確數據流以及可以進行Hoc層層嵌套,現在不用層層嵌套也可以把數據狀態共享問題解決掉。Hoc層層嵌套就是很大缺點,它做的也是解決數據狀態共享問題。在Hooks中實現的是 『狀態細粒度劃分』,『狀態以及變更邏輯隔離』,更加體現了web component 中的設計原則。
  2. React Hooks 有哪些api?api 使用限制:

    1. 只能在頂層調用Hooks。不要在循環,條件或嵌套函數中調用Hook。

    2. 僅從React功能組件調用Hooks。不要從常規JavaScript函數中調用Hook。 (還有另一個地方可以調用Hooks——你自己的定製Hooks。)
  • useState

const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<>
<h1>{count}</h1>
<button onClick={increment}>+</button>
</>
)
}

1) useState(0) 返回是個數組形式,參數一為參數名,參數二為參數名,useState(0)代表count的初始值是0,useState現階段只能傳入一個初始值。

2) useState類似setState,你可以看做是非同步的。

3) useState 必須保證執行順序一一致,React為每一次的useState調用分配一個空間,通過useState調用順序辨別各個空間。下面會導致變數名以及方法名不一致,防止槽不對應。

const Counter = () => {
const [count, setCount] = useState(0);
if(count%2===0){
const [ bar, setBar] = useState(null);
}
const [foo,setFoo] = useState(foo);
}

  • useEffect1)可以把useEffect Hook 視作componentDidMount、componentDidUpdate合體。通過useEffect的第二個參數告訴 React 用到了哪些外部變數,來決定是否更新時候執行。可以定義多個useEffect函數,會一次進行調用。

useEffect(() => {
// 每次componentDidMount或者componentDidUpdate都會調用這裡
})

2)useEffect 可以傳入第二個操作來避免性能的損耗,如果第二個參數數組中的成員變數沒有變化則會跳過此次改變。如何傳入一個空數組 ,說明每次都不改變,那麼該 effect 只會在組件 mount 和 unmount 時期執行。

useEffect(() => {
// 每次componentDidMount會調用這裡
},[])

3)如果需要在componentDidMountUnmount,需要執行一些事件,可以再return返回時候進行操作 ,componentDidMountUnmount也是唯一一個hook有,class中沒有的生命周期。

useEffect(() => {
//只有componentDidMount時候調用這裡
return () => {
// 只有componentDidMountUnmount時候調用這裡
};
}, [])

4)useEffect 模擬 componentDidpdate,useRef是永遠不會改變的一個hooks,每次返回值都是相同的。

const mounted = useRef();
useEffect(() => {
if(!mounted.current){
mounted.current = true;
} else {
// 執行 componentDidpdate
}
})

3. React Hooks 工作原理

用React hooks: not magic, just arrays裡面話來說Hooks只是數組,不是什麼神奇的魔法。

讓我們在這裡通過一個例子來演示狀態 hooks 的實現如何工作。

function RenderFunction() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");

return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}

hooks API背後的想法是你可以使用一個setter函數作為hook函數中的第二個數組項返回,而setter將控制由hook管理的狀態。這意味著此處存儲的數據位於正在渲染的組件之外。 此狀態不與其他組件共享,但它保留在可以隨後渲染特定組件的範圍內。

1)初始化

創建兩個空數組:settersstate

將游標設置為 0 ;

初始化:兩個空數組,Cursor為0;

2) 首次渲染

首次運行組件功能。

每次useState()調用,當在第一次運行時,將setter函數(綁定到游標位置)推送到setter數組,然後將某個狀態推送到state數組。

第一次渲染:作為游標增量寫入數組的項目。

3) 後續渲染

每個後續渲染都會重置游標,並且只從每個數組中讀取這些值。

後續渲染:從數組中讀取的項目為游標增量

4) 事件處理

每個setter都有一個對它的游標位置的引用,因此通過觸發對任何setter的調用,它將改變狀態數組中該位置的狀態值。

Setters「記住」他們的索引並根據它設置內存。

Rudi Yardley簡單的實現了一下"簡陋"的hooks:

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}

// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}

const setter = setters[cursor];
const value = state[cursor];

cursor++;
return [value, setter];
}

// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1

return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}

// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}

console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: [Rudi, Yardley]
MyComponent();
console.log(state); // Subsequent-render: [Rudi, Yardley]

// click the Fred button

console.log(state); // After-click: [Fred, Yardley]

看完簡單的原理,我們來解釋一下為什麼不能在循環,條件判斷,嵌套函數中使用hooks:

let firstRender = true;

function RenderFunctionComponent() {
let initName;

if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");

return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}

這段代碼在條件渲染中使用hooks,第一次渲染時,內部的兩個數組映射關係如下:

我們可以看到firstName和setFirstName、lastName和setLastName有正確的對應關係,但是第二次渲染時:

firstName的值是initName,lastName的值是fistName,這種映射的關係就混亂了,所以這就是不要在循環,條件判斷,嵌套函數中使用hooks的原因。

推薦閱讀:

相关文章