[译]每个开发者都应该知道的 4 个自定义 React Hooks

99 阅读3分钟

这个系列文章主要是用于练习英文文档的读写能力,期待半年后能脱离工具写出通畅的英文文档。原文地址

我的朋友们,如果我早点学习这 4 个 React hooks,我能写出更优雅的代码。

它们极大的提高了我的工作效率、代码的可伸缩性和可读性。你也想要学习它们吗?

1. useMount

当我们需要在组件第一次渲染发起 HTTP 请求或者一些其他初始化的逻辑时,我们往往会写出如下代码:

useEffect(() => {
	// festch request...
}}

这非常简单,但是它有一个很严重的缺点:当我们给 useEffect 传入一个空数组时,语义不够清晰。

所以我们可以自定义一个 hook : useMount ,仅当组件第一次渲染时才会执行对应的回调函数。

const useMount = (callback) => {
	useEffect(callback, [])
}

示例

import { useState } from 'react';

const Mount = () => {
  const [count, setCount] = useState(0);
  useMount(() => {
    console.log('first mounted');
  });
  return <div onClick={() => setCount(count + 1)}>{count}</div>;
};

export default Mount;

2. useUnmount

当我们需要在组件卸载时触发一个逻辑,比如:清理定时器。我们往往会写出如下代码:

useEffect(() => {
	return () => {
		// 当组件卸载时执行
	}
}, []}

显然我们也不能很直观的看出这会在组件卸载时执行。

所以我们需要自定义个 hook:useUnmount ,仅当组件卸载时才会执行回调函数

const useUnmount = (callback) => {
	useEffect(() => {
		return callback
	}, [])
}

示例

import { useState } from 'react';

const Child = () => {
	const [ count, setCount ] = useState(0)
  useUnmount(() => {
    console.log('Child component is unmount', count);
  });
  return (
    <div>
      count: {count}
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
};

const Unmount = () => {
  const [showChild, setShowChild] = useState(true);
  return (
    <div>
      {showChild && <Child />}
      <div onClick={() => setShowChild(!showChild)}>Toggle Child</div>
    </div>
  );
};

export default Unmount;

当 “Child” 组件卸载时,useUnmuount 的回调函数才真正执行,但是 count 的值会一直是 0,这是为什么呢?

这是因为在 useEffect 内部有一个闭包,回调函数是在组件第一次渲染时传入的。为了获取到最新的状态,我们需要配合 useRef 来实现:

import { useRef, useEffect } from 'react'

const useUnmount = (callback) => {
	const callbackRef = useRef(callback)
	callbackRef.current = callback
	useEffect(() = {
		return callbackRef.current()
	}, [])
}

3. useUpdateEffect

有时候我们想只在依赖项发生改变时才执行代码逻辑,我们往往会写出如下代码:

import { useState, useEffect } from 'react'

const UpdateEffect = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log('count is changed:', count)
  }, [count])
  return <div onClick={() => setCount(count + 1)}>{count}</div>
}

不幸的是,当组件挂载时会执行一次 useEffect 打印 count 的值为 0。怎么才能实现只有当 count 的值发生变化时才会执行回调函数?

const useUpdateEffect = (callback, deps) => {
  const mountedRef = useRef(false);
  const callbackRef = useRef(callback);
  callbackRef.current = callback;
  useEffect(() => {
    if (mountedRef.current) {
      callbackRef.current();
    } else {
			mountedRef.current = true;
		}
  }, [deps]);

  useEffect(() => {
    return () => mountedRef.current = false
  }, []);
};

4. useSetState

class 组件中我们使用 this.setState 来更新组件数据,并且 setState 会自动的处理对象类型的数据

const [ person, setPerson ] = React.useState({
  name: 'fatfish',
  age: 100
})
// Modify name
setPerson({
  ...person,
  name: 'medium'
})
// Modify age
setPerson({
  ...person,
  age: 1000
})
// Use setState to modify name
setState({
  name: 'medium'
})
// Use setState to modify age
setState({
  age: 1000
})

我们需要实现一个 hook:useSetState ,来简化 setPerson 的操作。这非常简单,只需要对 useState 进行简单的包装:

import { useState } from 'react'

const useSetState = (initState) => {
  const [state, setState] = useState(initState);

  const setMergeState = (modifyValue) => {
    setState((preState) => {
      const newState =
        typeof modifyValue === 'function' ? modifyValue(preState) : modifyValue;
      return newState
        ? {
            ...preState,
            ...newState,
          }
        : preState;
    });
  };
  return [state, setMergeState];
};

示例