React Hooks学习笔记(二)

193 阅读9分钟

useState

作用:数据存储,派发更新。

使得react无状态组件能够像有状态组件一样,可以拥有自己state。

类组件中,会在constructor里面定义状态this.state,使用setState来更新。函数组件就可以使用useState来创建状态。

使用方法:

接受一个参数作为初始值。 返回一个数组,第一个值为状态,第二个值为改变状态的函数。

import React,{ useState } from 'react'

function StateFunction () {
    const [name, setName] = useState('函数')

    return (
        <div onClick={ () => setName('我使用hooks变成这样了') }>
            {name}
        </div>
    )
}

export default StateFunction

useEffect

在函数组件中,当组件完成挂载、dom渲染完成之后,使用useEffect做一些操纵dom、请求数据的操作。

数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用。 因为渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用。

使用方法:

1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回一个函数,先执行返回函数,再执行参数函数

如果不接受第二个参数,则在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数。

如果第二个参数是空数组[ ],相当于没有发生变化,useEffect不依赖于state、props中的任意值,只会在首次渲染完成的时候调用一次,可以用来获取页面初始数据。

如果第二个参数是一个包含一个或多个值的数组,如[num]、[num,val],那么当数组中的值(任意一项即可)改变时,都会重新触发回调函数。注意这个值的改变是浅比较

使用useEffect函数来模拟生命周期函数

componentDidMount

会在组件挂载后(插入 DOM 树中)立即调用。该阶段通常进行以下操作:

  • 执行依赖于DOM的操作;
  • 发送网络请求;(官方建议)
  • 添加订阅消息(会在componentWillUnmount取消订阅);

可用useEffect传入空数组模拟,只在第一次渲染后执行。

useEffect( () => {console.log('第一次渲染')},[])

componentDidUpdate

componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。

该阶段通常进行以下操作:

  • 当组件更新后,对 DOM 进行操作;
  • 如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。 该方法有三个参数:
  • prevProps: 更新前的props
  • prevState: 更新前的state
  • snapshot: getSnapshotBeforeUpdate()生命周期的返回值 useEffect传入数组即可模拟。每次数组中的值发生改变,就会更新渲染。
useEffect( () => {console.log('n变了,更新渲染')},[n])

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作:

  • 清除 timer,取消网络请求或清除
  • 取消在 componentDidMount() 中创建的订阅等;

useEffect里面return一个函数会在销毁的时候执行。

useEffect( () => {
    console.log('第一次渲染')
//return出来的函数本来就是更新前、销毁前执行的函数,现在不监听任何状态,所以只在销毁前执行
    return () => {
        console.log('组件销毁')
    }
})

如果我们需要在组件销毁的阶段,做一些取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数的结尾返回一个函数,用于清除这些副作用。相当于componentWillUnmount。

    useEffect(()=>{
       /* 定时器 延时器等 */
       const timer = setInterval(()=>console.log(666),1000)
       /* 事件监听 */
       window.addEventListener('resize', handleResize)
       /* 此函数用于清除副作用 */
       return function(){
           clearInterval(timer) 
           window.removeEventListener('resize', handleResize)
       }
    },[ a ])

注意:当useEffect返回一个函数时,会先执行返回函数,再执行参数函数!

useEffect( () => {
    console.log('参数函数')
    const updateMouse = (e) => {
        console.log('打印当前位置')
        setPositions({ x:e.clientX, y:e.clientY })
    }
    document.addEventListener('click',updateMouse) //  添加绑定方法事件(要修改依赖,绑定到依赖上)

    return () => {
        //  在每次执行useEffect之前都会执行上一次return中内容
        document.removeEventListener('click',updateMouse)
        //  移除绑定方法事件(要修改依赖,绑定到依赖上)
        console.log('组件销毁')
    }
})

首次进入页面会先执行除return以外的内容,也就是将updateMouse方法绑定到click事件上。接着将这一次useEffect中的清除事件监听removeEventListener返回出去,但是此时并没有执行return中的内容

第一次点击的时候,打印当前鼠标页面坐标,然后执行上一次return返回出去的内容,也就是执行上一次return中的清除事件监听的方法,清除了上一个useEffect中的绑定事件

然后再开始执行新的useEffect中的绑定事件方法,并再次将该次useEffect清除事件绑定的方法return返回出去,如此就形成了一个链式一样的过程。

当页面卸载的时候,会执行最后一次return返回出来的清除事件绑定的方法,这样也就保证了页面卸载的时候,移除了绑定添加的DOM事件方法。

另外,useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而componentDidMonut和componentDidUpdate中的代码都是同步执行的。

useLayoutEffect

渲染更新之前的 useEffect

使用方法和useEffect一样,只是执行时机不同。

useEffect执行顺序: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调

useLayoutEffect执行顺序: 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成

