React Hooks 概述

122 阅读3分钟

React Hooks + 函数式组件是 React 官方推荐的写法,允许在函数式组件中使用状态,逻辑更清晰,并且让代码复用变得更简单。

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

  • useLayoutEffectcomponentDidMountcomponentDidUpdate 的调用阶段是一样的。推荐先用 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 可以实现逻辑的进一步抽象和复用。