《十二》React Hooks 基础和最重要的钩子

相比类组件,函数组件有一些自己的缺陷:

  1. 类组件可以定义自己的 State,用来保存组件内部的状态;函数组件不可以,因为函数每次调用都会产生新的临时变量,并且改变变量的值的时候,组件也不会重新渲染。
    import React from 'react'
    
    function FuncComponent() {
      let message = 'Hello JS'
    
      const handleMessageChange = () => {
        // 修改 message 之后,函数组件无法知道自己是需要重新渲染的
        message = 'Hello React'
      }
    
      return (
        <h1 onClick={handleMessageChange}>{message}</h1>
      )
    }
    
    export default FuncComponent
    
  2. 类组件有自己的生命周期,可以在对应的生命周期中完成需要的逻辑;函数组件没有生命周期。
  3. 类组件在重新渲染时,只会重新执行 render 函数;函数组件在重新渲染时,整个函数都会被重新执行。

针对上面出现的情况,开发者通常都会编写类组件。但是类组件也存在自己的问题,简而言之,就是类组件比较复杂,学习成本和编写成本都比较高。

Hooks 的出现,可以解决上面提到的问题。

Hooks 是 React V16.8 新增的特性。Hooks 的意思是钩子,本质上其实就是 JavaScript 函数,由于函数组件本身是没有 State、生命周期等特性的,Hooks 可以在另外一个地方保存好这些东西,在函数组件需要的时候把这些特性钩进来,可以让开发者在函数组件中就可以使用 State 以及生命周期等其他 React 的特性。

其实 Hooks 就是把类组件中的优势吸收到了函数组件中,对函数组件进行了增强;同时还保持了函数组件的简洁性。

Hooks 的使用规则:

  1. Hooks 只能在 React 的函数组件或者自定义 Hook 中使用,不能在类组件或者其他普通的 JS 函数中使用。
  2. Hooks 只能在 React 函数组件的顶层调用,不能在循环、条件判断或者子函数中调用。
    import React, {useState} from 'react'
    
    function Count() {
      // 正确
      const [count, setCount] = useState(0)
    
      // 错误
      if (true) {
        const [count, setCount] = useState(0)
      }
    
      return ( <h1>{count}</h1>)
    }
    
    export default Count
    

React 默认提供了一些常用的钩子,也可以封装自己的钩子。钩子一律使用 use 前缀命名,便于识别。

最重要的钩子:

React 中最重要的钩子是 useState()useEffect()

useState() 状态钩子:

useState():给函数组件钩入状态,与类组件中的 State 的功能完全相同。接收一个状态的值作为参数,在函数组件首次渲染时作为初始值,如果不传的话默认为 undefined。返回值是一个数组,数组的第一个元素是当前状态的值,第二个元素是设置状态值的函数,设置状态值的函数更新 State 变量时,是替换它而不是合并它。

在函数执行完退出后,正常来说变量都会消失,但是 State 中的变量会被 React 内部管理保留下来。
State 只在函数组件首次渲染时被创建,在下一次重新渲染时,useState 会返回之前保存的 State。

组件的重新渲染和组件的卸载是两回事。

import React, {useState} from 'react'

function Count() {
  console.log('渲染组件') // 函数组件初始渲染会打印;点击 button 按钮,调用 setCount 函数组件重新渲染会打印

  // 1. 调用 useState。参数传入 0 作为状态的初始值。返回值是一个数组,利用数组的解构,将状态的值命名为 count,设置状态值的函数命名为 setCount
  const [count, setCount] = useState(0)
  
  // useState 也可以接收一个函数作为参数,返回一个状态的初始值
  // const [count, setCount] = useState(() => {
  //  	 return 0
  // })

  const handleCountChange = () => {
    // 3. 修改状态的值
    // 调用 setCount,会做两件事:1.为 count 设置一个新的值;2.重新渲染函数组件,useState 会维护组件内部的状态,所以会根据最新的值返回 DOM
    setCount(count + 1)
     
     // 参数也可以是一个函数,接收原来的状态值,返回新的状态值
     // setCount((count => {
     //   return count +1
     // }))
  }

  return (
    <>
      {/* 2. 获取状态的值 */}
      <button onClick={handleCountChange}>{count}</button>
    </>
  )
}

export default Count

