Hooks API

useEffect

// deps 是依赖数组,可以为空 
useEffect(() => {
  console.log('useEffect')
},  [deps]);

useEffect的使用 参考上篇文章

执行时机:

  • useEffect可以看做componentDidMount、componentDidUpdate、componentWillUnmount生命周期的结合。
  • 只有当deps里的变量发生改变时,才会执行。当第二参数是空数组,该effect 只会在组件didMount和willUnmount后执行;当第二个参数为空时,组件每次渲染都会执行。

useMemo

// 返回一个有记忆的值(memoized)
const memoizedValue = useMemo(() => computeExpensiveValue(deps),deps);

// useMemo 可以帮助避免子组件的不必要的rerender

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => , [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => , [b]);
  return (
    
{child1} {child2}
) }

执行时机:

  • useMemo组件rendering 的时候执行,在useEffect之前。因此副作用应放在useEffect里,而不是useMemo。
  • 只有当deps发生变化时,useMemo 才会重新计算memoizedValue, 避免渲染时不必要的计算。当第二个参数为空时,组件每次渲染都会执行。

useCallback

// 返回一个有记忆的callback
const memoizedCallback = useCallback(
  () => {
    doSomething(deps);
  },
  [deps],
);

执行时机:

  • 只有当deps发生变化时,memoizedCallback才会改变。这对通过传递callbacks来优化子组件的渲染很有帮助。(配合子组件的shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用)
// useCallback 与useMemo
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

useCallback 的真正目的还是在于缓存了每次渲染时 inline callback 的实例,这样方便配合子组件的 shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用。

useCallback 适用于所有的场景吗?
我们知道useCallback也是有依赖项的,如果一个 callback 依赖于一个经常变化的 state,这个 callback 的引用是无法缓存的。React 文档的 FAQ 里也提到了这个问题,还原一下问题的场景:

function Form() {
  const [text, setText] = useState('');

  const handleSubmit = useCallback(() => {
    console.log(text);
  }, [text]); // 每次 text 变化时 handleSubmit 都会变

  return (
    
setText(e.target.value)} /> // 很重的组件
); }

官网提出,如果要记忆化的callback函数是一个事件处理器,rendering时不需要调用,那么,你可以用useRef创建一个实例变量,手动把最新的值存进来。例如:

function Form() {
  const [text, updateText] = useState('');
  const textRef = useRef();

  useEffect(() => {
    textRef.current = text; // Write it to the ref
  });

  const handleSubmit = useCallback(() => {
    const currentText = textRef.current; // Read it from the ref
    alert(currentText);
  }, [textRef]); // Don't recreate handleSubmit like [text] would do

  return (
    
updateText(e.target.value)} />
); }

但是,上面的额解决方案也有缺陷,只有在 DOM 更新时才对 ref.current 做更新,会导致在 render 阶段不能调用这个函数。更严重的是,因为对 ref 做了修改,在未来的 React 异步模式下可能会有诡异的情况出现(因此上文中官方的解法也是”异步模式不安全“的)。

怎么避免深度传递callback?

考虑到上面提到的弊端,目前推荐的解决方案是使用useReducer,传递dispatch通过context。reducer 其实是在下次 render 时才执行的,所以在 reducer 里,访问到的永远是新的 props 和 state。

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // useReducer 返回的 dispatch 函数是自带 memoize 的
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    
      
    
  );
}

Any child in the tree inside TodosApp can use the dispatch function to pass actions up to TodosApp:

function DeepChild(props) {
  // If we want to perform an action, we can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    
  );
}

TodosApp的任何一个子组件都可以使用dispatch函数向TodosApp传递actions.

如果你想同时把 state 作为 context 传递下去,请分成两个 context 来声明。

你可能感兴趣的:(Hooks API)