React Hooks

本文最后更新于:2022年7月12日 下午

Hooks

Why Hooks

  • Class 组件的复用不能直接继承,往往需要无页面逻辑的高阶组件;

  • Hooks 使函数式组件获得类组件能力,拥有自己的状态和响应式;

  • 副作用代码不用分散到各种生命周期中;

    除了转为视图的代码都是副作用代码

usage

  1. Hooks 只能在组件顶层作用域使用
  2. 必须保证所有 Hooks 都能被执行到并且按顺序执行
  3. Hooks 只能在函数和其他 Hook 中使用

常用 Hooks

useState

定义状态和改变状态的函数

const [currentValue,setNewValue] = React.useState(initState)

// 改变状态
setNewValue(newValue)
setNewValue(value=>{...newValue})

//  如果initState只需要在组件mount时执行一次,可以设置惰性返回值
const [state1, setState1] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

setState的同步和异步

  1. 合成事件和生命周期中的setState是异步的
  2. 原生事件和setTimeout中的setState是同步的
  3. 使用setStatecallback总能拿到最新的值
  4. setState 自带批处理优化,多次调用只会执行最新的一次

合成事件

React 对原生 DOM 事件都做了代理,且事件都是绑定到 app 根节点上,
通过事件冒泡时的 srcElement查找到目标节点上的事件标识,再去合成事件库中寻找相应的回调。

useContext

const MyContext = createContext()

<Mycontext.Provider value={666}>
	<MyComponent />
</Mycontext.Provider>

//...MyContext
const {value} = useContext(MyContext)

useEffect

定义副作用

React.useEffect(() => {
	// 副作用操作
	return () => {
		// 在组件销毁和effect重新执行之前执行
	};
}, [stateValue]); // 为[]则相当于didMount

useEffect(fn); //  组建每次渲染都执行
useEffect(fn, []); //  Mount时执行
useEffect(fn, [dep]); //  Mount 和 dep 变化时
useEffect(() => {
	return () => {};
}, []); //  unMount执行

// useEffect中使用异步函数
useEffect(()=>{
	(async()=>{
		await ...
	})()
})


useEffect(async()=>{...})// 报错!useEffect返回的是cleanup函数,加上async后回返回一个promise

useEffectcomponentDidMount 的区别

  1. useEffectcommit执行完之后会异步执行,不阻塞渲染
  2. componentDidMount/componentDidUpdatemutation执行完时候和layout同步执行
  3. useLayoutEffect能实现和👆一样的效果

useCallback

缓存函数实例

function Counter() {
	const [count, setCount] = useState(0);
	const handleIncrement = () => setCount(count + 1);
	// 默认每次重新渲染组件,都会创建新的handleElement函数
	return <button onClick={handleIncrement}>+</button>;
	// ⚠️同时由于创建了新函数,会导致接收函数的组件也重新渲染

	const handleIncrement = useCallback(() => setCount(count + 1), [count]);
	// 将count加入这个callback的依赖数组,只有当count改变时才创建新的函数,创建新的闭包并更新组件
}

useMemo

缓存计算结果

// 如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算,进而也可以避免不必要的渲染
const usersToShow = useMemo(() => {
  if(!users) return null;
  return users.data.filter((user) => {
    return user.first_name.includes(searchKey)
  })
},[users,searchKey])
// 脑补Vue的computed就懂了

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useCallback 和 useMemo 的 dep 数组传空则只返回首次执行结果

useRef

保存的数据发生变化不会触发重新渲染

  1. 存储跨组建的数据
const timer = ref(null);
//  一个存活在活动组件中的定时器
const startTimer = useCallback(() => {
	timer.current = setInterval(() => {
		/*do some*/
	});
}, []);
const stopTimer = useCallback(() => {
	cleatInterval(timer.current);
	timer.current = null;
}, []);
  1. 存储 DOM 引用
const xxxRef = useRef(null);
<input ref={xxxRef}></input>;

useContext

使用 provide 的 context

const text1 = {
  p1:'请输入'
}
const SomeContext = React.createContext(text1)

<SomeContext.Provider value={text1}>
      // 内部的组件就可以通过
      const xxx = useContext(SomeContext)
      // 来使用xxx数据
      <input placeholder={xxx.p1}>
</SomeContext>

路由 Hooks

  • useHistory
  • useLocation
  • useRouteMatch
  • useParams

校验 Hooks

通过 eslint 插件eslint-plugin-react-hooks

{//eslint config
...
"rules":{
  "react-hooks/rules-of-hooks":"error",
  //检查依赖项的声明
  "react-hooks/exhaustive-deps":"warn"
  }
...
}

自定义 Hooks

命名约定以use开头;

在函数内使用到了其他 Hooks

  • 获取异步请求结果
const useAsyncReq = (reqFunc) => {
	const [data, setData] = useState(null);
	const [isLoading, setLoading] = useState(false);
	const [error, setError] = useState(null);

	const execute = useCallback(() => {
		// 在mount和reqFunc变化都会开始一次新请求
		setLoading(true);
		setData(null);
		setError(null);

		return reqFunc()
			.then((res) => {
				setData(res);
			})
			.catch((err) => {
				setError(err);
			})
			.finally(() => {
				setLoading(false);
			});
	}, [reqFunc]);

	export { execute, isLoading, data, error };
};
  • useScroll
const getPosition = ()  => {
  x:document.body.scrollLeft,
  y:document.body.scrollTop
}

const useScroll = () => {
  const [position,setPosition] = useState(getPosition())

  useEffect(()=>{
    // mount时执行一次
    const handler = () => setPosition(getPosition())
    // 添加监听scroll
    document.addEventListener('scroll',handler)
    // 组件销毁时取消监听
    return () => document.removeEventListener('scroll',handler)
  },[])

  return position
}
  • useInput
const useInput = (initValue) => {
	const [value,setValue] = useState(initValue)
	return {
		value,
		onChange:e=>setValue(e.target.value)
	}
}
const nameInput = useInput('')
<input {...nameInput} placeholder="please input username"></input>
  • useLatest
const useLatest = <T>(value: T) => {
	const refed = useRef<T>(value);
	refed.current = value;
	return refed;
};
  • useCreated
/**  比较依赖数组是否变化 */
const isDepChange = (oldDeps:DependencyList, newDeps:DependencyList) => {
	if (oldDeps === newDeps) return false;
	for (let i = 0; i < oldDeps.length; i++) {
		if ((!Object.is(oldDeps[i]), newDeps[i])) {
			return true;
		}
	}
	return false;
};
const useCreated = <T>(func:()=>T,deps:DependencyList) => {

	const { current } = useRef({
		deps,
		obj : undefined as undefined | T,
		initialized:false
	})

	if(current.initialized === false || isDepChange(current.deps,deps)){
		current.deps = deps
		current.obj = func()
		current.initialized = true
	}

	return current.obj as T
};

React Hooks
http://yoursite.com/2022/03/02/react_hooks/
作者
tatekii
发布于
2022年3月2日
许可协议