掌握React基础知识第八章-React Hooks

310 阅读6分钟

2022即将到来,前端发展非常迅速,现在的前端已经不再是写写简单的页面了,伴随着web大前端,工程化越来越火,也出现了很多框架。很多公司也不单单是使用单一的框架了,作为前端开发国内最火的两大框架,都是必须要掌握的。所以,我决定在这疫情肆虐的年底把React学习一下,也为自己将来找工作多一分竞争力...

学习阶段,如有不对之处,欢迎评论区留言,我及时更正

本文已连载,其它章节传送门⬇️

第一章-jsx语法认识

第二章-函数组件和class类组件

第三章-Props和State

第四章-事件处理

第五章-Ref和Refs

第六章-生命周期

第七章-跨组件通信Context

React Hooks

Hook 是 React 16.8 的新增特性,为函数组件提供状态、生命周期等class组件才有的特性。Hook只能在函数组件中使用,函数组件有了Hook就不再是无状态组件了。

简单介绍下hook,它是一些可以让你在函数组件中“钩入” React state 及生命周期等特性的函数,react内置了一些hook函数,我们也可以自定义自己的hook。需要注意的是,hook函数只能在函数组件内部调用,不能嵌套在普通js函数和if else 以及for循环之中。因为react是按照hook的调用顺序来识别区分每一个hook的,如果调用顺序不同会到导致react不知道是哪个hook

注意:class相关的API 在hooks中不可以使用,函数组件没有生命周期钩子函数,也没有state和this相关用法...

useState

useState Hook,为函数组件提供状态,并初始化数据,修改数据

import { useState } from 'react'
import ReactDom from 'react-dom'
function Demo() {
/**
	useState可以接收函数类型的初始值
	const [state, setstate] = useState(() => {return 0})
	
*/ 

  const [state, setstate] = useState(0)

  const handleClick = () => {
    setstate(state + 1)
  }

  return (
    <div>
      <h1>{state}</h1>
      <button onClick={handleClick}>点击</button>
    </div>
  )
}
ReactDom.render(<Demo />, document.querySelector('#root'))

React中导入useState 并且在函数组件中调用,接收一个初始值参数,参数可以是任意值。返回一个数组,数组的第一位就是传入的初始值,第二位是一个我们用来修改此数据的函数

动画1.gif

当我们调用useState 函数第二个返回值来修改组件数据的时候,react将使用新的数据替换旧的数据,并且组件会重新渲染,多次点击调用setstate 修改数据,我们的useState会拿到上一次的数据,而不是每次渲染都重新初始化。但需要注意的是,引用类型数据的修改,我们要用新的数据去替换掉旧的数据,而不是直接修改旧数据,不然组件不会更新重新渲染,例如我们把代码改下:

...
const [state, setstate] = useState({ name: 'ls' })
...
const handleClick = () => {
    state.name = '小明'
    setstate(state)
  }

动画1.gif

可以看到,无论我们怎么点击都无法生效的,因为引用类型是一个地址,如果我们想修改数据,必须用一个新的对象去覆盖旧的对象

useState参数可以写成箭头函数的形式

const [state, setstate] = useState(() => {
    console.log('useState')
    return {
      name: 'ls'
    }
  })

useEffect

Effect Hook 可以让你在函数组件中执行副作用操作,副作用指操作DOM,获取数据,发布订阅等等... 官网有说明你可以把Effect Hook 看作 componentDidMount componentDidUpdatecomponentWillUnmount 三个生命周期钩子函数的组合

import ReactDOM from 'react-dom'
import React, { useState, useEffect } from 'react'

export default function Demo() {
  const [count, setCount] = useState(0)
  const handleClick = () => {
    setCount(count + 1)
  }
  useEffect(() => {
    document.title = `你点击了${count}次`
  })
  return (
    <div>
      <p>点击了{count}次</p>
      <button style={{
        width: '70px', height: '30px', 
        backgroundColor: '#1E90FF', color: 'white',
        outline:'none', border: 'none',
        borderRadius: '5px'
      }} 
        onClick={handleClick}
      >
        点击 +1
      </button>
    </div>
  )
}

ReactDOM.render(<Demo/>, document.querySelector('#root'))

动画1.gif

可以看到当点击的时候去修改document的title,这些就属于渲染组件额外的事情,属于副作用代码,当然就需要写到useEffect 里面喽~effect 会在组件渲染后以及组件更新后执行

useEffect的依赖项

