30分鐘帶你全面了解React Hooks
概述
1. Hooks 只能在函數組件內使用;
2. Hooks 用于擴充函數組件的功能,使函數組件可以完全代替類組件
React Hooks 都掛在 React 對象上,因此使用時為 React.useState() 的形式,若嫌麻煩,可以提前導入,如下:
import React, { useState } from "react"
React 內置的 Hooks 有很多,這里介紹一些常用到的。全部的請看 Hooks API
用到了 Hook 的函數組件名必須首字母大寫,否則會被 ESLint 報錯
1. useState
const [state, setState] = useState(initialState)
1.1 概念三連問
調用 useState 有什么作用?
useState 是用于聲明一個狀態(tài)變量的,用于為函數組件引入狀態(tài)。
我們傳遞給 useState 的參數是什么?
useState 只接收一個參數,這個參數可以是數字、字符串、對象等任意值,用于初始化聲明的狀態(tài)變量。也可以是一個返回初始值的函數,最好是函數,可在渲染時減少不必要的計算。
useState返回的是什么?
它返回一個長度為2的讀寫數組,數組的第一項是定義的狀態(tài)變量本身,第二項是一個用來更新該狀態(tài)變量的函數,約定是 set 前綴加上狀態(tài)的變量名。如 setState,setState() 函數接收一個參數,該參數可以是更新后的具體值,也可以是一個返回更新后具體值的函數。若 setState 接收的是一個函數,則會將舊的狀態(tài)值作為參數傳遞給接收的函數然后得到一個更新后的具體狀態(tài)值。
1.2 舉個例子
function App(){ const [n, setN] = useState(0) const [m, setM] = useState(() => 0) return ( <div> n: {n} <button onClick={() => setN(n+1)}>+1</button> <br/> m: {m} <button onClick={() => setM(oldM => oldM+1)}>+1</button> </div> ) }
1.3 注意事項
- useState Hook 中返回的 setState 并不會幫我們自動合并對象狀態(tài)的屬性
- setState 中接收的對象參數如果地址沒變的話會被 React 認為沒有改變,因此不會引起視圖的更新
2. useReducer
useReducer 是 useState 的升級版。在 useState 中返回的寫接口中,我們只能傳遞最終的結果,在 setN 的內部也只是簡單的賦值操作。
也就是說,得到結果的計算過程需要我們在函數組件內的回調函數中書寫,這無疑增加了函數組件的體積,而且也不符合 Flux 的思想(狀態(tài)由誰產生的,誰負責進行各種處理,并暴露處理接口出去給別人用)
因此,React 就提供了比 useState 更高級的狀態(tài)管理 Hook:useReducer,介紹如下:
2.1 使用方法
- 創(chuàng)建初始狀態(tài)值 initialState
- 創(chuàng)建包含所有操作的 reducer(state, action) 函數,每種操作類型均返回新的 state 值
- 根據 initialState 和 reducer 使用 const [state, dispatch] = useReducer(reducer, initialState) 得到讀寫 API
- 調用寫接口,傳遞的參數均掛在 action 對象上
2.2 舉個例子
import React, { useReducer } from 'react'; import ReactDOM from 'react-dom'; const initialState = { n: 0 } const reducer = (state, action) => { switch(action.type){ case 'addOne': return { n: state.n + 1 } case 'addTwo': return { n: state.n + 2 } case 'addX': return { n: state.n + action.x } default: { throw new Error('unknown type') } } } function App(){ const [state, dispatch] = useReducer(reducer, initialState) return ( <div> 我是 App {state.n} <button onClick={()=>dispatch({type: 'addOne'})}>+1</button> <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button> <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button> </div> ) } ReactDOM.render(<App/>,document.getElementById('root'));
3. useContext
context 是上下文的意思,上下文是局部的全局變量這個局部的范圍由開發(fā)者自己指定。
3.1 使用方法
useContext 的使用方法分三步走:
- 使用 const x = createContext(null) 創(chuàng)建上下文,在創(chuàng)建時一般不設置初始值,因此為 null,一般是在指定上下文作用域時初始化。
- 使用 <x.Provider value={}></x.Provider> 圈定上下文的作用域
- 在作用域中使用 const value = useContext(x) 使用上下文的數據
3.2 舉個例子
import React, { useState, createContext, useContext } from 'react'; import ReactDOM from 'react-dom'; const Context = createContext(null) function App(){ const [n, setN] = useState(0) return ( <Context.Provider value={{n, setN}}> <div> <Baba /> <Uncle /> </div> </Context.Provider> ) } function Baba(){ return ( <div> 我是爸爸 <Child /> </div> ) } function Uncle(){ const {n, setN} = useContext(Context) return ( <div> 我是叔叔 我拿到的 context 數據為 {n} </div> ) } function Child(){ const {n, setN} = useContext(Context) return ( <div> 我是兒子 我拿到的 context 數據為 {n} <button onClick={() => setN(n+5)}> 點擊改變 context 數據 </button> </div> ) } ReactDOM.render(<App/>,document.getElementById('root'));
4. useEffect
effect 是副作用的意思,對環(huán)境的改變就是副作用。副作用好像是函數式編程里的一個概念,這里不做過多解讀,也不太懂。
在 React 中,useEffect 就是在每次 render 后執(zhí)行的操作,相當于 afterRender, 接收的第一個參數是回調函數,第二個參數是回調時機??捎迷诤瘮到M件中模擬生命周期。
如果同時出現(xiàn)多個 useEffect ,會按出現(xiàn)順序依次執(zhí)行
4.1 模擬 componentDidMount
useEffect(()=>{ console.log('只在第一次 render 后執(zhí)行') },[])
4.2 模擬 componentDidMount + componentDidUpdate
useEffect(()=>{ console.log('每次 render 后都執(zhí)行,包括第一次 render') })
4.3 可添加依賴
useEffect(()=>{ console.log('只在 x 改變后執(zhí)行,包括第一次 x 從 undefined 變成 initialValue') },[x]) //如果有兩個依賴,則是當兩個依賴中的任何一個變化了都會執(zhí)行
4.4 模擬 componentWillUnmount
useEffect(()=>{ console.log('每次 render 后都執(zhí)行,包括第一次 render') return ()=>{ console.log('該組件要被銷毀了') } }) //直接 return 一個函數即可,該函數會在組件銷毀前執(zhí)行
5. useLayoutEffect
useEffect 總是在瀏覽器渲染完視圖過后才執(zhí)行,如果 useEffect 里面的回調函數有對 DOM 視圖的操作,則會出現(xiàn)一開始是初始化的視圖,后來執(zhí)行了 useEffect 里的回調后立馬改變了視圖的某一部分,會出現(xiàn)一個閃爍的狀態(tài)。
為了避免這種閃爍,可以將副作用的回調函數提前到瀏覽器渲染視圖的前面執(zhí)行,當還沒有將 DOM 掛載到頁面顯示前執(zhí)行 Effect 中對 DOM 進行操作的回調函數,則在瀏覽器渲染到頁面后不會出現(xiàn)閃爍的狀態(tài)。
layout 是視圖的意思,useLayoutEffect 就是在視圖顯示出來前執(zhí)行的副作用。
useEffect 和 useLayoutEffect 就是執(zhí)行的時間點不同,useLayoutEffect 是在瀏覽器渲染前執(zhí)行,useEffect 是在瀏覽器渲染后執(zhí)行。但二者都是在 render 函數執(zhí)行過程中運行,useEffect 是在 render 完畢后執(zhí)行,useLayoutEffect 是在 render 完畢前(視圖還沒渲染到瀏覽器頁面上)執(zhí)行。
因此 useLayoutEffect 總是在 useEffect 前執(zhí)行。
一般情況下,如果 Effect 中的回調函數中涉及到 DOM 視圖的改變,就應該用 useLayoutEffect,如果沒有,則用 useEffect。
6. useRef
useRef Hook 是用來定義一個在組件不斷 render 時保持不變的變量。
組件每次 render 后都會返回一個虛擬 DOM,組件內對應的變量都只屬于那個時刻的虛擬 DOM。
useRef Hook 就提供了創(chuàng)建貫穿整個虛擬 DOM 更新歷史的屬于這個組件的局部的全局變量。
為了確保每次 render 后使用 useRef 獲得的變量都能是之前的同一個變量,只能使用引用做到,因此,useRef 就將這個局部的全局變量的值存儲到了一個對象中,屬性名為:current
useRef 的 current 變化時不會自動 render
useRef 可以將創(chuàng)建的 Refs 對象通過 ref 屬性的方式引用到 DOM 節(jié)點或者 React 實例。這個作用在 React—ref 屬性 中有介紹。
同樣也可以作為組件的局部的全局變量使用,如下例的記錄當前是第幾次渲染頁面。
function App(){ const [state, dispatch] = useReducer(reducer, initialState) const count = useRef(0) useEffect(()=>{ count.current++; console.log(`這是第 ${count.current} 次渲染頁面`) }) return ( <div> 我是 App {state.n} <button onClick={()=>dispatch({type: 'addOne'})}>+1</button> <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button> <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button> </div> ) }
7. forwardRef(不是 Hook)
forwardRef 主要是用來對原生的不支持 ref屬性 函數組件進行包裝使之可以接收 ref屬性 的,具體使用方法可參考 React—ref 屬性
forwardRef 接收一個函數組件,返回一個可以接收 ref 屬性的函數組件
8. useMemo && useCallback
React 框架是通過不斷地 render 來得到不同的虛擬 DOM ,然后進行 DOM Diff 來進行頁面 DOM 的選擇性更新的,因此,在每次的 render 之后都會短時間內存在新舊兩個虛擬 DOM 。
對于組件內包含子組件的情況,當父組件內觸發(fā) render 時,就算子組件依賴的 props 沒有變化,子組件也會因為父組件的重新 render 再次 render 一遍。這樣就產生了不必要的 render 。
為了解決不必要的 render ,React 提供了 React.memo() 接口來對子組件進行封裝。如下:
function App(){ const [n, setN] = useState(0) const [m, setM] = useState(0) return ( <div> 我是父組件 n: {n} <button onClick={()=>setN(n+1)}>n+1</button> <button onClick={()=>setM(m+1)}>m+1</button> <Child value={m}/> //這樣當子組件依賴的 m 值沒有變化時,子組件就不會重新 render </div> ) } const Child = React.memo((props)=>{ useEffect(()=>{ console.log('子組件 render 了') }) return ( <div>我是子組件,我收到來自父組件的值為:m {props.value}</div> ) })
但是上述方式存在 bug,因為 React.memo 在判斷子組件依賴的屬性有沒有發(fā)生改變時僅僅是做的前后值是否相等的比較,如果子組件從父組件處接收的依賴是一個對象的話,比較的就會是對象的地址,而不是對象里面的內容,因此在每次父組件重新 render 后得到的會是不同地址的對象,盡管對象里面的值沒有更新,但是子組件發(fā)現(xiàn)地址變了也會重新 render。
為了解決這個問題,就又出來了 useMemo() Hook,useMemo 是用于在新舊組件交替時緩存復用一個函數或者一個對象,當某個依賴重新變化時才重新生成。
useMemo Hook 接收一個無參數的返回函數(或對象)的函數。并且 useMemo 必須有個依賴,告訴其在什么時候重新計算。有點類似于 Vue 的計算屬性的原理。如下:
function App(){ const [n, setN] = useState(0) const [m, setM] = useState(0) const onClickChild = useMemo(()=>{ return () => { console.log(m) } },[m]) return ( <div> 我是父組件 n: {n} <button onClick={()=>setN(n+1)}>n+1</button> <button onClick={()=>setM(m+1)}>m+1</button> <Child value={m} onClick = {onClickChild}/> </div> ) } const Child = React.memo((props)=>{ useEffect(()=>{ console.log('子組件 render 了') }) return ( <div> 我是子組件,我收到來自父組件的值為:m {props.value} <br/> <button onClick={props.onClick}>click</button> </div> ) })
useCallback() 是 useMemo 的語法糖,因為 useMemo 是接收一個沒有參數的返回函數(或對象)的函數,會有些奇怪,因此提供了 useCallback 來直接接收函數或對象。
const onClickChild = useMemo(() => { console.log(m) },[m])
9. useInperativeHandle
useInperativeHandel 是和 ref 相關的一個 Hook。
我們知道,ref 屬性是會將當前的組件實例或 原生DOM 直接賦值給傳入的 Ref 對象的 current 屬性上,而且函數組件不能接收 ref 屬性,因為函數組件沒有實例。但是如果函數組件經過 React.forwardRef() 封裝過后 可以接收 ref,一般情況下,這個 ref 是訪問的經過函數組件轉發(fā)過后的 原生DOM,但是,如果在函數組件內不僅僅是想讓外來的 ref 指向一個 原生DOM 呢?可不可以讓函數組件的 ref 像類組件中的 ref 指向實例一樣擁有更多的可控性操作呢?React 就為函數組件提供了一種封裝返回的 ref 指向的對象的方法,就是 useInperativeHandle Hook。
9.1 舉個例子
function App(){ const myRef = useRef(null) useEffect(()=>{ console.log(myRef.current.real) console.log(myRef.current.getParent()) }, []) return ( <div> 我是父組件 <Child ref={myRef}/> </div> ) } const Child = forwardRef((props, ref)=>{ const childRef = useRef(null) useImperativeHandle(ref, ()=>{ return { real: childRef.current, getParent(){ return childRef.current.parentNode } } }) return ( <div> 我是子組件,我有一個子DOM <button ref={childRef}>按鈕</button> </div> ) })
10. 自定義 Hook
自定義 Hook 就是自定義一個函數,這個函數必須以 use 開頭,并且,該函數里必須用到原生的 Ract Hooks,返回值一般是一個數組或一個對象,用于暴露該 Hooks 的讀寫接口。
自定義 Hook 通常是將函數組件中多次用到的 hook 整合到一起,盡量在函數組件中不要出現(xiàn)多次 hook 操作。
以上就是30分鐘帶你全面了解React Hooks的詳細內容,更多關于全面了解React Hooks的資料請關注腳本之家其它相關文章!
相關文章
react中實現(xiàn)將一個視頻流為m3u8格式的轉換
這篇文章主要介紹了react中實現(xiàn)將一個視頻流為m3u8格式的轉換方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07關于getDerivedStateFromProps填坑記錄
這篇文章主要介紹了關于getDerivedStateFromProps填坑記錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06詳解React Native 采用Fetch方式發(fā)送跨域POST請求
這篇文章主要介紹了詳解React Native 采用Fetch方式發(fā)送跨域POST請求,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11