阅读 1107

从Vue2.0到React17——React函数组件的生命周期

“这是我参与更文挑战的第4天,活动详情查看: 更文挑战

前言

React为类组件提供了一系列的生命周期钩子函数,有constructorgetDerivedStateFromPropsrendercomponentDidMountshouldComponentUpdategetSnapshotBeforeUpdatecomponentDidUpdatecomponentWillUnmount。但是在函数组件中无法使用些钩子函数,故得使用React Hook模拟这些钩子函数。

1、模拟constructor

在函数式组件中是没有constructor的概念,函数组件不需要构造函数,回顾一下类组件需要在constructor函数中实现那些任务。

  • 在其他语句之前前调用super(props),否则this.props为undefined;
  • 通过给this.state 赋值对象来初始化内部state;
  • 为事件处理函数绑定实例,否在函数中无法使用this

在函数组件中组件props可以通过函数的参数传入,利用 ES6 解构赋值的功能,来获取组件的参数数据,并可以给参数数据设置默认值。

import React from 'react';
const Index = (props) =>{
  const {title = 'hellow React'} = props;
  return(
    <div>{title}</div>
  )
}
export default Index;
复制代码

通过useState这个Hook来初始化state,const [state, setState] = useState(initialState),其中initialState就是一个state的初始值。

假如state的初始值需要经过一系列复杂的计算才能得到,可以传一个函数给useState

import React, { useState } from 'react';
const HelloWorld = (props) =>{
  const {num} = props;
  const getAamout = (num) =>{
    return num + 1;
  }
  const [amount,serAmount] = useState(() =>{return getAamout(num)})
  return(
    <div>{amount}</div>
  )
}
export default HelloWorld;
复制代码

state的初始值在组件挂载时才会用到,为了避免组件更新时再次调用getAamout函数获取amount初始值,故把getAamout函数包裹在() =>{return getAamout(num)}中,再传递给useState

2、模拟getDerivedStateFromProps

getDerivedStateFromProps钩子函数的作用是用组件的props派生出一个新的state,且这个state受props完全控制。

例如用num的prop派生出一个amount的state,先要把num的prop上一轮的值存在一个prevNum的state变量中以便比较。

import React, { useState } from 'react';
const HelloWorld = (props) =>{
  const {num} = props;
  const [prevNum, setPrevNum] = useState(null);
  const [amount,serAmount] = useState(0);
  if (num !== prevNum) {
    serAmount(num+1);
    setPrevNum(num);
  }
  return(
    <div>{amount}</div>
  )
}
export default HelloWorld;
复制代码

组件更新时,都会执行if (num !== prevNum),若numprevNum不相等,就会执行serAmount(num+1)更新amount这个state,且执行setPrevNum(num)把当前num这个prop存到prevNum这个state中。

以上就是用 React Hook 模拟了getDerivedStateFromProps钩子函数的过程。

3、模拟render

render这是函数组件体本身。

4、模拟componentDidMount

useEffect来模拟,因为componentDidMount钩子函数在组件的生命周期中只执行一次,那么要执行只运行一次的useEffect(仅在组件挂载时执行),可以传递一个空数组([])作为第二个参数给useEffect,而第一个参数接收一个函数,在函数中执行和componentDidMount钩子函数中执行的事项。

import React, {useState,useEffect } from 'react';
const HelloWorld = (props) =>{
  const [amount,serAmount] = useState(0);
  useEffect(() =>{
    console.log('相当组件执行componentDidMount钩子函数')
    console.log(amount)
  }, [] )
  return(
    <div>{amount}</div>
  )
}
export default HelloWorld;
复制代码

6、模拟shouldComponentUpdate

shouldComponentUpdate钩子函数作用是在组件更新阶段,通过对比前后的state和props是否有变化,若有变化,在函数最后return true让组件更新,若是没有变化,在函数最后return false让组件不更新,这是一个用来优化性能的钩子函数。在函数组件中有三种方法可以实现shouldComponentUpdate钩子函数的功能,下面一一来介绍。

6.1 用React.memo模拟

React.memo包裹一个组件来对它的props进行比较,而且这个组件只能是函数组件。当组件的props发生变化才会去更新这个组件。

React.memo仅检查包裹的组件的props变更,不比较组件的state,若组件的state发生变更,无法阻止组件更新。

import React, { useEffect } from 'react';
const HelloWorld = React.memo((props) => {
  const { num } = props;
  useEffect(() => {
    console.log('更新')
  })
  return (
    <div>{num}</div>
  )
})
export default HelloWorld;
复制代码

其中对props的比较只是浅比较,如果要进行深比较,可以自定义的比较函数通过第二个参数传入React.memo来实现。比较函数接收变化前后的props作为参数。

