React Hooks的理解

175 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

1.Hooks是什么

  • Hook 是 React 16.8 的新增特性,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
  • Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”
  • 经过多年的实战,函数组件是一个更加匹配React的设计理念 UI = f(data),也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生。

2.Hooks的出现解决了什么

  1. 组件的逻辑服用
  • 在Hooks出来之前,函数组件能够做的只是接受 Props、渲染 UI ,以及触发父组件传过来的事件。所有的处理逻辑都要在类组件中写,这样会使 class 类组件内部错综复杂,每一个类组件都有一套独特的状态,相互之间不能复用。
  1. class组件自身的问题
  • 类组件是一种面向对象思想的体现,类组件之间的状态会随着功能增强而变得越来越臃肿,代码维护成本也比较高,例如:生命周期的使用,this指向问题等。而且不利于后期维护。

3.为什么要使用自定义Hooks

自定义 hooks 是在 React Hooks 基础上的一个拓展,可以根据业务需求制定满足业务需要的组合 hooks ,更注重的是逻辑单元。通过业务场景不同,到底需要React Hooks 做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义 hooks 产生的初衷。

自定义 hooks 也可以说是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。

  • 注意:自定义hooks需要以use开头

4.常用的hook有哪些

  • useState
  • useEffect
  • useRef
  • useContext
  • useMemo
  • useCallback
  • useReducer
  • 自定义hook等

1.useState

用useState来获取初始状态的读取与修改。

1.useState的基本使用

  • useState的作用:为函数组件提供了状态(state)
  1. 导入useState函数
  2. 调用 useState 函数,并传入状态的初始值,拿到初始状态及方法
  3. 调用修改状态的方法更新状态
// 1.导入useState函数
import { useState } from 'react'

function App() {
  // 2.调用useState函数,并传入初始值
  const [count, setCount] = useState(0)
  return (
    <div className="App">
      {/* 3.调用状态及方法 */}
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  )
}
export default App

image.png

2.组件的更新过程

  • 组件第一次渲染
    a. 从头开始执行该组件中的代码逻辑
    b. 调用 useState(0) 将传入的参数作为状态初始值,即:0。
    c. 渲染组件,此时,获取到的状态 count 值为: 0。
  • 组件第二次渲染
    a.点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染。
    b. 组件重新渲染时,会再次执行该组件中的代码逻辑。
    c. 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1。
    d. 再次渲染组件,此时,获取到的状态 count 值为:1。
  • 注意useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值

3.使用的规则

  • useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
function List(){
  // 以数字为初始值
  const [count, setCount] = useState(0)
  // 以数组为初始值
  const [arr,setArr] = useState([])
}
  • 只能在函数组件中或者其他hook函数中使用useState。
  • 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)。
let num = 1
function List(){
  num++
  if(num / 2 === 0){
     const [str, setStr] = useState('xiaoming')
  }
  const [arr,setArr] = useState([])
}
  • 可以通过开发者工具查看hooks状态。

2. useEffect

useEffect函数的作用就是为react函数组件提供副作用处理的!

1.什么是副作用域

作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)。

2.常见的副作用域

  1. 发送ajax请求
  2. 手动修改dom
  3. localstorage操作

3.useEffect的基本使用

// 1.导入useState、useEffect函数
import { useState, useEffect } from 'react'

function App() {
  // 2.调用useState函数,并传入初始值
  const [count, setCount] = useState(0)

  useEffect(() => {
    // dom操作
    const ti = document.getElementById('ti')
    ti.innerHTML = `当前是${count}次`
  })

  return (
    <div className="App">
      <p id='ti'></p>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  )
}
export default App

4.依赖项控制执行时机

  1. 不添加依赖项
  • 组件首次渲染执行一次,之后不管那个状态更改都会再次重新执行。
useEffect(() => {
    console.log('副作用域执行了')
})
  1. 添加空依赖
  • 组件只在首次渲染时执行一次
