什么是Hooks?
'Hooks'的單詞意思為“鈎子”。React Hooks 的意思是,組件盡量寫成純函數,如果需要外部功能和副作用,就用鈎子把外部代碼"鈎"進來。而React Hooks 就是我們所說的“鈎子”。
常用的鈎子
useState()
useEffect()
userReducer()
useCallback()
useMemo()
useContext()
useRef()
一、userState():狀態鈎子
純函數組件沒有狀態,useState()
用於為函數組件引入狀態。在useState()中,數組第一項為一個變量,指向狀態的當前值。類似this.state,第二項是一個函數,用來更新狀態,類似setState。
import React, {useState} from 'react' const AddCount = () => { const [ count, setCount ] = useState(0) return ( <div> <button onClick={()=>setCount(count++)}>加一</button> </> ) } export default AddCount
二、useEffect():副作用鈎子
useEffect()
接受兩個參數,第一個參數是你要進行的異步操作,第二個參數是一個數組,用來給出Effect的依賴項。只要這個數組發生變化,useEffect()
就會執行
useEffect()可以看做 componentDidMount,componentDidUpdate 和 componentWillUnmount
這三個函數的組合。
useEffect( () => { const subscription = props.source.subscribe(); return () => {//相當於ComponentWillUnmount subscription.unsubscribe(); }; }, [props.source],//相當於ComponentDidUpdate );
三、useReducer():Action鈎子
const [state, dispatch] = useReducer(reducer, initialState)
它接受reducer函數和狀態的初始值作為參數,返回一個數組,其中第一項為當前的狀態值,第二項為發送action的dispatch函數。下面我們依然用來實現一個計數器。
const reduer = (state, action) => { switch(action) { case 'add': return state + 1; case 'reduce': return state - 1; case 'reset': return 0; default:return state; } }
函數組件:
import React,{useReducer} from 'react' export default function Counter() { const [counter, dispatch] = useReducer(reduer, 0); } return ( <div > <div>{counter}</div> <Button onClick={() => dispatch('add')}>遞增</Button> <Button onClick={() => dispatch('reduce')}>遞減</Button> <Button onClick={() => dispatch('reset')}>重置</Button> </div> ); }
useState是useReducer的一個子集,useState 返回的函數內部封裝了一個 dispatch。useReducer( 單個組件中用的少,太重了)
官方的定義:在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較復雜且包含多個子值(注意且字),或者下一個 state 依賴於之前的 state 等。
四、useCallback和useMemo
useMemo 和 useCallback 接收的參數都是一樣,第一個參數為回調,第二個參數為要依賴的數據
共同作用:僅僅依賴數據發生變化, 才會調用,也就是起到緩存的作用。useCallback緩存函數,useMemo 緩存返回值。
useCallback使用場景:
有一個父組件,其中包含子組件,子組件接收一個函數作為props;通常而言,如果父組件更新了,子組件也會執行更新;所有依賴本地狀態或props來創建函數,需要使用到緩存函數的地方,都是useCallback的應用場景。
父組件:
import React, { useCallback } from 'react' function ParentComp () { // ... const [ name, setName ] = useState('hi~') // 每次父組件渲染,返回的是同一個函數引用 const changeName = useCallback((newName) => setName(newName), []) return ( <div> <button onClick={increment}>點擊次數:{count}</button> <ChildComp name={name} onClick={changeName}/> </div> ); }
子組件
import React, { memo } from 'react' const ChildComp = memo(function ({ name, onClick }) { console.log('render child-comp ...') return <> <div>Child Comp ... {name}</div> <button onClick={() => onClick('hello')}>改變 name 值</button> </> })
點擊父組件按鈕,控制台不會打印子組件被渲染的信息了。
究其原因:useCallback() 起到了緩存的作用,即便父組件渲染了,useCallback() 包裹的函數也不會重新生成,會返回上一次的函數引用。
useMemo
import React, { useCallback } from 'react' function ParentComp () { // ... const [ name, setName ] = useState('hi~') const [ age, setAge ] = useState(20) const changeName = useCallback((newName) => setName(newName), []) const info = { name, age } // 復雜數據類型屬性 return ( <div> <button onClick={increment}>點擊次數:{count}</button> <ChildComp info={info} onClick={changeName}/> </div> ); }
父組件渲染,const info = { name, age }
一行會重新生成一個新對象,導致傳遞給子組件的 info 屬性值變化,進而導致子組件重新渲染。
function ParentComp () { // .... const [ name, setName ] = useState('hi~') const [ age, setAge ] = useState(20) const changeName = useCallback((newName) => setName(newName), []) const info = useMemo(() => ({ name, age }), [name, age]) // 包一層 return ( <div> <button onClick={increment}>點擊次數:{count}</button> <ChildComp info={info} onClick={changeName}/> </div> ); }
點擊父組件按鈕,控制台中不再打印子組件被渲染的信息了。
五、useContext
React的useContext應用場景:如果需要在組件A、B之間共享狀態,可以使用useContext()。在它們的父組件上使用React的Context API,在組件外部建立一個Context。否則需要使用props一層層傳遞參數。
import React,{ useContext } from 'react' const Ceshi = () => { const AppContext = React.createContext({}) const A =() => { const { name } = useContext(AppContext) return ( <p>A{name}</p> ) } const B =() => { const { name } = useContext(AppContext) return ( <p>B{name}</p> ) } return ( <AppContext.Provider value={{name: 'hook測試'}}> <A/> <B/> </AppContext.Provider> ) } export default Ceshi
顯示:Ahook測試,Bhook測試
六、useRef
只能為類組件定義ref屬性,而不能為函數組件定義ref屬性。想要在函數式組件中使用Ref,我們必須先了解兩個Api,useRef
和forwardRef
1、返回一個可變的ref對象,該對象只有個current屬性,初始值為傳入的參數(initialValue)。
2、返回的ref對象在組件的整個生命周期內保持不變
3、當更新current值時並不會re-render,這是與useState不同的地方
4、更新useRef是side effect(副作用),所以一般寫在useEffect或event handler里
5、useRef類似於類組件的this
6、每個組件的 ref 只跟組件本身有關,跟其他組件的 ref 沒有關系
import React, { useRef } from 'react' const LikeButton: React.FC = () => { let like = useRef(0) function handleAlertClick() { setTimeout(() => { alert(`you clicked on ${like.current}`) }, 3000) } return ( <> <button onClick={() => {like.current = like.current + 1}}>{like.current}贊</button> <button onClick=handleAlertClick}>Alert</button> </> ) } export default LikeButton
useRef與createRef的區別
組件依賴的props以及state狀態發生變更觸發更新時,createRef每次都會返回個新的引用;而useRef不會隨着組件的更新而重新創建。
let refFromCreateRef = createRef()
可以通過useRef傳入子組件,調用子組件的方法。
forwardRef: 將ref父類的ref作為參數傳入函數式組件中
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 可以直接獲取到button的DOM節點 const ref = React.useRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
useImperativeHandle在函數式組件中,用於定義暴露給父組件的ref方法,用來限制子組件對外暴露的信息。
只有useImperativeHandle第二個參數定義的屬性跟方法可以在父組件能夠獲取到。
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()
參考:
https://www.jianshu.com/p/d600f749bb19
https://www.jianshu.com/p/014ee0ebe959
https://blog.csdn.net/u011705725/article/details/115634265