import React, {useEffect } from 'react';
const HelloWorld = React.memo((props) =>{
  const {num} = props;
  useEffect(() =>{
    console.log('更新')
  })
  return(
    <div>{num}</div>
  )
},(prevProps, nextProps) =>{
  //更新
  return false
  //不更新
  return true
})
export default HelloWorld;
复制代码

当比较函数返回true则不更新,返回false则更新,这跟shouldComponentUpdate钩子函数的返回是相反的,要注意一下。

6.2 用useMemo模拟

useMemo(() => fn, deps)
复制代码

useMemo的第一参数是个函数,第二参数是数组,里面是要监听的数据,当数据发生改变时,重新执行第一参数。另外useMemo返回的是第一参数执行后返回的值。

若在第一参数函数中返回一个组件,那这个组件的更新由第二参数数组中的数据来控制,这样也间接实现了shouldComponentUpdate钩子函数。

但是由于在useMemo中不能使用useState定义组件的state,所以还是只能比较props来决定是否更新组件。

import React from 'react';
const HelloWorld = (props) =>{
  const {num} = props;
  return (
    <div>{num}</div>
  )
}
export default HelloWorld;
复制代码
import React,{useMemo,useState} from 'react';
import HelloWorld from './HelloWorld';
const Index = (props) =>{
  const [num,setNum] = useState(1)
  const Hello = useMemo(()=>{
    return (
      <HelloWorld num={num}></HelloWorld>
    )
  },[num])

  return (
    <div>
      {Hello}
    </div>
  )
}
export default Index;
复制代码

上面的HelloWorld组件,只有在num这个prop改变时才会更新。

使用useMemo包裹一个组件时,组件中有用到的props,一定添加到第二参数的数组中,否则当props更新了,组件中props还是旧数据。

6.3 用useCallback模拟

useCallbackuseMemo的区别是。useCallback返回的是一个函数,useMemo返回的是一个值。

useCallback的第一个参数是个函数,useCallback返回的就是这个函数。第二参数是数组,里面是要监听的数据,当数据发生改变时,才会重新返回第一参数。

若在第一参数函数是一个函数组件,那这个组件的更新由第二参数数组中的数据来控制,这样也间接实现了shouldComponentUpdate钩子函数。

import React from 'react';
const HelloWorld = (props) =>{
  const {num} = props;
  return (
    <div>{num}</div>
  )
}
export default HelloWorld;
复制代码
import React,{useCallback,useState} from 'react';
import HelloWorld from './HelloWorld';
const Index = (props) =>{
  const [num,setNum] = useState(1)
  const Hello = useCallback(()=>{
    return (
      <HelloWorld num={num}></HelloWorld>
    )
  },[num])

  return (
    <div>
      {Hello()}
    </div>
  )
}
export default Index;
复制代码

上述分别用useMemouseCallback来处理HelloWorld组件,返回Hello。唯一不同的是用useCallback处理时,要这样{Hello()}使用HelloWorld组件。

useCallback(fn, deps)相当于useMemo(() => fn, deps)

使用useCallback包裹一个组件时,组件中有用到的props,一定添加到第二参数的数组中,否则当props更新了,组件中props还是旧数据。

7、模拟getSnapshotBeforeUpdate

官方解释:目前还没有getSnapshotBeforeUpdate钩子函数的 Hook 等价写法,但很快会被添加。

8、模拟componentDidUpdate

useEffect来模拟,useEffect第一参数接受一个函数,第二参数是个数组,把要监听的props或state添加进去。当监听的props或state发生变化时,useEffect的第一参数就会执行。可以把componentDidUpdate钩子函数中要执行的代码,放在useEffect的第一参数函数中执行。

import React,{useState} from 'react';
const HelloWorld = (props) =>{
  const [num,setNum]=useState(1);
  const changNum = () =>{
      setNum(2);
  }
  useEffect(() =>{
    console.log('num数据发生变化')
  },[num])
  return (
    <div onClick={changNum}>{num}</div>
  )
}
export default HelloWorld;
复制代码

9、模拟componentWillUnmount

useEffect的第一参数函数中可以返回一个函数,当组件卸载时会执行这个函数。那么可以把componentWillUnmount钩子函数中要执行的代码,放在useEffect的第一参数函数中返回的函数中执行,以此来模拟componentWillUnmount钩子函数。

import React,{useState} from 'react';
const HelloWorld = (props) =>{
  const [num,setNum]=useState(1);
  useEffect(() =>{
    const timer = setinterval(() =>{
      console.log('定时器')
    },3000)
    return () =>{
      clearInterval(timer);//组件卸载时候会清除定时器
    }
  },[])
  return (
    <div>{num}</div>
  )
}
export default HelloWorld;
复制代码
文章分类
前端