React Hooks
在了解
React Hooks之前, 我們先看一下Class和函數(shù)式的一般寫法
Class組件 一般寫法
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
class App extends React.Component {
// 默認(rèn)參數(shù)
static defaultProps = {
message: "Hello World",
firstName: 'Yang',
lastName: 'yu'
};
// 傳參檢查
static propTypes = {
message: PropTypes.string
};
/**
* 方便調(diào)試
*/
static displayName = "App";
// 狀態(tài)
state = {
count: 1
};
/**
* 解決綁定 this 的幾種方法
* 1. onClick={this.increment.bind(this)}
* 2. 在 constructor中綁定 tihs ==> this.increment = this.increment.bind(this)
* 3 箭頭函數(shù): 浪費(fèi)內(nèi)存
*/
increment = () => {
this.setState({
count: 2
});
};
// 計(jì)算屬性
get name() {
return this.props.firstName + this.props.lastName
}
// 生命周期
componentDidMount() {}
render() {
return (
<div>
<div>{this.props.message}</div>
<div>{this.state.count}</div>
<button onClick={this.increment}>點(diǎn)擊</button>
<div>{this.name}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
函數(shù)組件一般寫法
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
interface Props {
message?: string;
}
const App: React.FunctionComponent<Props> = props => {
/**
* 如何使用 state, React版本 > 16.8
* useState 返回一個(gè)數(shù)組, 解構(gòu)
*/
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const increment = () => {
setCount(count + 1);
};
const increment1 = () => {
setCount1(count1 + 1);
};
/**
* 生命周期 代替 componetDidMount componentDidUpdate
*/
useEffect(() => {
console.log('componentDidMount() 或者 componentDidUpdate()')
})
// 第二個(gè)參數(shù)是 []
useEffect(() => {
console.log('只在第一次執(zhí)行')
// axios.get()
}, [])
// 當(dāng)某個(gè) 參數(shù)更新, 才觸發(fā)
useEffect(() => {
console.log('count 更新了之后執(zhí)行, 點(diǎn)擊 count1 不會(huì)更新')
}, [count])
useEffect(() => {
return () => {
console.log('我死了')
}
})
return (
<div>
<div>{props.message}</div>
<div>{count}</div>
<div>{count1}</div>
<button onClick={increment}>按鈕</button>
<button onClick={increment1}>按鈕1</button>
</div>
);
};
App.defaultProps = {
message: 'Hello World'
}
App.displayName = 'yym'
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
下面我們來看看一些
Hooks API
useState
-
使用
const [n, setN] = React.useState(0) const [user, setUser] = React.useState({name: 'yym'}) -
不可局部更新
-
因?yàn)?code>useState不會(huì)幫我們合并屬性
setUser({ ...user, name: 'yym1' })
-
-
地址會(huì)變
-
setState(obj)如果obj地址不變, 那么React認(rèn)為數(shù)據(jù)沒有變化
-
-
接收函數(shù)
const [user, setUser] = useState(() => ({name: 'yym2', age: 12})) // 減少多余計(jì)算過程 setN(i => i + 1) setN(i => i + 1) // 對(duì) state 多次操作,
簡(jiǎn)單實(shí)現(xiàn) useState
下面代碼可以放到
codesandbox里面跑一下
看一個(gè)簡(jiǎn)單的例子
const App = () => {
const [n, setN] = useState(0);
return (
<div>
<div>{n}</div>
<button onClick={() => setN(n + 1)}>incrementN</button>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"))
1. 進(jìn)入頁面, render App, 調(diào)用 App() , n = 0
2. 用戶點(diǎn)擊 button, 調(diào)用 setN(n + 1), 再次 render App, 得到虛擬 DOM, DOM diff 更新真 DOM
3. 每次調(diào)用 App, 都會(huì)運(yùn)行 useState(0), n 的值都會(huì)改變
QA:
1. 執(zhí)行 setN 的時(shí)候會(huì)發(fā)生什么? n 會(huì)變嗎? App() 會(huì)重新執(zhí)行嗎?
重新渲染; setN要把n改變, n 不變; App 會(huì)重新執(zhí)行;
2. 如果 App() 會(huì)重新執(zhí)行, 那么 useState(0) 的時(shí)候, n 每次值會(huì)不同
n 的值會(huì)不同
上面的例子我們可以分析:
-
setN-
setN一定會(huì)修改數(shù)據(jù)X, 將n + 1存入X,(X 泛指) -
setN一定會(huì)觸發(fā)<App />, 重新渲染
-
-
useState-
useState會(huì)從X讀取 n 的最新值
-
-
X- 每個(gè)組件有自己的數(shù)據(jù)
X, 我們將其命名為state
- 每個(gè)組件有自己的數(shù)據(jù)
根據(jù)上面的結(jié)論, 我們嘗試寫一下 useState
import React from "react";
import ReactDOM from "react-dom";
let _state: any;
const myUseState = (initialValue: any) => {
_state = _state === undefined ? initialValue : _state;
const setState = (newState: any) => {
_state = newState;
// 渲染頁面
render();
};
return [_state, setState];
};
// 不管這個(gè)實(shí)現(xiàn)
const render = () => ReactDOM.render(<App />, document.querySelector("#root"));
const App = () => {
console.log("App 渲染了");
// 使用自己寫的 myUseState
const [n, setN] = myUseState(0);
return (
<div>
<div>{n}</div>
<button onClick={() => setN(n + 1)}>incrementN</button>
</div>
);
};
export default App;
上面的代碼完全可以正常運(yùn)行, 但當(dāng)我們的 myUseState 有兩個(gè)的時(shí)候, 就是下面的代碼
import React from "react";
import ReactDOM from "react-dom";
let _state: any;
const myUseState = (initialValue: any) => {
_state = _state === undefined ? initialValue : _state;
const setState = (newState: any) => {
_state = newState;
// 渲染頁面
render();
};
return [_state, setState];
};
// 不管這個(gè)實(shí)現(xiàn)
const render = () => ReactDOM.render(<App />, document.querySelector("#root" ));
const App = () => {
console.log("App 渲染了");
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(1);
return (
<div>
<div>
{n}-{m}
</div>
<button onClick={() => setN(n + 1)}>incrementN</button>
<button onClick={() => setM(m + 1)}>incrementM</button>
</div>
);
};
export default App;
我們運(yùn)行上面的代碼, 發(fā)現(xiàn)更新 n, m 也會(huì)改變, 更新m, n 也會(huì)改變, 因?yàn)樗械臄?shù)據(jù)都放在 _state 會(huì)沖突, 該怎么解決?
-
把
_state做成對(duì)象_state = { m: 0, n: 0}, // 感覺不行, useState(0) 并不知道變量叫 m 還是 n, -
把
_state做成數(shù)組, 嘗試一下_state = [0, 0]// 把 _state 初始為數(shù)組 let _state: any[] = []; // 下標(biāo) let index = 0; const myUseState = (initialValue: any) => { // 設(shè)置唯一的值, 根據(jù) myUseState 的順序 const currentIndex = index; _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]; const setState = (newState: any) => { _state[currentIndex] = newState; // 渲染頁面 render(); }; index += 1; return [_state[currentIndex], setState]; }; const render = () => { // 每次重新渲染 index 重置 index = 0; ReactDOM.render(<App />, document.querySelector("#root")); }; -
上面數(shù)組的方法會(huì)有一定的缺點(diǎn)
-
useState的調(diào)用順序- 若第一次順序是 n m K
- 第二次必須保證順序一致,
-
useState不能寫在if中
const [n, setN] = React.useState(0); let m, setM; if (n % 2 === 1) { [m, setM] = React.useState(0); }// 會(huì)報(bào)錯(cuò) 順序不能變 React Hook "React.useState" is called conditionally. React Hooks must be called in the exact same order in every component render. (react-hooks/rules-of-hooks)eslint -
代碼在這里, 我們想一下 mpUseState 還有什么問題呢?
-
App組件用了_state和index, 那其它組件用什么?- 給每個(gè)組件創(chuàng)建一個(gè)
_state和index
- 給每個(gè)組件創(chuàng)建一個(gè)
- 放在全局作用域重名了怎么辦?
- 放在組件對(duì)應(yīng)的虛擬節(jié)點(diǎn)對(duì)象上
總結(jié) :
每個(gè)函數(shù)組件對(duì)應(yīng)一個(gè)
React節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)保存著
_state和idnexuseState會(huì)讀取state[index]index由useState出現(xiàn)順序決定setState會(huì)修改state, 并觸發(fā)更新
上面的屬于簡(jiǎn)化
useState的實(shí)現(xiàn), 我們看一個(gè)useState出現(xiàn)問題的 代碼
// 先點(diǎn)擊 log , 再點(diǎn)擊 incremwntN, 3s 后打印的是 n: 0, 而不是 n: 1
// 先點(diǎn)擊 incrementN, 再點(diǎn)擊 log, 3s 后 打印 n: 1
// ? 為啥是舊數(shù)據(jù)
1. setN 不會(huì)改變 n, 生成一個(gè) n 的分身, 改變的不是n, 所以 n: 0,
import React, { useState } from "react";
const App = () => {
const [n, setN] = useState(0);
const log = () =>
setTimeout(() => {
console.log(`n: ${n}`);
}, 3000);
return (
<div>
<div>{n}</div>
<button onClick={() => setN(n + 1)}>incrementN</button>
<button onClick={log}>log</button>
</div>
);
};
export default App;
如何讓狀態(tài)始終只是一個(gè)?
-
全局變量
- 用
window.xxx即可
- 用
-
用
useRefconst App = () => { const nRef = React.useRef(0); // { current: 0} const log = () => setTimeout(() => { console.log(`n: ${nRef.current}`); }, 3000); return ( <div> <div>{nRef.current} 不是實(shí)時(shí)刷新</div> <button onClick={() => (nRef.current += 1)}>incrementN</button> <button onClick={log}>log</button> </div> ); };// 手動(dòng)觸發(fā) App 更新, 強(qiáng)制, 更類似于 Vue3 const App = () => { const nRef = React.useRef(0); // { current: 0} const log = () => setTimeout(() => { console.log(`n: ${nRef.current}`); }, 3000); return ( <div> <div>{nRef.current}</div> <button onClick={() => (nRef.current += 1)}>incrementN</button> <button onClick={log}>log</button> </div> ); }; -
useContext-
useContext不僅能貫穿始終, 還能貫穿不同組件// 上下文 const themeContext = createContext(null); const App = () => { const [theme, setTheme] = useState("blue"); return ( // Provider 類似于作用域, 里面的可以使用 theme, settheme <themeContext.Provider value={{ theme, setTheme }}> <div style={{ backgroundColor: theme === "red" ? "red" : "blue" }}> <p>{theme}</p> <div> <ChildA /> </div> <div> <ChildB /> </div> </div> </themeContext.Provider> ); }; const ChildA = () => { const { setTheme } = useContext(themeContext); return ( <div> <button onClick={() => setTheme("red")}>red</button> </div> ); }; const ChildB = () => { const { setTheme } = useContext(themeContext); return ( <div> <button onClick={() => setTheme("blue")}>blue</button> </div> ); };
-
useReducer
使用方法:
-
useReducer是復(fù)雜點(diǎn)的useState
import React, { useReducer } from "react";
// 1. 創(chuàng)建初始值
const initial = {
n: 0
};
// 2. 創(chuàng)建所有操作 reducer(state, action)
const reducer = (state: any, action: any) => {
if (action.type === "add") {
return { n: state.n + action.number };
} else if (action.type === "multiple") {
return { n: state.n * 2 };
} else {
throw new Error("unknown word");
}
};
const App = () => {
// 3. 傳給 useReducer 得到讀和寫 API
const [state, dispatch] = useReducer(reducer, initial);
const onClick = () => {
// 4. 寫
dispatch({ type: "add", number: 1 });
};
const onClick1 = () => {
dispatch({ type: "add", number: 2 });
};
return (
<div>
{/* 4. 讀取值 */}
<h1>n: {state.n}</h1>
<button onClick={onClick}>add+1</button>
<button onClick={onClick1}>add+2</button>
</div>
);
};
export default App;
但如何代替 Redux
我們先弄一個(gè)初始的頁面, 在里面一步步實(shí)現(xiàn) Redux 功能, 示例Demo
// 這是我們初始頁面, 有三個(gè)組件 User Books Movies
import React, { useReducer } from "react";
const App = () => {
return (
<div>
<User />
<hr />
<Books />
<Movies />
</div>
);
};
const User = () => {
return (
<div>
<h1>個(gè)人信息</h1>
</div>
);
};
export default App;
-
先把數(shù)據(jù)集中到
store中// 代碼和初始一樣, 只是往里面加代碼 import React, { useReducer } from "react"; const store = { user: null, books: null, movies: null, } -
創(chuàng)建
reducer, 使用usereducer一樣的const reducer = (state, action) => { switch (action.type) { case "setUser": return { ...state, user: action.user }; case "setBooks": return { ...state, books: action.books }; case "setMovies": return { ...state, movies: action.movies }; default: throw new Error(); } };
-
我們使用
createContext, 創(chuàng)建一個(gè)上下文// 創(chuàng)建一個(gè) context const Context = createContext(null); -
使用
useReducer并把useReducer的讀寫 API, 放進(jìn)Context Value中const App = () => { // 使用 useReducer const [state, dispatch] = useReducer(reducer, store); return ( // 把 useReducer 的讀寫 API 放到 Context value里 <Context.Provider value={{ state, dispatch }}> <User /> <hr /> <Books /> <Movies /> </Context.Provider> ); }; -
經(jīng)過前四步, 我們就可以在在
User, Books, Movie的組件中使用Context.Provide的valueconst User = () => { // 使用 useContext const { state, dispatch } = useContext(Context); // 異步請(qǐng)求值 useEffect(() => { axios.get("/user").then((user: any) => { dispatch({ type: "setUser", user }); }); }, []); return ( <div> <h1>個(gè)人信息</h1> <div>name: {state.user ? state.user.name : ""}</div> </div> ); }; -
所以可以帶到一個(gè)簡(jiǎn)單的 Redux 使用,
// 所有代碼 import React, { useReducer, createContext, useContext, useEffect } from "react"; // store const store = { user: null, books: null, movies: null }; // reducer const reducer = (state, action) => { switch (action.type) { case "setUser": return { ...state, user: action.user }; case "setBooks": return { ...state, books: action.books }; case "setMovies": return { ...state, movies: action.movies }; default: throw new Error(); } }; // 創(chuàng)建一個(gè) context const Context = createContext(null); const App = () => { // 使用 useReducer const [state, dispatch] = useReducer(reducer, store); return ( // 把 useReducer 的讀寫 API 放到 Context value里 <Context.Provider value={{ state, dispatch }}> <User /> </Context.Provider> ); }; // user 可以使用 store 的值 const User = () => { const { state, dispatch } = useContext(Context); useEffect(() => { axios.get("/user").then((user: any) => { dispatch({ type: "setUser", user }); }); }, []); return ( <div> <h1>個(gè)人信息</h1> <div>name: {state.user ? state.user.name : ""}</div> </div> ); }; export default App;
當(dāng)一個(gè)項(xiàng)目比較大, 用到的組件比較多, 使用到的 reducer 比較多, 我們可以模塊化
把
user, books, movies單獨(dú)建一個(gè)文件-
把對(duì)應(yīng)的東西
export (default)出去// 例: 重構(gòu) reducer // reducer const reducer = (state, action) => { switch (action.type) { case "setUser": return { ...state, user: action.user }; case "setBooks": return { ...state, books: action.books }; case "setMovies": return { ...state, movies: action.movies }; default: throw new Error(); } }; // ==> const obj = { 'setUser': (state, action) => { return { ...state, user: action.user }; }, // ... } const reducer_copy = (state, action) => { const fn = obj[action.type] if(fn) { return fn(state, action) } else { throw new Error('type 錯(cuò)誤') } }// user_reducer.js export default { 'setUser': (state, action) => { return { ...state, user: action.user }; }, } // books_reducer.js // ... // idnex.js import userReducer from './reducers/user_reducer' const obj = { ...userReducer, ...otherReducer }
useContext
useContext 改變一個(gè)數(shù)的時(shí)候, 是通過自頂向下, 逐級(jí)更新數(shù)據(jù)做到的
上下文
-
全局變量是全局的上下文 -
上下文是局部的全局變量
在上面的 useReducer 中我們也是用了 useContext,
import React, { createContext, useState, useContext } from "react";
// 1. 創(chuàng)建一個(gè) context 上下文
const Context = createContext(null);
function App() {
console.log("App 執(zhí)行了");
const [n, setN] = useState(0);
return (
// 2. 使用 Context.Provide 弄一個(gè) 作用域 value 值
<Context.Provider value={{ n, setN }}>
<div className="App">
<Parent />
</div>
</Context.Provider>
);
}
function Parent() {
// 3. 作用域內(nèi)使用 useContext
const { n, setN } = useContext(Context);
return (
<div>
我是爸爸 n: {n} <Child />
</div>
);
}
function Child() {
const { n, setN } = useContext(Context);
const onClick = () => {
setN(i => i + 1);
};
return (
<div>
我是兒子 我得到的 n: {n}
<button onClick={onClick}>+1</button>
</div>
);
}
useEffect & useLayoutEffect
useEffect
- 副作用: 對(duì)環(huán)境的改變就是副作用
- 其實(shí)也可以當(dāng)做
afterRender: 每次render后運(yùn)行
-
用途
- 作為
componentDidMount使用,[]作為第二個(gè)參數(shù) - 作為
componentDidUpdate使用, 可制定依賴 - 作為
componentWillUnMount使用, 通過return - 以上三種可以同時(shí)存在
- 作為
-
特點(diǎn)
- 如果同時(shí)存在多個(gè)
useEffect, 會(huì)按照出現(xiàn)次序執(zhí)行
- 如果同時(shí)存在多個(gè)
-
Demo
import React, { useState, useEffect } from "react"; const App = () => { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; const onClcik1 = () => { setM(m + 1); }; // componentDidMount useEffect(() => { console.log("第一次渲染, n或m變化我也不打印了"); document.title = "Hello"; }, []); // componentDidUpdate useEffect(() => { console.log("第一二..N次渲染, 任何state變化我就渲染"); }); // componentDidUpdate useEffect(() => { console.log("第一次渲染, 并且只有m變化我再渲染"); }, [m]); // componentDidMount & componentWillUnMount useEffect(() => { const timerId: any = setInterval(() => { console.log("定時(shí)器"); }, 1000); return () => { window.clearInterval(timerId); }; }); return ( <div> n: {n} <button onClick={onClick}>+n</button> <hr /> m: {m} <button onClick={onClcik1}>+m</button> </div> ); }; export default App;
useLayoutEffect
- 其函數(shù)簽名與
useEffect相同,但它會(huì)在所有的 DOM 變更之后同步調(diào)用 effect??梢允褂盟鼇碜x取 DOM 布局并同步觸發(fā)重渲染。在瀏覽器執(zhí)行繪制之前,useLayoutEffect內(nèi)部的更新計(jì)劃將被同步刷新。 -
useEffect在瀏覽器渲染完成后執(zhí)行 -
useLayout在瀏覽器渲染前執(zhí)行
需要截圖 老是的白板
- 特點(diǎn)
-
useLayoutEffect總是比useEffect先執(zhí)行 (上面說useEffect按次序執(zhí)行, 如果有useLayoutEffect, 先執(zhí)行) -
useLayoutEffect里的任務(wù)最好影響了Layout - 盡可能使用標(biāo)準(zhǔn)的
useEffect以避免阻塞視覺更新 - 優(yōu)先使用
useEffect
-
useMemo & useCallback
-
在了解
React.useMemo之前, 我們先來了解一下React.memo, 直接看代碼- 我們平常寫
React, 經(jīng)常會(huì)有多余的render -
React.memo幫助我們控制何時(shí)重新渲染組件, 可以和函數(shù)式組件一起使用
// 這是一個(gè)普通的父子組件, 子組件使用了父組件的 state const App = () => { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1) } return ( <div> <button onClick={onClick}>update: {n}</button> <Child data={m} /> </div> }; const Child = (props: any) => { console.log('child 執(zhí)行了'); // TODO: 大量代碼 return <div>child: {props.data}</div> }- 上面的代碼我每次更新
n, 在控制臺(tái)看到會(huì)更新Child組件, 但是我們的Child組件, 只依賴m, 但m沒有變化, 我們不希望Child執(zhí)行 -
props不變, 沒必要執(zhí)行函數(shù)組件 - 但我們?cè)趺磧?yōu)化呢?
React.memo
// 上面的代碼改變一下 // 使用 React.memo 包裹一下, 只有當(dāng)我們改變 m 的時(shí)候, Child 才會(huì)執(zhí)行 const Child = React.memo((props: any) => { console.log("child 執(zhí)行了"); // TODO: 大量代碼 return <div>child: {props.data}</div>; });-
React.memo使得一個(gè)組件只有在它的props變化在執(zhí)行一遍, 再次執(zhí)行
- 我們平常寫
-
但是
React.memo有一個(gè)問題: 添加監(jiān)聽函數(shù), 還是會(huì)執(zhí)行Child, 看代碼const App = () => { const [n, setN] = useState(0); // 每次執(zhí)行 m = 0, 值是一樣的 const [m, setM] = useState(0); const onClick = () => { // n 變化不會(huì)執(zhí)行 Child setN(n + 1); }; // 每次App 執(zhí)行, 都會(huì)重新執(zhí)行 // 之前是一個(gè)空函數(shù), 現(xiàn)在是另一個(gè)空函數(shù), 不是同一個(gè)空函數(shù), 引用類型, 地址不同 const onClickChild = () => {}; return ( <div> <button onClick={onClick}>update: {n}</button> {/* 傳一個(gè)函數(shù) */} <Child data={m} onClick={onClickChild} /> </div> ); }; const Child = React.memo((props: any) => { console.log("child 執(zhí)行了"); // TODO: 大量代碼 // 在這里添加了 onClick事件 return <div onClick={props.onClick}>child: {props.data}</div>; });- 怎么解決呢?
react.useMemo
- 怎么解決呢?
-
React.useMemo-
如何使用?
// render 時(shí): 先根據(jù)[name]里面的 name判斷, 因?yàn)?useMemo 作為一個(gè)有著暫存能力, 暫存了上一次的結(jié)果 // 對(duì)比上一次 name, name值不變, data就不重新賦值成新的對(duì)象, 沒有新的對(duì)象, 沒有新的內(nèi)存地址, 就不會(huì) // 重新渲染 const data = useMemo(()=>{ return {} },[m, n]) // 1. 第一個(gè)參數(shù) () => value // 2. 第二個(gè)參數(shù)依賴 [m, n] // 3. 當(dāng)依賴變化, 重新計(jì)算新的 value // 4. 以來不變, 使用之前的 value //函數(shù) const data = useMemo(() => { return () => {} }) -
上面使用
React.memo改造成React.useMemoconst App = () => { const [n, setN] = useState(0); const [m, setM] = useState(0); const onClick = () => { setN(n + 1); }; // useMemo const onClickChild = useMemo(() => { return () => {}; }, [m]); return ( <div> <button onClick={onClick}>update: {n}</button> {/* dlkfs */} <Child data={m} onClick={onClickChild} /> </div> ); }; const Child = React.memo((props: any) => { console.log("child 執(zhí)行了"); // TODO: 大量代碼 return <div onClick={props.onClick}>child: {props.data}</div>; });
-
-
有點(diǎn)難用, 于是有了
useCallback, 作用和useMemo一樣, 是useMemo的語法糖-
用法
useCallback( () => { callback }, [input],) // 等價(jià)于 useMemo(() => () => { callback }, [input]) const onClickChild = useMemo(() => { return () => {}; }, [m]); const onClickChild = useCallback(() => {}, [m]);
-
useRef
-
使用
目的: 1. 如果需要一個(gè)值, 在組建不斷 render 時(shí)保持不變 使用: 1. 初始化 => const count = useRef(0) 2. 讀取: count.currentconst App = () => { const [n, setN] = useState(0); // 初始化一個(gè)值 const count = useRef(0); const onClick = () => { setN(n + 1); }; useEffect(() => { // 通過 .current 來讀取, 更新一次 +1 count.current += 1; console.log(count.current); }); return ( <div> <button onClick={onClick}>update: {n}</button> </div> ); }; -
為什么需要
current?- 為了保證兩次
useRef是同一個(gè)值 (只有引用能做到)
- 為了保證兩次
-
做幾個(gè)
Hook對(duì)比-
useState/useReducer每次都變 -
useMemo/useCallback依賴變化才變 -
useRef永遠(yuǎn)不變
-
-
對(duì)比
Vue3 的 ref// vue <template> <div @click="increment"> ref會(huì)更新: {count} </div> </template> <script> import { ref } from 'vue' export default { setup() { // 直接使用 ref const count = ref(0) const increment = () => { count.value++ } return { count, increment} } } </script>// react 的 ref 不會(huì)自動(dòng)更新ui const App = () => { const [n, setN] = useState(0); const count = useRef(0); const onClick = () => { setN(n + 1); }; const onClick2 = () => { count.current += 1; // 會(huì)改變值 +1 console.log(count.current, "count..."); }; return ( <div> <button onClick={onClick}>update: {n}</button> {/* 頁面沒有更新 */} <button onClick={onClick2}>update count: {count.current}</button> </div> ); }; // 在 實(shí)現(xiàn) useState 中我們有如何手動(dòng)刷新頁面的方法, 使用 useState
forwardRef & useImperativeHandle
-
簡(jiǎn)單看個(gè)例子
const App = () => { const buttonRef = useRef(null); return ( <div> {/* 我們想要獲取子組件的 DOM 引用 */} <ButtonComponent ref={buttonRef}>按鈕</ButtonComponent> </div> ); }; const ButtonComponent = (props: any) => { console.log(props, 'props....') // {children: "按鈕"} 沒有得到 ref return <button className="red" {...props} />; }; // warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? // 上面的報(bào)錯(cuò), 函數(shù)組件不支持 ref, 可以使用 React.forwardRef()
-
使用
forwardRef// 函數(shù)組件希望接收別的傳來的 ref, 需要 forwardRef 包起來 // forwardRef 只是多加了一個(gè) ref 參數(shù) // 改造上面的 const App = () => { const buttonRef = useRef(null); return ( <div> <Button2 ref={buttonRef}>按鈕</Button2> </div> ); }; // forwardRed 包裹 const Button2 = forwardRef((props, ref) => { return <button className="red" ref={ref} {...props} />; });
-
總結(jié):
useRef: 1. 可以用來引用 DOM 對(duì)象 2. 也可以用來引用普通對(duì)象 forwardRef 1. 函數(shù)式組件 由于 props 不包含 ref, 所以需要 forwardRef
-
和
forwardRef相關(guān)的useImperativeHandle, 減少暴露給父組件的屬性// 官方例子: function FancyInput(props, ref) { const inputRef = useRef(); // 可以自定義暴露出去的實(shí)例值 // 其實(shí)可以叫做 setRef useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
自定義 Hook
- 自定義 Hook 是一個(gè)函數(shù),其名稱以 “
use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook