React Hooks
- 每次render 都有獨(dú)立的變量和Effects
1. useState
-
vue
-data
data() {
return {
count: 0
}
}
-
react類組件
里面的state
interface CounterStateObj {
count: number
}
export class Counter extends React.Component <any, CounterStateObj>{
constructor(props) {
super(props);
this.state = {
count: 0
};
}
}
hook
function Counter() {
const [count, setCount] = useState<number>(0);
const log = () => {
setTimeout(() => {
console.log('2秒后', count)
}, 2000)
console.log(count)
}
const clickHandler = () => {
log()
setTest(count + 1)
}
return (
<div>
<p>clicked {count} times</p>
<button onClick={clickHandler}>Click me</button>
</div>
);
}
Capture Value
頁面 | log函數(shù) |
---|---|
1 | 0 |
2 | 1 |
3 | 2 |
... | ... |
- 類比照相機(jī)對著一個(gè)杯子拍照惠桃,第二張杯子碎了氨距,但第一張照片永遠(yuǎn)是好的(每次setState就是新拍了一張照片,它不會影響前一個(gè)capture的狀態(tài))
function Example2() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
setCount(count + 1);
}, 3000);
};
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
setCount
</button>
<button onClick={handleClick}>
Delay setCount
</button>
</div>
);
}
操作
先點(diǎn)擊第二個(gè)按鈕, 然后在3秒內(nèi)連續(xù)點(diǎn)擊兩次第一個(gè)按鈕
頁面結(jié)果
0 -> 1 -> 2 -> 1
問題 --- 接著再重復(fù)操作呢麻诀?
- 拿到最新的值
setCount(data => data + 1)
2. useRef
- useRef 返回的 ref 對象在組件的整個(gè)生命周期內(nèi)保持不變献酗,也就是說每次重新渲染函數(shù)組件時(shí),返回的ref 對象都是同一個(gè)(使用 React.createRef 偎窘,每次重新渲染組件都會重新創(chuàng)建 ref)
function Counter() {
const count = useRef<number>(0);
const domRef = useRef<HTMLDivElement>()
const clickHandler = () => {
count.current++
}
return (
<div>
<p>You clicked {count.current} times</p>
<div ref='domRef'></div>
<button onClick={clickHandler}>Click me</button>
</div>
);
}
- 訪問dom
- vue
<div ref='aa'></div> this.$refs['aa']
- react class
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
3. useCallback
- 依賴不變乌助,返回的是同一個(gè)函數(shù)
const [contentClassName, setContentClassName] = useState<string>('')
const resizeHandler = useCallback(() => {
if (contentClassName) {
setH(contentClassName)
}
}, [contentClassName])
useEffect(() => {
let event: UEventEmitter = Ioc(UEventEmitter)
event.on('fullScreenChange', () => {
resizeHandler()
})
window.addEventListener('resize', resizeHandler)
return () => {
window.removeEventListener('resize', resizeHandler)
event.delete('fullScreenChange')
}
}, [contentClassName, resizeHandler])
4. useEffect
- 只要狀態(tài)更新,它就會根據(jù)傳入的依賴項(xiàng)決定是否執(zhí)行陌知, 可以拿到最新的狀態(tài)
- react 類組件 里面的
componentDidMount
+componentDidUpdate
- Vue
created
+updated
- 與 componentDidMount 或 componentDidUpdate 不同他托, useEffect 調(diào)度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應(yīng)用看起來響應(yīng)更快
// 變化執(zhí)行
useEffect(() => {
})
// 變化不執(zhí)行
useEffect(() => {
}, [])
// 只有 test 變化才會執(zhí)行
useEffect(() => {
return () => {}
}, [test])
5. useMemo
- 類比 Vue 的 computed
- 適合大量計(jì)算仆葡,緩存值赏参,依賴不變不會重新更新
const [value, setValue] = useState(0);
const increase = useMemo(() => {
if(value > 2) return value + 1;
}, [value]);
- 和
React.memo()
、React.PureComponent
區(qū)別沿盅?
React.memo()適用函數(shù)組件把篓,僅僅淺比較props
React.PureComponent類組件,淺比較props和state腰涧,根據(jù)結(jié)果決定是否重新渲染組件
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b=韧掩 />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
6. useContext
- 獲取context值
7. useReducer
- 像 redux一樣管理數(shù)據(jù)和行為
8. useLayoutEffect
- 瀏覽器 layout 之后,painting 之前執(zhí)行
- 可以獲取元素樣式
輪子
1.實(shí)現(xiàn)類似類組件的生命周期
componentDidMount
export function useOnMount(fn: () => void, destoryCallBack?: () => void) {
useEffect(() => {
fn()
if (destoryCallBack) {
return () => {
destoryCallBack()
}
}
// eslint-disable-next-line
}, [])
}
componentDidUpdate
export function useOnUpdate(fn: () => void, dep?: any[]) {
const ref = useRef({ fn, mounted: false })
ref.current.fn = fn
useEffect(() => {
// 首次渲染不執(zhí)行
if (!ref.current.mounted) {
ref.current.mounted = true
} else {
ref.current.fn()
}
// eslint-disable-next-line
}, dep)
}
forceUpdate
export function useForceUpdate() {
const [, setValue] = useState(0)
return useCallback(() => {
// 遞增state值窖铡,強(qiáng)制React進(jìn)行重新渲染
setValue(val => (val + 1) % (Number.MAX_SAFE_INTEGER - 1))
}, [])
}
2.實(shí)現(xiàn)多語言
- 當(dāng)語言變化的時(shí)候 觸發(fā)一次render
- 根據(jù)當(dāng)前語言返回對應(yīng)的值
import { store } from '../store/redux'; // redux
import { sysLanguage } from '../config/langulate'; // 語言配置文件
import { useState, useEffect } from 'react';
import { LangValue } from '../interface/common'; // 接口
export function useLanguage() {
const [lang, setLang] = useState<LangValue>(store.getState().user.lang)
useEffect(() => {
let unsubscribe = store.subscribe(() => {
setLang(store.getState().user.lang)
})
return () => {
unsubscribe()
}
// eslint-disable-next-line
}, [])
return [(cn: string) => {
if (cn && sysLanguage[lang] && sysLanguage[lang][cn]) {
return sysLanguage[lang][cn]
} else {
return cn || ''
}
}
]
}
優(yōu)化
- 大量計(jì)算 適用useMemo,
- 為避免子組件的不必要渲染疗锐,useMemo + React.memo() 或 React.PureComponent
例1
function Test() {
console.log('test render')
return (
<div className="test">
呵呵呵
</div>
)
}
export function App() {
const [test, setTest] = useState(1)
console.log('app render')
return (
<div className='app-container' onClick={() => setTest(test + 1)}>
{ test }
<Test/>
</div>
)
}
每次點(diǎn)擊 App 和 Test 各重新渲染一次
例1優(yōu)化
function TestTem() {
console.log('test render')
return (
<div className="test">
呵呵呵
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
console.log('app render')
return (
<div className='app-container' onClick={() => setTest(test + 1)}>
{ test }
<Test/>
</div>
)
}
每次點(diǎn)擊 只有 App 重新渲染
例2
type ConfigObj = {
name: number
}
function TestTem(props: {
config: ConfigObj
}) {
console.log('test render')
return (
<div className="test">
呵呵呵
{props.config.name}
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
const [nameData, setNameData] = useState(1)
const config: ConfigObj = {
name: nameData
}
const clickHandler = () => {
setTest(test + 1)
// setNameData(nameData + 2)
}
console.log('app render')
return (
<div className='app-container' onClick={clickHandler}>
{ test }
<Test config={config}/>
</div>
)
}
每次點(diǎn)擊 App 和 Test 各執(zhí)行一次
例2優(yōu)化
type ConfigObj = {
name: number
}
function TestTem(props: {
config: ConfigObj
}) {
console.log('test render')
return (
<div className="test">
呵呵呵
{props.config.name}
</div>
)
}
const Test = React.memo(TestTem)
export function App() {
const [test, setTest] = useState(1)
const [nameData, setNameData] = useState(1)
// 優(yōu)化
const config: ConfigObj = useMemo(() => {
return {
name: nameData
}
}, [nameData])
const clickHandler = () => {
setTest(test + 1)
// setNameData(nameData + 2) // 放開這句話 會讓config 產(chǎn)生變化 Test會重新渲染
}
console.log('app render')
return (
<div className='app-container' onClick={clickHandler}>
{ test }
<Test config={config}/>
</div>
)
}
- 惰性創(chuàng)建昂貴的對象
const aaa = () => {
console.log('喀納斯')
return 1
}
// 每次render,都會執(zhí)行一遍
const [test, setTest] = useState<number>(aaa())
// 只會被執(zhí)行一次
const [test, setTest] = useState<number>(() => aaa())
- 在useEffect里面及時(shí)解綁事件
const resizeHandler = useCallback(() => {
if (contentClassName) {
setH(contentClassName)
}
}, [contentClassName])
useEffect(() => {
let event: UEventEmitter = Ioc(UEventEmitter)
event.on('fullScreenChange', () => {
resizeHandler()
})
window.addEventListener('resize', resizeHandler)
return () => {
window.removeEventListener('resize', resizeHandler)
event.delete('fullScreenChange')
}
}, [contentClassName, resizeHandler])
- 通用邏輯抽取為自定義hook
- 復(fù)雜的操作和狀態(tài)變更费彼,適用useReducer
- 避免向下傳遞回調(diào), 在大型的組件樹中滑臊,我們推薦的替代方案是通過 context 用
useReducer
往下傳一個(gè)dispatch
函數(shù):
- 避免向下傳遞回調(diào), 在大型的組件樹中滑臊,我們推薦的替代方案是通過 context 用
const TodosDispatch = React.createContext(null);
function TodosApp() {
// 提示:`dispatch` 不會在重新渲染之間變化
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
function DeepChild(props) {
// 如果我們想要執(zhí)行一個(gè) action,我們可以從 context 中獲取 dispatch箍铲。
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
- 每次render 組件執(zhí)行兩次雇卷?
export default function Test () {
const [test, setTest] = useState(1)
console.log('1111')
return (
<div>
{test}
<div onClick={() => setTest(test + 1)}>
23123123
</div>
</div>
)
}
嚴(yán)格模式
不能自動(dòng)檢測到你的副作用,但它可以幫助你發(fā)現(xiàn)它們,使它們更具確定性关划。通過故意重復(fù)調(diào)用以下函數(shù)來實(shí)現(xiàn)的該操作
- Hook 會因?yàn)樵阡秩緯r(shí)創(chuàng)建函數(shù)而變慢嗎膘融?
不會
** 項(xiàng)目結(jié)構(gòu)
IRIS.png