React Hooks + 函数式组件是 React 官方推荐的写法,允许在函数式组件中使用状态,逻辑更清晰,并且让代码复用变得更简单。
useState:修改数据自动更新视图
- 参数为初始值
- 不能在 useEffect 中 setState:setState 会触发组件刷新,组件刷新会触发 useEffect
- 不要使用太多 useState,可以合并为一个对象
const Counter = () => {
const [count, setCount] = useState(0)
return (
<>
<span> {count} </span>
<span onClick = {() => setCount(count + 1)}> 加1 </span>
</>
)
}
useEffect:组件构建和刷新时触发
- 使用多个 useEffect 实现关注点分离
- 第二个参数指定依赖的状态,这些状态没变化的时候跳过 effect
const Counter = (props) => {
const [count, setCount] = useState(0)
// 等同于 componentDidMount 和 componentDidUpdate
useEffect(() => {
document.title = `现在是${count}`
// 这个函数在 componentWillUnmount 时执行
return () => {
document.title = 'counter'
}
})
const [data, setData] = useState(null)
useEffect(() => {
const getData = async () => {
setData( await fetch(`xxx/${props.id}`) )
}
getData()
}, [props.id])
return (
<>
<span> {count} </span>
<span onClick = {() => setCount(count + 1)}> 加1 </span>
<span> {data} </span>
</>
)
}
useContext:提供跨组件的状态共享api
- 只把需要全局共享的数据,如主题和用户id,放到 context 中,否则频繁的刷新可能会导致性能问题
- 刷新会导致相应的组件树刷新
const users = {
user1: {
name: lisi
},
user2: {
name: zhangsan
}
}
const UserContext = React.createContext(null)
const App = () => {
return (
<UserContext.Provider value={users.user1}>
<SubComp></SubComp>
</UserContext.Provider>
)
}
const SubComp = () => {
const user = useCOntext(UserContext)
return (
<span> {user.name} </span>
)
}
useReducer:redux 式的状态管理
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + (action.payload || 1)
}
case 'decrement':
return {
count: state.count - (action.payload || 1)
}
default:
throw new Error()
}
}
const init = (initialCount) => {
return {count: initialCount}
}
const initialState = {count: 0}
// const initialCount = 0
const Counter = () => {
const [count, dispatch] = useReducer(reducer, initialState)
// 惰性初始化
// const [count, dispatch] = useReducer(reducer, init, initialCount)
return (
<>
<span> {count} </span>
<span onClick={() => dispatch( {type: 'increment'} )}> 加1 </span>
<span onClick={() => dispatch( {type: 'decrement'} )}> 减1 </span>
<span onClick={() => dispatch( {type: 'decrement', payload: 5} )}> 减5 </span>
</>
)
}
useCallback:缓存函数
-
减少不必要的子组件刷新
-
子组件需要父组件传入函数时,父组件的刷新会导致子组件的刷新
-
函数是对象,组件刷新后函数 !== 刷新前的函数
-
useCallback 的使用同样有成本,所以只在子组件刷新成本高的时候用
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo:缓存数据
- 仅用做性能优化,可能在依赖未更新时重新计算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef:提供不随组件刷新而变化的对象
const refContainer = useRef(initialValue)
refContainer.current = 'newRef'
useImperativeHandle:限制ref暴露的api
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useLayoutEffect
useLayoutEffect与componentDidMount、componentDidUpdate的调用阶段是一样的。推荐先用useEffect,只有当它出问题的时候再尝试使用useLayoutEffect。
useDebugValue:使自定义 Hook 可以在 React 开发者工具显示
- 编写共享库的时候用
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
// 第二个参数可选,用于格式化数据
useDebugValue(date, date => date.toDateString());
自定义 Hook:名称为 use 打头的函数
- 内部可以调用其他 Hook
结语
常用 Hook:useState、useEffect;全局共享状态:useContext,尽量只用于需要全局共享的数据;另一种 useState:useReducer;性能优化:useCallback、useMemo、useRef,不做提前优化;命令式访问组件、获取整个组件生命周期内不变的对象:useRef。自定义 Hook 可以实现逻辑的进一步抽象和复用。