本篇內容主要講解“React hooks是什么及怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“React hooks是什么及怎么使用”吧!
Hooks 是react16.8新增特性,它可以使用一些state的新特性,簡化邏輯復用,副作用統一數據。
Hooks就是把某個目標結果鉤到某個可能會變化的數據源或者事件源上,那么當被鉤到的數據或者事件發生變化時,產生這個目標結果的代碼會重新執行,產生更新后的結果。
Hook出世之前React存在的問題
在組件之間復用狀態邏輯很難
React 沒有提供將可復用性行為“附加”到組件的途徑(例如,把組件連接到 store)。有一些解決此類問題的方案,比如 render props 和 高階組件。但是這類方案需要重新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。
復雜組件變得難以理解
組件常常在 componentDidMount 和 componentDidUpdate中獲取數據。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設置事件監聽,而之后需在 componentWillUnmount 中清除。相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,并且導致邏輯不一致。
難以理解的 class
class 是學習 React 的一大屏障。你必須去理解 JavaScript 中 this 的工作方式,這與其他語言存在巨大差異。還不能忘記綁定事件處理器。沒有穩定的語法提案,這些代碼非常冗余。大家可以很好地理解 props,state 和自頂向下的數據流,但對 class 卻一籌莫展。
Hook帶來的解決方案
你可以使用 Hook 從組件中提取狀態邏輯,使得這些邏輯可以單獨測試并復用。Hook 使你在無需修改組件結構的情況下復用狀態邏輯。
Hook 將組件中相互關聯的部分拆分成更小的函數(比如設置訂閱或請求數據),而并非強制按照生命周期劃分。你還可以使用 reducer 來管理組件的內部狀態,使其更加可預測。
Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 組件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學習復雜的函數式或響應式編程技術。
useState 是react自帶的一個hook函數,它的作用就是用來聲明狀態變量。useState這個函數接收的參數是我們的狀態初始值(initial state),它返回了一個數組,這個數組的第[0]項是當前當前的狀態值,第[1]項是可以改變狀態值的方法函數。
初始化
//返回一個 state,以及更新 state 的函數 setState(接收一個新的 state 值并將組件的一次重新渲染加入隊列) const [state, setState] = useState(initialState);
函數式更新
//如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,并返回一個更新后的值。
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}惰性初始 state
//如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算并返回初始的 state,此函數只在初始渲染時被調用
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});跳過 state 更新
調用 State Hook 的更新函數并傳入當前的 state 時,React 將跳過子組件的渲染及 effect 的執行。(React 使用 Object.is 比較算法 來比較 state。)
useEffect
我們寫的有狀態組件,通常會產生很多的副作用(side effect),比如發起ajax請求獲取數據,添加一些監聽的注冊和取消注冊,手動修改dom等等。我們之前都把這些副作用的函數寫在生命周期函數鉤子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而現在的useEffect就相當與這些聲明周期函數鉤子的集合體。它以一抵三。
簡單例子
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 類似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文檔的標題
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me </button>
</div>
);
}清除 effect
通常,組件卸載時需要清除 effect 創建的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。以下就是一個創建訂閱的例子:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除訂閱
subscription.unsubscribe();
};
});為防止內存泄漏,清除函數會在組件卸載前執行。另外,如果組件多次渲染(通常如此),則在執行下一個 effect 之前,上一個 effect 就已被清除。
effect 的執行時機
與 componentDidMount、componentDidUpdate 不同的是,在瀏覽器完成布局與繪制之后,傳給 useEffect 的函數會延遲調用。這使得它適用于許多常見的副作用場景,比如設置訂閱和事件處理等情況,因此不應在函數中執行阻塞瀏覽器更新屏幕的操作。
effect 的條件執行
默認情況下,effect 會在每輪組件渲染完成后執行。這樣的話,一旦 effect 的依賴發生變化,它就會被重新創建。在某些情況下,我們不需要在每次組件更新時都創建新的訂閱,而是僅需要在 source prop 改變時重新創建。要實現這一點,可以給 useEffect 傳遞第二個參數,它是 effect 所依賴的值數組。
//此時,只有當 props.source 改變后才會重新創建訂閱。(要實現componentDidMount功能只需要設置第二個參數為[]即可)
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);useContext
可以深層組件傳值,父組件傳給子孫組件。接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。
當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,并使用最新傳遞給 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也會在組件本身使用 useContext 時重新渲染
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context! </button>
);
}useReducer
useState 的替代方案,可以用于復雜狀態處理。它接收一個形如 (state, action) => newState 的 reducer,并返回當前的 state 以及與其配套的 dispatch 方法。(如果你熟悉 Redux 的話,就已經知道它如何工作了。)參考 前端react面試題詳細解答
有兩種不同初始化 useReducer state 的方式,你可以根據使用場景選擇其中的一種。將初始 state 作為第二個參數傳入 useReducer 是最簡單的方法:
//nst [state, dispatch] = useReducer(reducer, initialArg, init);
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較復雜且包含多個子值,或者下一個 state 依賴于之前的 state 等。并且,使用 useReducer 還能給那些會觸發深更新的組件做性能優化,因為你可以向子組件傳遞 dispatch 而不是回調函數 。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}你可以選擇惰性地創建初始 state。為此,需要將 init 函數作為 useReducer 的第三個參數傳入,這樣初始 state 將被設置為 init(initialArg)。
這么做可以將用于計算 state 的邏輯提取到 reducer 外部,這也為將來對重置 state 的 action 做處理提供了便利:
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count} <button
onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}跳過 dispatch
如果 Reducer Hook 的返回值與當前 state 相同,React 將跳過子組件的渲染及副作用的執行。(React 使用 Object.is 比較算法 來比較 state。)
useMemo
把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助于避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值。memo是淺比較,意思是,對象只比較內存地址,只要你內存地址沒變,管你對象里面的值千變萬化都不會觸發render。
你可以把 useMemo 作為性能優化的手段,但不要把它當成語義上的保證。將來,React 可能會選擇“遺忘”以前的一些 memoized 值,并在下次渲染時重新計算它們,比如為離屏組件釋放內存。先編寫在沒有 useMemo 的情況下也可以執行的代碼 —— 之后再在你的代碼中添加 useMemo,以達到優化性能的目的。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
把內聯回調函數及依賴項數組作為參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新。當你把回調函數傳遞給經過優化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。
useMemo 與 useCallback 類似,都是有著緩存的作用,useMemo 是緩存值的,useCallback 是緩存函數的。
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);useRef
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。
useEffect里面的state的值,是固定的,這個是有辦法解決的,就是用useRef,可以理解成useRef的一個作用:就是相當于全局作用域,一處被修改,其他地方全更新。
本質上,useRef 就像是可以在其 .current 屬性中保存一個可變值的“盒子”。你應該熟悉 ref 這一種訪問 DOM 的主要方式。如果你將 ref 對象以 <div ref={myRef} /> 形式傳入組件,則無論該節點如何改變,React 都會將 ref 對象的 .current 屬性設置為相應的 DOM 節點。然而,useRef() 比 ref 屬性更有用。它可以很方便地保存任何可變值,其類似于在 class 中使用實例字段的方式。
請記住,當 ref 對象內容發生變化時,useRef 并不會通知你。變更 .current 屬性不會引發組件重新渲染。如果想要在 React 綁定或解綁 DOM 節點的 ref 時運行某些代碼,則需要使用回調 ref 來實現。
const Hook =()=>{
const [count, setCount] = useState(0)
const btnRef = useRef(null)
useEffect(() => {
console.log('use effect...')
const onClick = ()=>{
setCount(count+1)
}
btnRef.current.addEventListener('click',onClick, false)
return ()=> btnRef.current.removeEventListener('click',onClick, false)
},[count])
return(
<div>
<div>
{count} </div>
<button ref={btnRef}>click me </button>
</div>
)
}useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值。在大多數情況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle 應當與 forwardRef 一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);在本例中,渲染 <FancyInput ref={inputRef} /> 的父組件可以調用 inputRef.current.focus()。
自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。
例如,下面的 useFriendStatus 是我們第一個自定義的 Hook:
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}自定義一個當resize 的時候 監聽window的width和height的hook
import {useEffect, useState} from "react";
export const useWindowSize = () => {
const [width, setWidth] = useState()
const [height, setHeight] = useState()
useEffect(() => {
const {clientWidth, clientHeight} = document.documentElement
setWidth(clientWidth)
setHeight(clientHeight)
}, [])
useEffect(() => {
const handleWindowSize = () =>{
const {clientWidth, clientHeight} = document.documentElement
setWidth(clientWidth)
setHeight(clientHeight)
};
window.addEventListener('resize', handleWindowSize, false)
return () => {
window.removeEventListener('resize',handleWindowSize, false)
}
})
return [width, height]
}使用:
const [width, height] = useWindowSize() const isOnline = useFriendStatus(id);
到此,相信大家對“React hooks是什么及怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。