DTUED

你应该知道的Hooks知识

2021-12-01  本文已影响0人  袋鼠云数栈前端

Hooks

HooksReact16.8 的新增特性,能够在不写 class 的情况下使用 state 以及其他特性。

动机

Hooks 规则

函数组件和类组件的不同

函数组件能够捕获到当前渲染的所用的值。

点击查看示例

对于类组件来说,虽然 props是一个不可变的数据,但是 this是一个可变的数据,在我们渲染组件的时候 this 发生了改变,所以 this.props 发生了改变,因此在 this.showMessage 中会拿到最新的 props 值。

对于函数组件来说捕获了渲染所使用的值,当我们使用 hooks 时,这种特性也同样的试用于 state 上。

点击查看示例

const showMessage = () => {
    alert("写入:" + message);
};

const handleSendClick = () => {
    setTimeout(showMessage, 3000);
};

const handleMessageChange = (e) => {
    setMessage(e.target.value);
};

如果我们想跳出'函数组件捕获当前渲染的所用值‘这个特性,我们可以采用 ref 来追踪某些数据。通ref.current可以获取到最新的值

const showMessage = () => {
    alert("写入:" + ref.current);
};

const handleSendClick = () => {
    setTimeout(showMessage, 3000);
};

const handleMessageChange = (e) => {
    setMessage(e.target.value);
    ref.current = e.target.value;
};

useEffect

useEffect 能够在函数组件中执行副作用操作(数据获取/涉及订阅),其实可以把 useEffect 看作是 componentDidMount / componentDidUpdate / componentWillUnMount 的组合

对于 useEffect 的执行,React 处理逻辑是采用异步调用的,对于每一个 effectcallback 会像 setTimeout 回调函数一样,放到任务队列里面,等到主线程执行完毕才会执行。所以 effect 的回调函数不会阻塞浏览器绘制视图

  1. 相关的生命周期替换方案
React.useEffect(()=>{
    //请求数据,事件监听,操纵DOM
},[]) //dep=[],只有在初始化执行
/* 
  因为useEffect会捕获props和state,
  所以即使是在回调函数中我们拿到的还是最初的props和state
*/
React.useEffect(()=>{
    /* 请求数据 , 事件监听 , 操纵dom , 增加定时器,延时器 */
    return function componentWillUnmount(){
        /* 解除事件监听器 ,清除定时器,延时器 */
    }
},[])/* 切记 dep = [] */

//useEffect第一个函数的返回值可以作为componentWillUnmount使用
React.useEffect(()=>{
    console.log('props变化:componentWillReceiveProps')
},[ props ])
React.useEffect(()=>{
    console.log('组件更新完成:componentDidUpdate ')     
}) //没有dep依赖项,没有第二个参数,那么每一次执行函数组件,都会执行该 effect。
  1. useEffect 中[]需要处理什么

React 官网 FAQ这样说:

只有当函数(以及它所调用的函数)不引用 propsstate 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略,使用 eslint-plugin-react-hooks 帮助我们的代码做一个校验

点击查看详细示例

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}
//只会做一次更新,然后定时器不再转动
  1. 是否应该把函数当做 effect 的依赖
const loadResourceCatalog = async () => {
    if (!templateType) return
    const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
    const res: any = await API[reqApi]()
    if (!res.success) return
    setCatalog(res.data)
}

useEffect(() => {
    loadResourceCatalog();
}, [])
//在函数loadResourceCatalog中使用了templateType这样的一个state
//在开发的过程中可能会忘记函数loadResourceCatalog依赖templateType值

第一个简单的解法,对于某些只在 useEffect 中使用的函数,直接定义在 effect 中,以至于能够直接依赖某些 state

useEffect(() => {
    const loadResourceCatalog = async () => {
        if (!templateType) return
        const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
        const res: any = await API[reqApi]()
        if (!res.success) return
        setCatalog(res.data)
    }
    loadResourceCatalog();
}, [templateType])

假如我们需要在很多地方用到我们定义的函数,不能够把定义放到当前的 effect 中,并且将函数放到了第二个的依赖参数中,那这个代码将就进入死循环。因为函数在每一次渲染中都返回一个新的引用