useLayoutEffect 代码可能会阻塞浏览器的绘制。

useMemo

性能优化

形成独立的渲染空间,能够使组件,变量按照约定好规则更新。渲染条件依赖于第二个参数【依赖数组】。无状态组件的更新是从头到尾的更新,如果想要重新渲染一部分视图,而不是整个组件,那么useMemo是最佳方案,避免了不需要的更新和不必要的上下文执行。

类组件使用ShouldcomponentUpdate来限制更新次数,memo就像无状态组件的ShouldcomponentUpdate,memo函数针对的是一个组件的渲染,是否重复执行。(<Foo/>) useMemo更为细小,针对的是一段函数逻辑,是否重复执行。(() => {})

memo的作用结合了pureComponent纯组件和 ShouldcomponentUpdate功能,会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。 memo包裹的组件,给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值。

useMemo和memo差不多,都是判定是否满足当前的限定条件来决定是否执行useMemo的callback函数,而useMemo的第二个参数是一个依赖数组,数组里的参数变化决定了useMemo是否更新回调函数,useMemo返回值就是经过判定更新的结果。它可以应用在元素上,应用在组件上,也可以应用在上下文当中。

被useMemo包裹起来的上下文,形成一个独立的闭包,会缓存之前的state值,如果没有加相关的更新条件,是获取不到更新之后的state的值的。

使用方法

1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回的是一个值。返回值可以是任何,函数、对象等都可以

useMemo与useEffect的不同就是调用时机 —— useEffect执行的是副作用,所以一定是在渲染之后运行的;而useMemo是需要有返回值的,返回值会参与渲染,所以useMemo是是在渲染期间完成的。

import React, { useMemo } from 'react'

useMemo(() => {}, [] ) 

应用场景

优化需要计算的场景

import React, { useState, useMemo } from 'react';

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

    const double = useMemo(() => {
        return count * 2
    }, [count === 3])

  return (
      <div>
          <button onClick={() => setCount(count + 1)}>点我加一</button>
          <h1>count: {count}</h1>
          <h1>double: {double}</h1>
      </div>
  )
}

export default App;

这里是否重新渲染Double组件是根据表达式count === 3来判断的。

count === 3值为true或者false,当从false变为true或者从true变为false时,double值会更新渲染。

所以不断点击按钮,count和double的变化如下:

count:0  double:0
count:1  double:0
count:2  double:0
count:3  double:6
count:4  double:8
count:5  double:8

父子组件的重新渲染

使用useMemo(() => { return onClick }, [])来包裹onClick函数,让它不会每一次都重新渲染,而是只渲染一次。

只有当App组件里的count值为3和4的时候,Counter组件才重新渲染。

import React, { useState, useMemo, memo } from 'react';

const Counter = memo(function Counter(props) {
    console.log('Counter');
    return (
        <h1>Counter: {props.counter}</h1>
    )
})

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

    const double = useMemo(() => {
        return count * 2
    }, [count === 3])

    const onClick = useMemo(() => {
        return () => {
            console.log('click')
        }
    }, [])

  return (
      <div>
          <button onClick={() => setCount(count + 1)}>点我加一</button>
          <h1 onClick={props.onClick}>count: {count}</h1>
          <Counter counter={double} onClick={onClick}/>
      </div>
  )
}

export default App;

useCallback

useCallback就是useMemo可以省略一层函数的写法。

useMemo(() => fn, []) 就等于 useCallback(fn, [])

const onClick = useMemo(() => {
        return () => {
            console.log('click')
        }
}, [])
const onClick = useCallback(() => {
  console.log('click')
}, [])

useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据。

共同作用:仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。

两者区别:

  • useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值,应用场景如: 需要计算的状态
  • useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化,整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,减少资源浪费。

应用场景

一般父组件更新,子组件也会更新。但是如果父组件传入子组件的内容不变,那么子组件某些操作(某些操作是指需要跟随传入内容的改变而同步进行的操作)是没必要执行的,这会影响页面性能,所以我们可以对这情况进行优化。

直接用上面的例子:

import React, { useState, useMemo, memo, useCallback } from 'react';

const Counter = memo(function Counter(props) {
    console.log('Counter');
    return (
        <h1 onClick={props.onClick}>Counter: {props.counter}</h1>
    )
})

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

    const double = useMemo(() => {
        return count * 2
    }, [count === 3])

    const onClick = useCallback(() => {
        console.log('click')
    }, [])

  return (
      <div>
          <button onClick={() => setCount(count + 1)}>点我加一</button>
          <h1>count: {count}</h1>
          <Counter counter={double} onClick={onClick}/>
      </div>
  )
}

export default App;
如果有函数传递给子组件,使用useCallback

如果有值传递给子组件,使用useMemo

分割线~~~~~~~~~

整理不完了,后续再弄