前言
Hook
是 React 16.8
的新增特性。它是完全可選的
,並且100%向後兼容
。它可以讓你使用函數組件的方式,運用類組件以及 react 其他的一些特性,比如管理狀態
、生命週期鉤子
等。從概念上講,React 組件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。
優點:
1、代碼可讀性更強,原本同一塊功能的代碼邏輯被拆分在了不同的生命週期函數中,容易使開發者不利於維護和迭代,通過 React Hooks 可以將功能代碼聚合,方便閱讀維護。例如,每個生命週期中常常會包含一些不相關的邏輯。一般我們都會在 componentDidMount
和 componentDidUpdate
中獲取數據
。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯
,如設置事件監聽
,而之後需在 componentWillUnmount 中清除。相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關
的代碼
卻在同一個方法
中組合
在一起。如此很容易產生 bug,並且導致邏輯不一致
。
2、組件樹層級變淺。在原本的代碼中,我們經常使用 HOC/render props 等方式來複用組件的狀態,增強功能等,無疑增加了組件樹層數及渲染,在 React DevTools 中觀察過 React 應用,你會發現由 providers
,consumers
,高階組件
,render
props
等其他抽象層組成的組件會形成“嵌套地獄
”。而在 React Hooks 中,這些功能都可以通過強大的自定義的 Hooks 來實現。
3、不用再去考慮 this 的指向問題。在類組件中,你必須去理解 JavaScript 中 this 的工作方式。
缺點:
對一些鉤子函數不支持。當下 v16.8 的版本中,還無法實現 getSnapshotBeforeUpdate
和 componentDidCatch
這兩個在類組件中的生命週期函數。
Hook 規則
- 不在循環,條件或嵌套函數中調用 Hook, 確保總是在你的 React 函數的最頂層調用他們。
- 不在普通的 JavaScript 函數中調用 Hook,在 React 的函數組件或者自定義 Hook 中調用 Hook。
Hook API
名稱 | 描述 |
---|---|
useState | 在函數組件中維護自己的狀態 |
useEffect | 在函數組件中實現生命週期鉤子函數 |
useContext | 用來處理多層級傳遞數據的方式,減少組件嵌套 |
useReducer | 跟react-redux的使用方式一樣,算是提供一個 mini 的 Redux 版本 |
useCallback | 獲得一個記憶函數,避免在某些情況下重新渲染子組件,用來做性能優化 |
useMemo | 獲得一個記憶組件,和useCallback非常類似,它適用於返回確定的值 |
useRef | 生成對 DOM 對象的引用,它是一個真正的引用,而不是把值拷過去 |
useImperativeHandle | 透傳ref,用於讓父組件獲取子組件內的引用 |
useLayoutEffect | 同步執行副作用,在頁面完全渲染完成後,操作DOM |
useState
在類組件中,我們使用 this.state
來保存組件狀態,並對其修改觸發組件重新渲染。而在函數組件中,由於沒有 this 這個黑魔法,可能通過 useState
來幫我們保存組件的狀態。
- useState(),返回一個 state,以及更新 state 的函數。
- useState() 中第一個參數是值或者對象,初始渲染期間,返回的狀態 (state) 是傳入的第一個參數相同。
- 如果依賴於先前的state,在第二個參數中接收先前的 state,並返回一個更新後的值。
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0); //0是count的默認值
return (
<div className="App">
Count: {count}
<button onClick={() => setCount((preState=>preState+1))}>+</button>
<button onClick={() => setCount(0)}>還原</button>
</div>
);
}
注意:與類組件中的 setState 方法不同,useState 不會自動合併更新對象
。當第一個參數是一個對象時,你可以用函數式的 setState 結合展開運算符
來達到合併更新對象的效果。
import React, { useState } from "react";
function App1(){
const [obj,setObj]=useState({
name:'admin',
age:18
})
return(
<div>
姓名:{obj.name}-年齡:{obj.age}
<button onClick={()=>setObj({...obj,name:obj.name+"用戶"})}>只修改姓名</button>
</div>
)
}
useEffect
語法:useEffect(fn,Array)
- 第一個參數傳遞函數,可以用來做一些副作用比如異步請求,修改外部參數等行爲。
- 第二個參數是個數組,
數組中
的值發生變化
纔會觸發
useEffect 第一個參數中的函數。 - 如果第二個參數是個
空數組
的話,默認會在頁面加載後執行一次
。 - 如果第一個參數有返回值,會在
組件銷燬
或者調用函數前調用。 - 可以使用useEffect模擬
componentDidMount
、componentDidMount
和componentWillUnmount
鉤子函數。
import React, { useState, useEffect } from "react";
function App(){
const [count, setCount] = useState(0);
useEffect(()=>{
//只要count有變化,就會執行這裏
},[count])
useEffect(()=>{
//如果在下面沒有參數的話,頁面加載後執行,執行一次
return()=>{
//頁面退出的時候執行
}
},[])
}
useContext
用來處理多層級傳遞數據的方式,使用 useContext 可以解決 Consumer 多狀態嵌套的問題
import React, { useContext } from "react";
const colorContext = React.createContext("gray");
function Bar() {
const color = useContext(colorContext);
return <div>{color}</div>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<colorContext.Provider value={"red"}>
<Foo />
</colorContext.Provider>
);
}
useReducer
useReducer 這個 Hooks 在使用上幾乎跟 React-Redux 一模一樣,唯一缺少的就是無法使用 redux 提供的中間件,算是提供一個 mini 的 Redux 版本。
import React, { useReducer } from "react";
const initialState = {
count: 0
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
-
</button>
</>
);
}
useCallback
通過 useCallback 獲得一個記憶後的函數,避免函數組件在每次渲染的時候如果有傳遞函數的話,重新渲染子組件。用來做性能優化。
import React, { useCallback } from "react";
function App() {
const memoizedHandleClick = useCallback(() => {
console.log('Click happened')
}, []); // 空數組代表無論什麼情況下該函數都不會發生改變
return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
useMemo
記憶組件,和useCallback類似,不同的是:useCallback 不會執行第一個參數函數,而是將它返回給你,而 useMemo 會執行第一個函數並且將函數執行結果返回給你。所以在前面的例子中,可以返回 handleClick 來達到存儲函數的目的。
所以 useCallback 常用記憶事件函數,生成記憶後的事件函數並傳遞給子組件使用。而 useMemo 更適合經過函數計算得到一個確定的值,比如記憶組件。
import React, { useMemo } from "react";
function App() {
const memoizedHandleClick = useMemo(() => () => {
console.log('Click happened')
}, []); // 空數組代表無論什麼情況下該函數都不會發生改變
return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
useRef
跟 createRef 類似,都可以用來生成對 DOM 對象的引用。不同點在於,它是一個真正的引用,而不是把值拷過去。
import React, { useState, useRef } from "react";
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
setName(nameRef.current.value);
};
return (
<div className="App">
<p>{name}</p>
<div>
<input ref={nameRef} type="text" />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
useImperativeHandle
透傳ref,用於讓父組件獲取子組件內的引用。
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => inputRef.current);
return (
<div>
<input type="text" name="child input" ref={inputRef} placeholder="我聚焦了"/>
<input type="text" name="child input1" placeholder="我沒有聚焦"/>
</div>
)
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<ChildInput ref={inputRef} />
</div>
);
}
useLayoutEffect
大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調用一些副作用
,比如對 DOM 的操作
,就需要使用 useLayoutEffect
,useLayoutEffect 中的副作用會在 DOM 更新之後同步執行。
import React, { useState, useEffect, useLayoutEffect } from "react";
function App() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
const title = document.querySelector("#title");
const titleWidth = title.getBoundingClientRect().width;
console.log("useLayoutEffect");
if (width !== titleWidth) {
setWidth(titleWidth);
}
});
useEffect(() => {
console.log("useEffect");
});
return (
<div>
<h1 id="title">hello</h1>
<h2>{width}</h2>
</div>
);
}
在上面的例子中,useLayoutEffect 會在 render,DOM 更新之後同步觸發函數,會優於 useEffect 異步觸發函數。
參考資料:https://react.docschina.org/docs/hooks-reference.html
https://github.com/happylindz/blog/issues/19