useEffect(() => {
    console.log('副作用域执行了')
}, [])
  1. 添加特定依赖项
  • 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行。
import { useState, useEffect } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('何必辨认我')

  useEffect(() => {
    console.log('副作用域执行了')
  }, [count,])

  return (
    <div className="App">
      <p>{count}</p>
      <p>{name}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <button onClick={() => setName('何必辨认我=>')}>更改名字</button>
    </div>
  )
}
export default App
  1. 清理副作用
  • 如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑。
import { useEffect, useState } from "react"

const App = () => {
  const [count, setCount] = useState(0)
  const [falg, setFalg] = useState(true)
  useEffect(() => {
    console.log('副作用域执行了')
    return () => {
      // 用来清理副作用的事情
      console.log('清除副作用函数')
    }
  }, [falg])
  return (
    <div>
      {falg ? <p>{count}</p> : null}
      <button onClick={() => setFalg(!falg)}>显隐</button>
    </div>
  )
}
export default App

3.useRef

在函数组件中获取真实的dom元素对象或者是组件对象。接受一个状态null作为初始值,返回一个ref对象上有一个current属性就是ref对象需要获取的内容。

  1. 导入 useRef 函数
  2. 执行 useRef 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例)
  3. 通过ref 绑定 要获取的元素或者组件
import { useEffect, useRef } from "react"


const App = () => {
  const h1Ref = useRef(null)

  useEffect(() => {
    console.log(h1Ref)
  }, [])

  return (
    <div>
      <h1 ref={h1Ref}>我是h1标签</h1>
    </div>
  )
}
export default App

4.useContext

可以使用 useContext ,来获取父级组件传递过来的context值,这个当前值就是最近的父级组件Provider设置的value 值,useContext参数一般是由createContext方式创建的 ,也可以父级上下文context传递的(参数为 context)。useContext可以代替context.Consumer来获取Provider中保存的value值。

  1. 使用createContext 创建Context对象
  2. 在顶部组件通过Provider 提供数据
  3. 在底部组件通过useContext函数获取数据
import { createContext, useContext } from "react"
// 创建context对象
const Context = createContext()

function Person() {
  return (
    <div>
      <p>Person</p>
      <Son />
    </div>
  )
}

function Son() {
  // 底部组件通过useContext函数获取数据
  const name = useContext(Context)
  return (
    <div>
      <p style={{ color: 'pink' }}>Son {name}</p>
    </div>
  )
}

const App = () => {
  return (
    // {/* 顶部组件通过Provider提供数据 */}
    <Context.Provider value={'hello react'}>
      <div> <Person /> </div>
    </Context.Provider>
  )
}
export default App

5.useMemo

  • useMemo可以在函数组件render上下文中同步执行一个函数逻辑,这个函数的返回值可以作为一个新的状态缓存起来。

我们在没有使用useMemo的时候

import { useState, } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const [value, setValue] = useState('')

  const fcCount = () => {
    console.log('hello react')
    return count + 1
  }

  return (
    <div>
      <p>count:{count}</p>
      <p>fcCount: {fcCount()}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <br />
      <input
        value={value}
        onInput={(e) => {
          setValue(e.target.value)
        }}
      />
    </div>
  )
}
export default App

运行结果操作如图,不管是点击add还是在输入框输入都会重新计算fcCount函数。 image.png 这时我们优化代码如下

import { useState, useMemo } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const [value, setValue] = useState('')

  const fcCount = useMemo(() => {
    console.log('hello react')
    return count + 1
  }, [count])

  return (
    <div>
      <p>count:{count}</p>
      <p>fcCount: {fcCount}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <br />
      <input
        value={value}
        onInput={(e) => {
          setValue(e.target.value)
        }}
      />
    </div>
  )
}
export default App

这时候我们运行会发现输入框输入不会重新计算fcCount了。这也就达到了我们想要的效果。 image.png

6.useCallback

返回一个memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

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

最后

感谢大家的耐心阅读,如果对你有用,感谢你的分享与点赞^-^。