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。
  2. 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 剖析 & 差异
网络的导航,是从输出 url 到最终获取到文件的过程。其中牵扯到浏览器架构、操作系统、网络等一系列常识。本文将从各个角度具体阐述这一过程,波及广度与深度。如果您是曾经有肯定根底的同学,那么本文能够疾速带你系统化整顿碎片化常识。