请添加图片描述可以在函数组件中多次使用 useState() 定义多个变量,会按照声明的顺序依次执行。

function FuncComponent() {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState('Hello JS')

  return (
    <>
      <h1>{count}</h1>
      <h1>{message}</h1>
    </>
  )
}

useEffect() 副作用钩子:

useEffect():给函数组件钩入类似于类组件中的生命周期的功能。接收一个函数作为参数,在函数组件渲染完成后会自动回调这个函数,就可以在这个函数中执行一些事件监听、网络请求、手动更新 DOM 等的副作用操作。

可以把 useEffect() 看做 componentDidMount()componentDidUpdate()componentWillUnmount() 这三个生命周期钩子的组合。

import React, {useState, useEffect} from 'react'

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

  // 告知 React,在函数组件渲染完成后要执行的有副作用的代码。相当于给函数组件钩入了 componentDidMount()、componentDidUpdate()
  useEffect(() => {
  	console.log('传入 useEffect 的函数被执行') // 函数组件初始渲染会打印;点击 button 按钮,函数组件重新渲染会打印
  	
    document.title = count
  })

  const handleCountChange = () => {
    setCount(count + 1)
  }

  return (
    <>
      <button onClick={handleCountChange}>{count}</button>
    </>
  )
}

export default Count

请添加图片描述
默认情况下,函数组件无论是第一次渲染,还是更新后重新渲染,都会执行传入 useEffect() 中的那个回调函数。可以给 useEffect() 传入第二个参数,是一个数组,来决定该 useEffect() 在哪些 State 发生变化时才执行。如果只想初始渲染时执行一次,那么第二个参数可以传入一个空数组。

import React, {useState, useEffect} from 'react'

function FuncComponent() {
 const [count, setCount] = useState(0)
 const [message, setMessage] = useState('Hello JS')

 useEffect(() => {
   console.log('只有更新 count 时才执行,更新 message 不会执行')
 }, [count])

 return (
   <>
     <button onClick={() => setCount(count +1)}>{count}</button>
     <button onClick={() => setMessage('Hello React')}>{message}</button>
   </>
 )
}

export default FuncComponent

在一个函数组件中,可以存在多个 useEffect(),会按照声明的顺序依次执行。

import React, {useEffect} from 'react'

function FuncComponent() {
  useEffect(() => {
  	document.title = '多个 Effect'
  })
  
  useEffect(() => {
    const timer = setTimeout(() => {
      // ...
    }, 1000)

    return () => {
      clearTimeout(timer)
    }
  })

  return (<h1>多个 Effect</h1>)
}

export default FuncComponent

需要清除的 Effect:

在类组件中,某些副作用的操作,例如定时器等,需要在 componentWillUnmount() 中清除。

在函数组件中,传入 useEffect() 中的函数也可以返回一个清除函数,返回的清除函数会在函数组件重新渲染或者卸载的时候自动执行(初始渲染不会),就可以在这个清除函数中执行一些清除副作用的操作。

返回的清除函数在函数组件重新渲染执行时,会先执行清除上一个 Effect 的副作用。

useEffect(() => {
 ChatAPI.subscribe(props.id, handleStatusChange)
 return () => {
   ChatAPI.subscribe(props.id, handleStatusChange)
 }
})

假如第一次渲染的时候 props 是 { id: 10 },第二次渲染的时候是 { id: 20 }
执行的顺序并不是:

  1. React 清除了 {id: 10} 的 Effect。
  2. React 渲染 {id: 20} 的 UI。
  3. React 运行 {id: 20} 的 Effect。

而是:

  1. React 渲染 {id: 20} 的 UI。
  2. React 清除了 {id: 10} 的 Effect。
  3. React 运行 {id: 20} 的 Effect。
import React, {useEffect} from 'react'

function FuncComponent() {
  useEffect(() => {
    const timer = setTimeout(() => {
      // ...
    }, 1000)

    // 返回的清除函数会在函数组件重新渲染或者卸载的时候自动执行,可以在其中执行一些清除副作用的操作。相当于给函数组件钩入了 componentWillUnmount()
    return () => {
      console.log('返回的清除函数被执行') // 函数组件重新渲染会打印;函数组件卸载会打印

      clearTimeout(timer)
    }
  })

  return ( <h1>清除机制</h1>)
}

export default FuncComponent
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值