React Hooks 中文官网 zh-hans.reactjs.org/docs/hooks-…
React Hooks
内置 Hook | 描述 |
---|---|
useState | 维护状态 |
useEffect | 完成副作用操作 |
useContext | 使用共享状态 |
useReducer | useState 的替代方案,类似 redux |
useCallback | 缓存函数及其引用 |
useMemo | 缓存函数及其值 |
useRef | 访问 DOM |
useImperativeHandle | 使用子组件暴露的值和方法 |
useLayoutEffect | 完成副作用操作,区别于 useEffect,此 hook 会阻塞浏览器绘制 |
useState
在 React 函数组件上添加内部 state
import React, { useState } from 'react';
export default () => {
const [count, setCount] = useState(0);
return (
<>
<h1>count: {count}</h1>
{/* 普通更新 */}
<button onClick={()=>setCount(count + 1)}>add</button>
{/* 函数式更新 */}
<button onClick={()=>setCount(prevCount => prevCount + 1)}>add</button>
</>
);
};
useEffect
在函数组件中执行副作用操作 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
import React, { useState, useEffect } from 'react';
export default () => {
const [count, setCount] = useState(0);
useEffect(()=>{
console.log('useEffect执行了:componentDidMount');
}, [])
useEffect(()=>{
console.log('useEffect执行了:componentDidUpdate');
}, [count])
useEffect(()=>{
return ()=>{
console.log('useEffect执行了:componentWillUnmount');
}
}, [])
return (
<>
<h1>count: {count}</h1>
<button onClick={()=>setCount(count + 1)}>add</button>
</>
);
};
useContext
Context 提供了一种在组件之间共享值的方式,而不必显式地通过组件树逐层传递 props
import React, { useContext } from 'react';
const data = {
name: 'falco',
age: '18'
}
const ComponentContext = React.createContext(data);
const Child = () => {
const { name, age } = useContext(ComponentContext);
return (
<div>
{name} - {age}
</div>
)
}
const Parent = () => {
return (
<>
<ComponentContext.Provider value={data}>
<Child/>
</ComponentContext.Provider>
</>
)
}
export default Parent;
useReducer
是 useState 的替代方案。接收一个形如 (state, action) => newState 的 reducer, 并返回当前的 state 以及与其配套的 dispatch 方法
import React, { useReducer } from 'react';
const initialState = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
]
const reducer = (state, action) => {
const { type, payload } = action;
switch(type){
case 'add':
return [...state, payload];
case 'update':
return state.map(item => item.id === payload.id ? { ...item, ...payload } : item);
case 'remove':
return state.filter(item => item.id !== payload.id);
case 'clear':
return []
default:
throw new Error()
}
}
export default () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
List: {JSON.stringify(state)}
<div>
<button onClick={()=>dispatch({type: 'add', payload: { id: 3, name: '王五' }})}>add</button>
<button onClick={()=>dispatch({type: 'update', payload: { id: 3, name: '赵六' }})}>update</button>
<button onClick={()=>dispatch({type: 'remove', payload: { id: 1 }})}>remove</button>
<button onClick={()=>dispatch({type: 'clear', payload: { id: 1 }})}>clear</button>
</div>
</>
)
}
useCallback
useCallback 和 useMemo 都可缓存函数的引用或值,但是从更细的使用角度来说 useCallback 缓存函数的引用,useMemo 缓存计算数据的值
import React, {
useState,
useRef,
useEffect,
useCallback
} from 'react';
const usePrevProps = value => {
const ref = useRef(null)
useEffect(()=>{
ref.current = value
}, [])
return ref.current
}
const Button = React.memo(({ title, onClick }) => {
console.log('render button');
return <button onClick={onClick}>{title}</button>;
});
export default () => {
const [count, setCount] = useState(0);
const [total, setTotal] = useState(0);
// const handleCount = () => setCount(count + 1);
const handleCount = useCallback(() => setCount(count + 1), [])
const handleTotal = () => setTotal(total + 1);
const prevHandleCount = usePrevProps(handleCount);
console.log('两次处理函数是否相等:', prevHandleCount === handleCount); // true
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<br />
<div>
<Button title={'add Count'} onClick={handleCount} />
<Button title={'add Total'} onClick={handleTotal} />
</div>
</div>
)
}
useMemo
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
import React, {
useState,
useRef,
useEffect,
useCallback,
useMemo
} from 'react';
const usePrevProps = value => {
const ref = useRef(null)
useEffect(()=>{
ref.current = value
}, [])
return ref.current
}
export default () => {
const [count, setCount] = useState(0);
const [total, setTotal] = useState(0);
// 当count变量值改变的时候才会执行useMemo第一个入参的函数
const calcValue = useMemo(()=>{
return Array(100000)
.fill('')
.map(v => v);
}, [count])
const handleCount = () => setCount(count + 1);
const handleTotal = () => setTotal(total + 1);
const prevCalcValue = usePrevProps(calcValue);
console.log('两次计算结果是否相等:', prevCalcValue === calcValue); // false
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<br />
<div>
<button onClick={handleCount}>Increment Count</button>
<button onClick={handleTotal}>Increment Total</button>
</div>
</div>
)
}
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
import React, { useRef } from 'react';
export default () => {
const inputEl = useRef(null);
const handleFocus = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
}
return (
<>
<input ref={inputEl} />
<button onClick={handleFocus}>Focus</button>
</>
)
}
useImperativeHandle
可以让父组件调用到子组件暴露出来的属性/方法。 useImperativeHandle 应当与 forwardRef 一起使用
import React, {
useState,
useRef,
forwardRef,
useImperativeHandle
} from 'react';
const Child = forwardRef((props, ref) => {
const inputRef = useRef(null);
const [value, setValue] = useState(0);
useImperativeHandle(ref, ()=>({
focus: () => {
inputRef.current.focus()
},
setValue
}))
return (
<>
<div>child-value: {value}</div>
<input ref={inputRef}/>
</>
)
})
export default () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
}
const handleAdd = () => {
inputEl.current.setValue(val => val + 1)
}
return (
<>
<Child ref={inputEl} />
<button onClick={handleFocus}>Focus</button>
<button onClick={handleAdd}>add</button>
</>
)
}
useLayoutEffect
在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。 在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新,也就是说它会阻塞浏览器绘制。 所以尽可能使用 useEffect 以避免阻塞视觉更新。
import React, {
useState,
useRef,
useEffect,
useLayoutEffect
} from 'react';
const UseEffect = () => {
const box = useRef(null);
useEffect(()=>{
box.current.style.backgroundColor = 'green';
}, [])
return (
<div
ref={box}
style={{
width: 200,
height: 200,
backgroundColor: 'red',
}}
>
UseEffect
</div>
)
}
const UseLayoutEffect = () => {
const box = useRef(null);
useLayoutEffect(()=>{
box.current.style.backgroundColor = 'green';
}, [])
return (
<div
ref={box}
style={{
width: 200,
height: 200,
backgroundColor: 'pink',
}}
>
UseLayoutEffect
</div>
)
}
export default () => {
const [visible, setVisible] = useState(false);
return (
<div className='App'>
<button onClick={()=>setVisible(!visible)}>挂载/卸载组件</button>
{
visible && (
<div style={{ display: 'flex' }}>
<UseEffect />
<UseLayoutEffect />
</div>
)
}
</div>
)
}