React hook超詳細(xì)教程
什么是hook
React Hook是React 16.8版本之后添加的新屬性,用最簡(jiǎn)單的話來說,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ì)重渲染。
//語(yǔ)法:
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賦值是一樣的。
語(yǔ)法
react
import {useRef} from "react"
const refObject = useRef(initialValue)
//useRef接收initialValue作為初始值,它的返回值是一個(gè)ref對(duì)象,這個(gè)對(duì)象的.current屬性就是該數(shù)據(jù)的最新值。使用useRef的一個(gè)最簡(jiǎn)單的情況就是在函數(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ì)被重新定義一次
語(yǔ)法:
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。
語(yǔ)法:
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í)避免無(wú)用渲染
如果一個(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í)候,無(wú)論這些組件用不用到這些數(shù)據(jù)它們都會(huì)被重新渲染,這可能會(huì)造成一些性能問題.
解決方法:
1.拆分Context
這個(gè)方法是最被推薦的做法,和useState一樣,我們可以將不需要同時(shí)改變的context拆分成不同的context,讓它們的職責(zé)更加分明,這樣子組件只會(huì)訂閱那些它們需要訂閱的context從而避免無(wú)用的重渲染。
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用最簡(jiǎn)單的話來說就是允許我們?cè)诤瘮?shù)組件里面像使用redux一樣通過reducer和action來管理我們組件狀態(tài)的變換
語(yǔ)法:
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?Hooks?實(shí)現(xiàn)的中文輸入組件
這篇文章主要為大家介紹了React?Hooks實(shí)現(xiàn)的中文輸入組件示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
解決react-connect中使用forwardRef遇到的問題
這篇文章主要介紹了解決react-connect中使用forwardRef遇到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
關(guān)于react ant 組件 Select下拉框 值回顯的問題
這篇文章主要介紹了關(guān)于react ant 組件 Select下拉框 值回顯的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
React合成事件原理及實(shí)現(xiàn)(React18和React16)
本文主要介紹了React合成事件原理及實(shí)現(xiàn),包含React18和React16兩種版本,具有一定的參考價(jià)值,感興趣的可以了解一下2025-02-02
React?Hook中的useState函數(shù)的詳細(xì)解析
Hook 就是 JavaScript 函數(shù),這個(gè)函數(shù)可以幫助你鉤入(hook into) React State以及生命周期等特性,這篇文章主要介紹了React?Hook?useState函數(shù)的詳細(xì)解析的相關(guān)資料,需要的朋友可以參考下2022-10-10
ReactNative?狀態(tài)管理redux使用詳解
這篇文章主要介紹了ReactNative?狀態(tài)管理redux使用詳解2023-03-03
react如何將字符串轉(zhuǎn)義成html語(yǔ)句
這篇文章主要介紹了react如何將字符串轉(zhuǎn)義成html語(yǔ)句問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