const Template = () => {
    const getStandardTemplateList = async () => {
        const res: any = await API.getStandardTemplateList()
      if (!res.success) return;
        const { data } = res;
        setCascaderOptions(data);
        getDefaultOption(data[0])
    }
    useEffect(()=>{
        getStandardTemplateList()
    }, [])
}

针对这种情况,如果当前函数没有引用任何组件内的任何值,可以将该函数提取到组件外面去定义,这样就不会组件每次 render 时不会再次改变函数引用。

const getStandardTemplateList = async () => {
    const res: any = await API.getStandardTemplateList()
  if (!res.success) return;
    const { data } = res;
    setCascaderOptions(data);
    getDefaultOption(data[0])
}

const Template = () => {
    useEffect(()=>{
        getStandardTemplateList()
    }, [])
}

如果说当前函数中引用了组件内的一些状态值,可以采用 useCallBack 对当前函数进行包裹

const loadResourceCatalog = useCallback(async () => {
    if (!templateType) return
    const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
    const res: any = await API[reqApi]()
    if (!res.success) return
    setCatalog(res.data)
}, [templateType])

useEffect(() => {
    loadResourceCatalog();
}, [loadResourceCatalog])
//通过useCallback的包裹,如果templateType保持不变,那么loadResourceCatalog也会保持不变,所以useEffect也不会重新运行
//如果templateType改变,那么loadResourceCatalog也会改变,所以useEffect也会重新运行

useCallback

React 官网定义

useCallback(fn, deps)

返回一个 memoized 回调函数,该回调函数仅在某个依赖项改变时才会更新

import React, { useCallback, useState } from "react";

const CallBackTest = () => {
  const [count, setCount] = useState(0);
  const [total, setTotal] = useState(0);
  const handleCount = () => setCount(count + 1);
  //const handleCount = useCallback(() => setCount(count + 1), [count]);
  const handleTotal = () => setTotal(total + 1);

  return (
    <div>
      <div>Count is {count}</div>
      <div>Total is {total}</div>
      

      <div>
        <Child onClick={handleCount} label="Increment Count" />
        <Child onClick={handleTotal} label="Increment Total" />
      </div>
    </div>
  );
};

const Child = React.memo(({ onClick, label }) => {
  console.log(`${label} Child Render`);
  return <button onClick={onClick}>{label}</button>;
});

export default CallBackTest;

点击查看详细示例

React.memo 是通过记忆组件渲染结果的方式来提高性能,memoreact16.6 引入的新属性,通过浅比较(源码通过 Object.is 方法比较)当前依赖的 props 和下一个 props 是否相同来决定是否重新渲染;如果使用过类组件方式,就能知道 memo 其实就相当于 class 组件中的 React.PureComponent,区别就在于 memo 用于函数组件。useCallbackReact.memo 一定要结合使用才能有效果。

使用场景

useMemo

React 官网定义

返回一个 memoized

仅会在某个依赖项改变时才重新计算 memoized 值,这种优化有助于避免在每次渲染时都进行高开销的计算 useCallback(fn, deps) 相当于useMemo(() => fn, deps),对于实现上,基本上是和 useCallback 相似,只是略微有些不同

使用场景

两个 hooks 内置于 React 都有特别的原因:

1.引用相等

当在 React 函数组件中定义一个对象时,它跟上次定义的相同对象,引用是不一样的(即使它具有所有相同值和相同属性)

大多数时候,你不需要考虑去优化不必要的重新渲染,因为优化总会带来成本。

  1. 昂贵的计算
    计算成本很高的同步计算值的函数

总结

本文介绍了 hooks 产生动机、函数组件和类组件的区别以及 useEffect / useCallback / useMemo 等内容。重点介绍了 useEffect 的生命周期替换方案以及是否把函数作为 useEffect 的第二参数。

参考链接

When to useMemo and useCallback

How to fetch data with React Hooks

A Complete Guide to useEffect

How Are Function Components Different from Classes?

useCallback、useMemo 分析 & 差别

上一篇 下一篇

猜你喜欢

热点阅读