React hook超詳細(xì)教程
什么是hook
React Hook是React 16.8版本之后添加的新屬性,用最簡單的話來說,React Hook就是一些React提供的內(nèi)置函數(shù),這些函數(shù)可以讓函數(shù)組件和類組件一樣能夠擁有組件狀態(tài)(state)以及進(jìn)行副作用(side effect)
但是不要什么業(yè)務(wù)都使用hook,請(qǐng)?jiān)诤线m的時(shí)候使用hook,否則會(huì)造成性能問題.(能不用的時(shí)候就不能,當(dāng)遇到性能不好優(yōu)化的時(shí)候,自然會(huì)想到使用它)
useState
它允許函數(shù)組件將自己的狀態(tài)持久化到React運(yùn)行時(shí)的某個(gè)地方,這樣在組件每次重新渲染的時(shí)候都可以從這個(gè)地方拿到該狀態(tài),而且當(dāng)該狀態(tài)被更新的時(shí)候,組件也會(huì)重渲染。
//語法: import {useState} from "react" const [state, setState] = useState(initialState)//數(shù)組解構(gòu)賦值 //useState接收一個(gè)initialState變量作為狀態(tài)的初始值,返回值是一個(gè)數(shù)組。返回?cái)?shù)組的第一個(gè)元素代表當(dāng)前state的最新值,第二個(gè)元素是一個(gè)用來更新state的函數(shù)。state和setState這兩個(gè)變量的命名是你自己取的 //state用于組件內(nèi)部使用的數(shù)據(jù) //setState函數(shù)用于修改state,當(dāng)修改后會(huì)觸發(fā)所有使用過state的地方重新取值(調(diào)用render) //可以用多個(gè)useState
案例:
import React, { Component,useState } from 'react' export default function Box3() { const [first, setfirst] = useState(0) return ( <div> <h1>{first}</h1> <button onClick={()=>{setfirst(first+1)}}>點(diǎn)擊first+1</button> </div> ) }
可以看到數(shù)據(jù)修改了并刷新了模板
useEffect
useEffect是用來使函數(shù)組件也可以進(jìn)行副作用操作的。那么什么是副作用呢?
函數(shù)的副作用就是函數(shù)除了返回值外對(duì)外界環(huán)境造成的其它影響假如我們每次執(zhí)行一個(gè)函數(shù),該函數(shù)都會(huì)操作全局的一個(gè)變量,那么對(duì)全局變量的操作就是這個(gè)函數(shù)的副作用。而在React的世界里,我們的副作用大體可以分為兩類,一類是調(diào)用瀏覽器的API,例如使用addEventListener來添加事件監(jiān)聽函數(shù)等,另外一類是發(fā)起獲取服務(wù)器數(shù)據(jù)的請(qǐng)求,例如當(dāng)用戶組件掛載的時(shí)候去異步獲取用戶的信息等。
import {useEffect} from "react" useEffect(effect?=>clean, dependencies?) //useEffect的第一個(gè)參數(shù)effect是要執(zhí)行的副作用函數(shù),它可以是任意的用戶自定義函數(shù),用戶可以在這個(gè)函數(shù)里面 操作一些瀏覽器的API或者和外部環(huán)境進(jìn)行交互,網(wǎng)絡(luò)請(qǐng)求等,這個(gè)函數(shù)會(huì)在每次組件渲染完成之后被調(diào)用 //useEffect可以有一個(gè)返回值,返回一個(gè)函數(shù),系統(tǒng)在組件重新渲染之前調(diào)用它 //第二個(gè)參數(shù)dependencies來限制該副作用的執(zhí)行條件
案例:組件銷毀時(shí)清除計(jì)算器
import React,{useEffect,useState} from 'react' export default function Box1(props) { let [i,seti]=useState(0) useEffect(()=>{ console.log(i) let timer=setInterval(() => { seti(i+1) },1000); return ()=>{ clearInterval(timer) } }) return ( <div> <h1>{i}</h1> </div> ) }
//父組件 import React,{useState} from 'react' import Box1 from './Box1' export default function Box2() { let [flag,setflag]=useState(true) return ( <div> <button onClick={()=>{setflag(!flag)}}>點(diǎn)擊銷毀/創(chuàng)建Box1</button> {flag&&<Box1></Box1>} </div> ) }
useRef
useRef是用來在組件不同渲染之間共用一些數(shù)據(jù)的,它的作用和我們?cè)陬惤M件里面為this賦值是一樣的。
語法
react import {useRef} from "react" const refObject = useRef(initialValue) //useRef接收initialValue作為初始值,它的返回值是一個(gè)ref對(duì)象,這個(gè)對(duì)象的.current屬性就是該數(shù)據(jù)的最新值。使用useRef的一個(gè)最簡單的情況就是在函數(shù)組件里面獲取DOM對(duì)象的引用
案例:
import { useRef, useEffect } from 'react' import ReactDOM from 'react-dom' const AutoFocusInput = () => { const inputRef = useRef(null) useEffect(() => { // 組件掛載后自動(dòng)聚焦 inputRef.current.focus() }, []) return ( <input ref={inputRef} type='text' /> ) } ReactDOM.render(<AutoFocusInput />, document.getElementById('root')) //在上面代碼中inputRef其實(shí)就是一個(gè){current: input節(jié)點(diǎn)}對(duì)象,只不過它可以保證在組件每次渲染的時(shí)候拿到的都是同一個(gè)對(duì)象。
useCallback
useCallback就是把我們?cè)诤瘮?shù)組件內(nèi)部定義的函數(shù)保存起來,當(dāng)組件重新渲染時(shí)還是使用之前的,就不會(huì)被重新定義一次
語法:
import {useCallback} from "react" const memoizedCallback = useCallback(callback, dependencies) //useCallback接收兩個(gè)參數(shù),第一個(gè)參數(shù)是需要被記住的函數(shù),第二個(gè)參數(shù)是這個(gè)函數(shù)的dependencies,只有dependencies數(shù)組里面的元素的值發(fā)生變化時(shí)useCallback才會(huì)返回新定義的函數(shù),否則useCallback都會(huì)返回之前定義的函數(shù)。
案例:
import React,{useCallback,useEffect,useState} from 'react' export default function Box7() { let [arr,setarr]=useState([{id:1,name:'ljy'},{id:2,name:'jack'},{id:3,name:'marry'}]) let fn=useCallback((index)=>{ console.log(index) },[]) useEffect(()=>{ console.log('fn變化了') },[fn]) return ( <div> <>{arr.map(el=><p key={el.id}><span>{el.id}---{el.name}</span></p>)}</> <button onClick={()=>{setarr([...arr])}}>點(diǎn)擊修改數(shù)據(jù)</button> </div> ) }
useMemo
useMemo和useCallback的作用十分類似,只不過它允許你記住任何類型的變量(不只是函數(shù))
import {useMemo} from "react" const memoizedValue = useMemo(() => valueNeededToBeMemoized, dependencies) //useMemo接收一個(gè)函數(shù),該函數(shù)的返回值就是需要被記住的變量,當(dāng)useMemo的第二個(gè)參數(shù)dependencies數(shù)組里面的元素的值沒有發(fā)生變化的時(shí)候,memoizedValue使用的就是上一次的值。
案例:
import React, { useMemo } from 'react' import ReactDOM from 'react-dom' const RenderPrimes = ({ iterations, multiplier }) => { const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [ iterations, multiplier ]) return ( <div> Primes! {primes} </div> ) } ReactDOM.render(<RenderPrimes />, document.getElementById('root')) //例子中calculatePrimes是用來計(jì)算素?cái)?shù)的,因此每次調(diào)用它都需要消耗大量的計(jì)算資源。 為了提高組件渲染的性能,我們可以使用useMemo來記住計(jì)算的結(jié)果, 當(dāng)iterations和multiplier保持不變的時(shí)候,我們就不需要重新執(zhí)行calculatePrimes函數(shù)來重新計(jì)算了,直接使用上一次的結(jié)果即可。
useContext
我們知道React中組件之間傳遞參數(shù)的方式是props,假如我們?cè)诟讣?jí)組件中定義了某些狀態(tài),而這些狀態(tài)需要在該組件深層次嵌套的子組件中被使用的話就需要將這些狀態(tài)以props的形式層層傳遞,這就造成了props drilling的問題。為了解決這個(gè)問題,React允許我們使用Context來在父級(jí)組件和底下任意層次的子組件之間傳遞狀態(tài)。在函數(shù)組件中我們可以使用useContext Hook來使用Context。
語法:
const value = useContext(MyContext) //useContext接收一個(gè)context對(duì)象為參數(shù),該context對(duì)象是由React.createContext函數(shù)生成的。 useContext的返回值是當(dāng)前context的值,這個(gè)值是由最鄰近的<MyContext.Provider>來決定的。 一旦在某個(gè)組件里面使用了useContext這就相當(dāng)于該組件訂閱了這個(gè)context的變化, 當(dāng)最近的<MyContext.Provider>的context值發(fā)生變化時(shí),使用到該context的子組件就會(huì)被觸發(fā)重渲染,且它們會(huì)拿到context的最新值。
案例:
import React, { useContext, useState } from 'react' import ReactDOM from 'react-dom' //定義context const NumberContext = React.createContext() const NumberDisplay = () => { const [currentNumber, setCurrentNumber] = useContext(NumberContext) const handleCurrentNumberChange = () => { setCurrentNumber(Math.floor(Math.random() * 100)) } return ( <> <div>Current number is: {currentNumber}</div> <button onClick={handleCurrentNumberChange}>Change current number</button> </> ) } const ParentComponent = () => { const [currentNumber, setCurrentNumber] = useState(100) return ( <NumberContext.Provider value={[currentNumber, setCurrentNumber]}> <NumberDisplay /> //這里填兒子組件,后面孫子組件就可以直接使用爺爺組件傳遞的值 </NumberContext.Provider> ) } ReactDOM.render(<ParentComponent />, document.getElementById('root'))
使用時(shí)避免無用渲染
如果一個(gè)函數(shù)組件使用了useContext(SomeContext)的話它就訂閱了這個(gè)SomeContext的變化,這樣當(dāng)SomeContext.Provider的value發(fā)生變化的時(shí)候,這個(gè)組件就會(huì)被重新渲染。
這里有一個(gè)問題就是,我們可能會(huì)把很多不同的數(shù)據(jù)放在同一個(gè)context里面,而不同的子組件可能只關(guān)心這個(gè)context的某一部分?jǐn)?shù)據(jù),當(dāng)context里面的任意值發(fā)生變化的時(shí)候,無論這些組件用不用到這些數(shù)據(jù)它們都會(huì)被重新渲染,這可能會(huì)造成一些性能問題.
解決方法:
1.拆分Context
這個(gè)方法是最被推薦的做法,和useState一樣,我們可以將不需要同時(shí)改變的context拆分成不同的context,讓它們的職責(zé)更加分明,這樣子組件只會(huì)訂閱那些它們需要訂閱的context從而避免無用的重渲染。
import React, { useContext, useState } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const ThemeContext = React.createContext() const ConfigurationContext = React.createContext() const ChildrenComponent = () => { const [themeContext] = useContext(ThemeContext) return ( <div> <ExpensiveTree theme={themeContext} /> </div> ) } const App = () => { const [themeContext, setThemeContext] = useState({ color: 'red' }) const [configurationContext, setConfigurationContext] = useState({ showTips: false }) return ( <ThemeContext.Provider value={[themeContext, setThemeContext]}> <ConfigurationContext.Provider value={[configurationContext, setConfigurationContext]}> <ChildrenComponent /> </ConfigurationContext.Provider> </ThemeContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
2.拆分組件,使用memo來優(yōu)化消耗性能的組件
如果出于某些原因你不能拆分context,仍然可以通過將消耗性能的組件和父組件的其他部分分離開來,并且使用memo函數(shù)來優(yōu)化消耗性能的組件
import React, { useContext, useState } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const AppContext = React.createContext() const ExpensiveComponentWrapper = React.memo(({ theme }) => { return ( <ExpensiveTree theme={theme} /> ) }) const ChildrenComponent = () => { const [appContext] = useContext(AppContext) const theme = appContext.theme return { <div> <ExpensiveComponentWrapper theme={theme} /> </div> ) } const App = () => { const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }}) return ( <AppContext.Provider value={[appContext, setAppContext]}> <ChildrenComponent /> </AppContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
3.不拆分組件,也可以使用useMemo來優(yōu)化
import React, { useContext, useState, useMemo } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const AppContext = React.createContext() const ChildrenComponent = () => { const [appContext] = useContext(AppContext) const theme = appContext.theme return useMemo(() => ( <div> <ExpensiveTree theme={theme} /> </div> ), [theme] ) } const App = () => { const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }}) return ( <AppContext.Provider value={[appContext, setAppContext]}> <ChildrenComponent /> </AppContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
useReducer
.useReducer用最簡單的話來說就是允許我們?cè)诤瘮?shù)組件里面像使用redux一樣通過reducer和action來管理我們組件狀態(tài)的變換
語法:
const [state, dispatch] = useReducer(reducer, initialArg, init?) //useReducer和useState類似,都是用來管理組件狀態(tài)的,只不過和useState的setState不一樣的是,useReducer返回的dispatch函數(shù)是用來觸發(fā)某些改變state的action而不是直接設(shè)置state的值,至于不同的action如何產(chǎn)生新的state的值則在reducer里面定義。 //useReducer接收的三個(gè)參數(shù)分別是: //reducer: 這是一個(gè)函數(shù),它的簽名是(currentState, action) => newState,從它的函數(shù)簽名可以看出它會(huì)接收當(dāng)前的state和當(dāng)前dispatch的action為參數(shù),然后返回下一個(gè)state,也就是說它負(fù)責(zé)狀態(tài)轉(zhuǎn)換的工作。 //initialArg:如果調(diào)用者沒有提供第三個(gè)init參數(shù),這個(gè)參數(shù)代表的是這個(gè)reducer的初始狀態(tài),如果init參數(shù)有被指定的話,initialArg會(huì)被作為參數(shù)傳進(jìn)init函數(shù)來生成初始狀態(tài)。 //init: 這是一個(gè)用來生成初始狀態(tài)的函數(shù),它的函數(shù)簽名是(initialArg) => initialState,從它的函數(shù)簽名可以看出它會(huì)接收useReducer的第二個(gè)參數(shù)initialArg作為參數(shù),并生成一個(gè)初始狀態(tài)initialState
案例:
import React,{useReducer} from 'react' export default function Box3() { let redux=(initState,action)=>{ if(action.type=='NAME'){ //這里進(jìn)行判斷type是什么,然后修改對(duì)應(yīng)的值 initState.name=action.value } initState=JSON.parse(JSON.stringify(initState)) //這一步是狀態(tài)轉(zhuǎn)換,返回一個(gè)新的initState,沒有這一步則修改之后不會(huì)刷新 return initState } let [state,dispatch]=useReducer(redux,{name:'hello'},(arg)=>{ arg.name="ljy" return arg //return返回的就是初始值 }) return ( <> <h1>{state.name}</h1> <button onClick={()=>{dispatch({type:'NAME',value:'修改了name'})}}>點(diǎn)擊修改</button> </> ) }
useReducer +useContext實(shí)現(xiàn)redux
第一步:外部創(chuàng)建上下文對(duì)象
/ctx.ctx.js import React from 'react' let ctx=React.createContext() export default ctx
第二步:定義生產(chǎn)者,并在其調(diào)用useReducer,將值通過value傳給后代
import React,{useReducer} from 'react' import ctx from './ctx/ctx' export default function App(props) { let redux=(initState,action)=>{ if(action.type=='NAME'){ initState.name=action.value } initState=JSON.parse(JSON.stringify(initState)) return initState } let [state,dispatch]=useReducer(redux,{name:'hello'},(arg)=>{ arg.name="ljy" return arg }) return ( <ctx.Provider value={[state,dispatch]}> {/* 傳給后代組件state,和修改的方法dispatch */} {props.children} {/* 相當(dāng)于Vue中的插槽的用法 */} </ctx.Provider> ) } 第三步:index.js文件中掛載
import React from ‘react'; import ReactDOM from ‘react-dom/client'; import App from ‘./App' import Box1 from ‘./Box1' const root = ReactDOM.createRoot(document.getElementById(‘root')); root.render();
第四步:子組件或?qū)O組件使用
import React,{useContext} from 'react' import ctx from './ctx/ctx' import Box2 from './Box2' export default function Box1() { let [state,dispatch]=useContext(ctx) console.log(state) return ( <> <h1>Box1中---{state.name}</h1> <button onClick={()=>{dispatch({type:"NAME",value:'ljy666'})}}>修改state</button> <Box2></Box2> </> ) } import { type } from '@testing-library/user-event/dist/type' import React,{useContext} from 'react' import ctx from './ctx/ctx' export default function Box2() { let [state,dispatch]=useContext(ctx) return ( <> <h1>Box2中---{state.name}</h1> <button onClick={()=>{dispatch({type:'NAME',value:'孫組件修改了'})}}>點(diǎn)擊修改</button> </> ) }
到此這篇關(guān)于React hook超詳細(xì)教程的文章就介紹到這了,更多相關(guān)React hook內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-redux及redux狀態(tài)管理工具使用詳解
Redux是為javascript應(yīng)用程序提供一個(gè)狀態(tài)管理工具集中的管理react中多個(gè)組件的狀態(tài)redux是專門作狀態(tài)管理的js庫(不是react插件庫可以用在其他js框架中例如vue,但是基本用在react中),這篇文章主要介紹了react-redux及redux狀態(tài)管理工具使用詳解,需要的朋友可以參考下2023-01-01React路由鑒權(quán)的實(shí)現(xiàn)方法
這篇文章主要介紹了React路由鑒權(quán)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09react以create-react-app為基礎(chǔ)創(chuàng)建項(xiàng)目
這篇文章主要介紹了react以create-react-app為基礎(chǔ)創(chuàng)建項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03ReactNative 之FlatList使用及踩坑封裝總結(jié)
本篇文章主要介紹了ReactNative 之FlatList使用及踩坑封裝總結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11React+高德地圖實(shí)時(shí)獲取經(jīng)緯度,定位地址
思路其實(shí)沒有那么復(fù)雜,把地圖想成一個(gè)盒子容器,地圖中心點(diǎn)想成盒子中心點(diǎn);扎點(diǎn)在【地圖中心點(diǎn)】不會(huì)動(dòng),當(dāng)移動(dòng)地圖時(shí),去獲取【地圖中心點(diǎn)】經(jīng)緯度,設(shè)置某個(gè)位置的時(shí)候,將經(jīng)緯度設(shè)置為【地圖中心點(diǎn)】即可2021-06-06