React筆記-Hooks(九)
Hooks
概念
React Hooks 的意思是 組件盡量寫成純函式 如果需要外部功能和副作用 就用鉤子把外部代碼"鉤"進來
函陣列件和類組件區別
- 函陣列件沒有狀態(state) 類組件有
- 函陣列件沒有生命周期 類組件有(掛載-更新-銷毀)
- 函陣列件沒有this 類組件有
- 函陣列件更適合做UI展示 類組件更適合做復雜的業務邏輯組件
為什么純函陣列件逐漸取代類組件
React團隊希望 組件不要變成復雜的容器 最好只是資料流的管道 開發者根據需要 組合管道即可
Hooks用法
useState() 狀態鉤子
純函陣列件沒有狀態,useState()用于設定和使用組件的狀態屬性
// Hooks是無狀態的 所以用這種方式替換類組件中的狀態(state)
const [state, setState] = useState(initialValue);
// state:初始的狀態屬性,指向狀態當前值,類似this.state
// setState:修改狀態屬性值的函式,用來更新狀態,小駝峰命名
// setState((currentState) => {]})可以傳入一個函式 函式接收一個引數 用于存放當前state
// initialValue:狀態的初始值,該值會賦給state
import { useState } from 'react';
function LearnHooks () {
let a = 1;
const [num, setNum] = useState(0);
const [name, setName] = useState([{name : 'bob', age : 18}, {name : 'sam', age : 20}, {name : 'kitty', age : 22}]);
const addNum = () => {
setNum(num + 1)
// setNum是異步操作 所以console.log(num)輸出的是更新前的值
console.log(num)
a += 1;
// 這里a永遠輸出為2
// 原因是hooks重新渲染是自呼叫 每次都會重新把a設為1 然后執行 a + 1
console.log(a)
}
console.log('LearnHooks渲染了')
return (
<div>
<h1>學習hooks的userState</h1>
<div>當前num : {num}</div>
<button onClick={() => addNum()}>num + 1</button>
<div>{name.map((item) => {
return <div key={item.name}>name : {item.name}, age : {item.age}</div>
})}
</div>
<input type="text" onChange={ ({target : {value}}) => setName([{name : value}])}/>
</div>
);
}
export default LearnHooks;
useEffect() 副作用鉤子
可以實作特定的功能 如異步請求
useEffect(() => {
// 回呼函式,其中是要進行的異步操作代碼
return () => {}
// useEffect中的return陳述句可以用于清除effect產生的副作用,當組件卸載時,React會執行return陳述句中的函式,以清除effect產生的副作用,例如,如果在useEffect中訂閱了一個事件,那么在return陳述句中取消訂閱可以避免記憶體泄漏,
}, [array])
// [array]:useEffect執行的依賴,當該陣列的值發生改變時,回呼函式中的代碼就會被執行
// 如果[array]省略,則表示不依賴,在每次渲染時回呼函式都會執行
// 如果[array]是空陣列,即useEffect第二項為[],表示只執行一次
import React, { useState, useEffect } from 'react';
function hook() {
const [num, setNum] = useState(1)
/**
* 第一個引數是回呼函式
* 第二個引數是依賴項
* 每次num變化時都會變化
*
* 注意初始化的時候,也會呼叫一次
*/
useEffect(() => {
console.log("每次num,改變我才會觸發")
}, [num])
return (
<div>
<button onClick={() => setNum(num + 1)}>+1</button>
<div>你好,react hook{num}</div>
</div>
);
}
export default hook;
useLayoutEffect()
useLayoutEffect是React提供的一個Hook,與useEffect功能類似,但在組件更新DOM前執行,而不是之后,
與useEffect不同的是,useLayoutEffect會阻塞瀏覽器渲染,并立即同步執行副作用函式,這可以確保使用組件的代碼看到的是最新的DOM布局,因為它們在組件掛載或更新時都會在渲染通道中優先處理,
// useLayoutEffect接收兩個引數:一個副作用函式和一個依賴項陣列,副作用函式中可以進行DOM操作、計算布局等任務,并且可以通過回傳一個清除函式來清理副作用產生的任何資源,
useLayoutEffect(() => { 副作用函式執行邏輯 }, [ 依賴項 ])
// 1. 撰寫副作用函式:useLayoutEffect需要傳遞一個函式作為引數,該函式稱為副作用函式,在這個函式里,你可以訪問到DOM、執行異步操作或計算等其它操作,并考慮它們的收尾作業,當組件的props或state發生變化時,都將重新運行該函式,
import { useLayoutEffect } from 'react';
function useMyLayoutEffect() {
// 執行DOM相關操作
return () => {
// 清理作業
};
}
// 2. 將useLayoutEffect掛載到組件:要在組件中使用useLayoutEffect這個函式,只需要呼叫它即可,并把上一步撰寫的副作用函式作為第一個引數,
function MyComponent() {
useLayoutEffect(useMyLayoutEffect, []);
return <div>Hello, world!</div>;
}
// 給useLayoutEffect傳遞依賴項陣列:和useEffect一樣,useLayoutEffect的第二個引數是一個陣列,其中包含在副作用函式中需要被“監視”的任何變數,當其中的變數發生更改時,useLayoutEffect將重新運行其副作用函式,
function MyComponent({ name }) {
useLayoutEffect(() => {
console.log(`MyComponent is mounted: ${name}`);
}, [name]);
return <div>Hello, {name}!</div>;
}
useContext() 共享狀態鉤子
可以共享狀態,作用是進行狀態的分發,避免了使用Props進行資料的傳遞
// 第一步:創建全域的Context
const AppContext = React.createContext([初始化引數])
// 第二步:通過全域的Context進行狀態值的共享
<AppContext.Provider value=https://www.cnblogs.com/SpicyPeper/archive/2023/05/22/{{ 屬性名: 值 }}>
<其他組件1 />
<其他組件2 />
// 第三步:使用context
const context = useContext(AppContext);
{context.name}
// 在 App.js 檔案中
import React, { createContext, useState } from 'react';
import Child from './Child';
export const MyContext = createContext();
function App() {
const [count, setCount] = useState(0);
return (
<MyContext.Provider value=https://www.cnblogs.com/SpicyPeper/archive/2023/05/22/{{ count, setCount }}>
Count: {count}
);
}
// 在 Child.js 檔案中
import React, { useContext } from'react';
import { MyContext } from './App';
function Child() {
const { count, setCount } = useContext(MyContext);
return (
<div>
<h2>Child Component</h2>
<h3>Count: {count}</h3>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
useMemo() 記憶鉤子(值)
useMemo是React中的一個hook,用于優化組件的性能,它的作用是快取函式的回傳值,只有當依賴項發生變化時才重新計算,這樣可以避免在每次渲染時都重新計算函式的回傳值,從而提高組件的性能,
const memoizedValue = https://www.cnblogs.com/SpicyPeper/archive/2023/05/22/useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 第一個引數是一個回呼函式,用于計算需要快取的值
// 第二個引數是一個陣列,用于指定依賴項,只有當依賴項發生變化時,才會重新計算memoizedValue的值,
import React, { useState, useMemo } from 'react';
function MyComponent() {
const { a, setA } = useState(1);
const result = useMemo(() => {
// 只有當a發生變化時才會重新計算,這樣可以避免在每次其他渲染時都重新計算結果,提高組件的性能
return a * 2;
}, [a]);
const add = () => {
setA(a + 1)
}
return (
<div>
<div>{result}</div>
<button onClick={() => add()}>a+1</button>
</div>
);
}
useCallback() 記憶鉤子(函式)
useCallback是React中的一個Hook函式,用于優化函陣列件的性能,它的作用是回傳一個記憶化的回呼函式,當依賴項發生變化時才會重新生成新的回呼函式,這樣可以避免在每次渲染時都創建新的回呼函式,從而提高組件的性能,
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
// 第一個引數是回呼函式
// 第二個引數是依賴項陣列,當依賴項陣列中的任意一個值發生變化時,useCallback會重新生成新的回呼函式,如果依賴項陣列為空,則每次渲染都會回傳同一個回呼函式,
// 需要注意的是,useCallback回傳的是一個記憶化的回呼函式,而不是一個普通的函式,因此,如果需要在組件外部使用該回呼函式,需要將其作為props傳遞給子組件,
// 舉例:使用useCallback優化組件性能
import React, { useState, useCallback } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 普通的回呼函式
const handleClick = () => {
setCount(count + 1);
};
// 使用useCallback優化的回呼函式
const handleClickMemoized = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>普通的回呼函式</button>
<button onClick={handleClickMemoized}>使用useCallback優化的回呼函式</button>
</div>
);
}
export default MyComponent;
useReducer() 行為鉤子
useReducer是React中一個狀態管理的Hooks,用于處理復雜的組件狀態邏輯,它和useState類似,都是用于管理組件狀態的,但是useReducer可以更好地處理復雜的狀態邏輯,尤其是在多個狀態相互影響的情況下會更加方便和清晰,
// 1. 定義初始狀態(initial state)和reducer函式,reducer函式的作用是根據當前的狀態和操作型別(action)來回傳新的狀態值,
const initialState = {
count: 0,
};
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return { count: state.count + 1 };
case 'SUB':
return { count: state.count - 1 };
default:
throw new Error();
}
}
// 2. 在組件中使用useReducer Hook,傳入reducer函式和initial state引數,獲取當前的state值和dispatch函式,
import React, { useReducer } from 'react';
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Counter: {state.count}</h2>
<button onClick={() => dispatch({ type: 'ADD' })}>+</button>
<button onClick={() => dispatch({ type: 'SUB' })}>-</button>
</div>
);
}
useRef() 保存參考值
useRef是React中的一個hook,用于創建一個可變的參考,類似于在類組件中使用的this.refs,與useState不同,useRef回傳一個可變的值,而不會觸發重新渲染組件,
useRef可以用于保存任何可變值,例如DOM元素的參考、定時器的識別符號、上一個渲染周期的狀態等,它還提供了一個.current屬性來訪問保存的值,
使用場景
獲取DOM元素的參考
保存上一個渲染周期的狀態
在useEffect中訪問最新的props和state值
保存定時器的識別符號,以便在組件卸載時清除
// useRef創建的ref物件與組件生命周期不相關,因此它不會在呼叫setState或props更新時自動更新,ref改變取決于使用它的具體方式
const ref = useRef()
// 因為hooks重新渲染其實是組件的自呼叫,所以我們不能在組件中直接定義一個值,let a = 1 或 const arr = []都是錯誤的,此時我們使用useRef來保存和訪問持久性資料
useImperativeHandle()
useImperativeHandle是React Hook中的一個函式,用于在使用ref時,向父組件暴露子組件的方法或屬性,它可以覆寫默認情況下通過ref自動公開該組件實體的方式,從而更加精準的控制哪些內容公開給父組件,
useImperativeHandle接受兩個引數:ref物件和一個callback函式,callback函式應該回傳包含想要掛載到ref上的任何公共方法或屬性的物件,當父組件從ref呼叫該方法或訪問該屬性時,callback函式定義的邏輯將被執行,
// 1. 在子組件中使用forwardRef高階組件轉發ref,以在父組件中獲得對子組件的參考,
import React, { forwardRef } from 'react';
const Child = forwardRef((props, ref) => {
// 組件...
});
// 2. 使用useImperativeHandle Hook,將可供父組件訪問的方法或屬性包裝在一個callback函式中,并將該函式作為useImperativeHandle的第二個引數傳遞
import React, { forwardRef, useImperativeHandle } from 'react';
const Child = forwardRef((props, ref) => {
const someMethod = () => {
console.log('Hello from the child component');
};
useImperativeHandle(ref, () => ({
someMethod,
}));
return <div>...</div>;
});
export default Child;
// 3. 在父組件中使用ref來呼叫從子組件暴露的方法
import React, { useRef } from 'react';
import Child from './Child';
function Parent() {
const childRef = useRef(null);
const handleClick = () => {
// 呼叫子組件中公開的 someMethod 方法
childRef.current.someMethod();
};
return (
<div>
<button onClick={handleClick}>呼叫子組件方法</button>
<Child ref={childRef} />
</div>
);
}
其他
Immutable Data(不可變資料)
解決的問題
在 js 中,物件都是參考型別,在按參考傳遞資料的場景中,會存在多個變數指向同一個記憶體地址的情況,如果有多個代碼塊同時更改這個參考,就會產生競態
實作的原理
Persistent Data Structure(持久化資料結構):用一種資料結構來保存資料,當資料被修改時,會回傳一個物件,但是新的物件會盡可能的利用之前的資料結構而不會對記憶體造成浪費,也就是使用舊資料創建新資料時,要保證舊資料同時可用且不變,同時為了避免 deepCopy把所有節點都復制一遍帶來的性能損耗,Immutable 使用了 Structural Sharing(結構共享)
redux / flux
// redux / flux 要求采用回傳新物件的形式,來觸發資料更新、re-render,一般推薦的做法就是采用物件解構的方式,如果 state 物件巨大(注意:物件巨大),在結構、拷貝 state 的程序中,耗時會較長,
return {
...state,
settings: {
...state.settings,
profile:{
...state.settings.profile,
darkmode: true,
}
}
}
immer
immer中文網址
// 開源庫實作思路:原始物件先做了一層 Proxy 代理,得到 draftState 傳遞給 function,function(帶副作用) 直接更改 draftState,最后 produce 回傳新的物件
// 安裝
npm install immer
//使用
import React, { useState } from "react";
import produce from "immer";
export default function App() {
const [list, setList] = useState([1, 2, 3]);
const addMutable = () => {
list.push("新資料");
setList(list);
};
const addImmutable = () => {
/**
* 第一個引數是要代理的資料
* 第二個引數是一個函式
*/
const newVal = produce(list, draft => {
/**
* draft 相當于 list
* 在這個方法里面,可以直接修改draft,注意draft也只能在這個方法里面修改
* 不需要回傳值,immer內部已經幫我處理好了
*/
draft.push('新資料')
})
console.log(newVal)
setList(newVal);
};
return (
<div className="App">
<button onClick={addMutable}>已可變的方式添加</button>
<button onClick={addImmutable}>已不可變的方式添加</button>
{list.map((item, index) => (<li key={index}>{item}</li>))}
</div>
);
}
函陣列件傳值
hooks直接通過props傳值
//父組件中在子組件標簽上定義屬性
import 子組件
<Son 屬性={值}></Son>
// 子組件接收父組件中傳遞屬性
props.屬性
父傳子
// 父組件
import { useState } from 'react'
import Son from './son'
function Father() {
const [data, setData] = useState(0)
return (
<div>
<Son
{/*通過定義屬性傳值*/}
name = 'bob' d = {data}
></Son>
<button onClick={() => setData(data + 1)}>+1</button>
</div>
)
}
export default Father
// 子組件
import { useState } from 'react'
function Son (props) {
return (
<div>
<div>{props.name}</div>
<div>{props.d}</div>
</div>
)
}
export default Son
子傳父
// 父組件
import { useState } from 'react'
import Son from './son'
function Father() {
const [data, setData] = useState(0)
const getSon = (msg) => {
console.log(msg)
}
return (
<div>
<Son
{/*定義一個方法用于接收子組件傳值*/}
name = 'bob' d = {data} giveFather={(msg) => getSon(msg)}
></Son>
<button onClick={() => setData(data + 1)}>+1</button>
</div>
)
}
export default Father
// 子組件
import { useState } from 'react'
function Son (props) {
// 接收父組件方法 通過此方法傳值給父組件
const set = () => {
props.giveFather('兒子給父親的')
}
return (
<div>
<div>{props.name}</div>
<div>{props.d}</div>
<button onClick={set}>給父親</button>
</div>
)
}
export default Son
React.memo
一個高階組件,用于優化React組件的性能,它可以幫助我們避免不必要的渲染,從而提高應用程式的性能,當組件的props沒有改變時,React.memo會使用之前的渲染結果,而不會重新渲染組件,這對于那些渲染開銷較大的組件特別有用,
import React from 'react';
const MyComponent = React.memo(props => {
// 組件代碼
}, (prevProps, currentProps) => {
// prevProps 上次props
// currentProps 當前props
return Boolean;
// false 渲染
// true 不渲染
});
// 在上面的代碼中,我們將一個函陣列件傳遞給React.memo(),并將其回傳的新組件賦值給MyComponent,現在,MyComponent將只在其props發生更改時重新渲染,
// 需要注意的是,React.memo()僅檢查props的淺層比較,如果props包含復雜的物件或函式,可能需要手動實作更深層次的比較,
import React, { useState } from "react";
// 子組件
const SonMemo = React.memo(
// 第一個引數 接收一個hook
(props) => {
return (
<div>{props.data}</div>
)
// 第二個引數 接收一個函式
}, (prevProps, currentProps) => {
// 偶數不渲染
// 奇數渲染
return currentProps.data % 2 === 0
}
)
function FatherMemo () {
const [num, setNum] = useState(0)
return (
<div>
<h1>{num}</h1>
{/*點擊加一*/}
<button onClick={() => setNum(num + 1)}>按鈕</button>
<SonMemo data=https://www.cnblogs.com/SpicyPeper/archive/2023/05/22/{num}>