依赖项就是能够设置useEffect的依赖,只在count变化时,才会执行相应的effect。比如我们修改下代码:

const [money, setMoney] = useState(100)

const changeMoney = () => {
    setMoney(
      money + 10
    )
  }

return (
    <div>
      <h1>Count:{count}</h1>
      <h1>Money:{money}</h1>
      <button onClick={handleClick}>修改count</button>
      <button onClick={changeMoney}>修改Money</button>
    </div>
  )

动画1 (1).gif 可以看到,首次渲染执行一次,当我们点击修改count和修改Money的时候都执行了一次。 但是当我们点击修改Money的时候其实并不需要去重新执行effect的 因为count并没有改变,这时候我们就需要为effect添加一个依赖

useEffect(() => {
    console.log('useEffect副作用执行了')
    document.title = `当前已点击${count}次`
  },[count])

动画1.gif

useEffect接收第二个为数组的参数,里面是我们effect依赖到的数据,此时我们再点击想修改Money的时候 就不会再执行effect函数了,这样就跳过了不必要的执行

我们还可以把依赖项设置为一个空数组,这样该effect只会在 组件第一次渲染的时候执行,此时可以看作是componentDidMount 钩子函数

useEffect清理副作用

清理副作用就是当组件销毁的时候,我们需要清除的一些操作,比如取消监听事件,定时器等等...

useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行了')
    })

    return () => { Window.clearInterval(timer) }
  })

useEffect 还可以实现componentWillUnmount 钩子函数的作用,通过return一个函数来实现,此函数会在每次useEffect 的回调函数执行之前执行一次,目的是为了清除上一次副作用带来的影响和组件销毁的时候执行

useContext

useContext Hook 能够订阅context的值和读取context的变化,相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer> 。这样我们在函数组件中也能够使用context共享数据了

import React, { useContext } from 'react'
import ReactDOM from 'react-dom'
const obj = {
  name: '小明',
  age: 18
}
const userContext = React.createContext(obj)

function Son() {
  const user = useContext(userContext)
  return (
    <div>
      <p>姓名:{user.name}</p>
      <p>年龄:{user.age}</p>
    </div>
  )
}
function Person() {
  return (
    <div>
      <Son/>
    </div>
  )
}
function App() {
  return (
    <div>
      <userContext.Provider value={{name: '小红', age: 20}}>
        <Person/>
      </userContext.Provider>
    </div>
  )
}
ReactDOM.render(<App/>, document.querySelector('#root'))

创建三个组件,并创建context共享数据,我们需要在子组件Son里面使用useContext读取离它自身最近的context值,此时就需要在Son组件里用到此方法。该Hook会伴随离它最近的Provider的value值变化而重新触发,官方也有说明:调用了 useContext 的组件总会在 context 值变化时重新渲染 传送门

自定义 Hook

除了React给我们提供的一些内部的hook,我们还可以实现自己的hook。当我们有一段逻辑在多个组件中都使用到的时候,我们就可以提取成一个单独的自定义hook,这样只需在用到的组件中引入而无需每个组件都写一遍

export default function Index() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  })
  useEffect(() => {
    const move = (e) => {
      setPosition({
        x: e.pageX,
        y: e.pageY
      })
    }
    document.addEventListener('mousemove',move)
    return () => {
      document.removeEventListener('mousemove', move)
    }
  },[])
  return (
    <div>
      <p>x轴:{position.x}</p>
      <p>y轴:{position.y}</p>
    </div>
  )
}

例如我们有一段获取坐标轴的逻辑,在多个组件中都需要使用这个坐标轴,那么我们就可以把这段逻辑抽取为一个单独的hook.js文件中去

新建hook.js

// 新建hook.js
import {useState, useEffect} from 'react'

export function useMove() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  })
  useEffect(() => {
    const move = (e) => {
      setPosition({
        x: e.pageX,
        y: e.pageY
      })
    }
    document.addEventListener('mousemove',move)
    return () => {
      document.removeEventListener('mousemove', move)
    }
  },[])
  return position
}

在组件中引入此hook,修改代码:


import ReactDOM from 'react-dom'
import React from 'react'
import {useMove} from './hook'
export default function Index() {
  const { x, y } = useMove()
  return (
    <div>
      <p>x轴:{x}</p>
      <p>y轴:{y}</p>
    </div>
  )
}

ReactDOM.render(<Index/>, document.querySelector('#root'))

这样我们在任意一个组件都可以引入此hook,但是需要注意的是 自定义hook名必须是和react内部hook一样的 采用小驼峰命名规则