你应该知道的Hooks知识

Hooks

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

动机

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

Hooks 规则

  • 只有在最顶层使用 Hooks不要再循环/条件/嵌套函数中使用`
  • 只有在 React 函数中调用 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 的组合

  • 第一个参数是一个 callback,返回 destorydestory 作为下一个 callback 执行前调用,用于清除上一次 callback 产生的副作用
  • 第二个参数是依赖项,一个数组,可以有多个依赖项。依赖项改变,执行上一个 callback 返回的 destory,和执行新的 effect 第一个参数 callback

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

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

//useEffect第一个函数的返回值可以作为componentWillUnmount使用
  • componentWillReceiveProps 替代方案
    其实两者的执行时机是完全不同的,一个在 render 阶段,一个在 commit 阶段,useEffect 会初始化执行一次,但是 componentWillReceiveProps 只会在 props 变化时执行更新
React.useEffect(()=>{
    console.log('props变化:componentWillReceiveProps')
},[ props ])
  • componentDidUpdate 替代方案
    useEffectcomponentDidUpdate 在执行时期虽然有点差别,useEffect 是异步执行,componentDidUpdate 是同步执行 ,但都是在 commit 阶段
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 一定要结合使用才能有效果。

使用场景

  • 作为 props,传递给子组件,为避免子元素不必要的渲染,需要配合 React.Memo 使用,否则无意义
  • 作为 useEffect 的依赖项,需要进行比较的时候才需要加上 useCallback

useMemo

React 官网定义

返回一个 memoized

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

使用场景

  • 避免在每次渲染时都进行高开销的计算

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

1.引用相等

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

  • 依赖列表
  • React.memo

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

  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 分析 & 差别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容