React Hooks(钩子函数)

Posted 甜甜酷盖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Hooks(钩子函数)相关的知识,希望对你有一定的参考价值。

React Hooks

什么是Hooks?

首先:React的组件创建方式,一种是类组件,一种是纯函数组件

React团队认为组件的最佳写法应该是函数,而不是类。

但是纯函数组件有着类组件不具备的特点:

  • 纯函数组件没有状态
  • 纯函数组件没有生命周期
  • 纯函数组件没有this

这就注定,纯函数组件只能做UI展示的功能,如果涉及到状态的管理与切换,我们就必须得用类组件或者redux,但是在简单的页面中使用类组件或者redux会使代码显得很重。

因此,React团队设计了React hooks(钩子)。

React Hooks的意思是:组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。

四种常用的钩子:

  • useState()
  • useReducer()
  • useContext()
  • useEffect()
  • useRef()

UseState()

我们知道,纯函数组件没有状态,useState()用于为函数组件引入状态。

举例:
state是一个普通变量:

//引入状态钩子useState()
import React,useState from 'react'
import './App.css';

function App() 
  //useState创造一个状态,赋值一个初始值,当前赋值的初始值为0
  //count是一个变量,此变量的值指向当前状态的值 相当于this.state
  //setcount是一个函数,此函数可以修改状态的值 相当于this.Setstate
  const [count,setCount] = useState(0)
  const addCount = ()=>
    setCount(count+1)
  
  return (
    <div className="App">
       <div>count</div>
       <button onClick = addCount>点击加1</button>
    </div>
  );


export default App;

state是一个对象:

setState()不会局部更新

意思是,如果state是一个对象,不能部分setState,所以我们使用…user将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖。

import React,useState from 'react'
import './App.css'

function App()
  const [user,setUser]=useState(age:'11',name:'Bob')
  const handerClick=()=>
      setUser(
          ...user,
          name:'jack'
      )
  
  return (
    <div className='App'>
        <h1>user.name</h1>
          <h1>user.age</h1>
          <button onClick=handerClick>
            Click
          </button>
    </div>
  )
 
 
 export default App;

改变前:

改变后:

useReducer()

useState() 的替代方案,用于包含多种状态,或者下一个 state 依赖于之前的 state,实现函数组件的状态管理。

基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。

举例:

点击加1,点击减1

//实现点击改变状态
import React,useReducer from 'react'
import './App.css';
function App()
  
  //useReducer(),state表示状态,action表示相关操作
  const reducer = (state,action)=>
    if (action.type === 'add') 
      return 
          ...state,
          count: state.count + 1,
      
    else if (action.type === 'jian') 
      return 
          ...state,
          count: state.count - 1,
      
     else 
      return state
     
  
 
  const addCount=()=>
    dispatch(
      type:'add'
    )
  
  const min=()=>
    dispatch(
      type:'jian'
    )
  
  const [state,dispatch] = useReducer(reducer,count:0)
  return(
    <div>
      <div>state.count</div>
      <button onClick=addCount>点击加1</button>
      <button onClick=min>点击减1</button>
    </div>
  )

export default App;

useContext()

useContext()用于在组件之间共享状态,而不必显式地通过组件树的逐层传递 props。

实现步骤:

  1. 使用createContext创建Context对象
  2. 在顶层组件通过provider提供数据
  3. 在底层组件通过useContext函数获取数据
//引入状态钩子useState()
import React,useContext from 'react'
import './App.css';
function App()
    //通过createContext来创建上下文
    const AppContext = React.createContext()

    const Achild = ()=>
        //在子组件中通过useContext来获取数据
        const name1 = useContext(AppContext)
        return(
            <div>
                这是组件A,使用的name值是:name1
            </div>
        )
    
    
    const Bchild = ()=>
        //在子组件中通过useContext(Context句柄)来获取数据
        const name2 = useContext(AppContext)
        return(
            <div>
                这是组件B,使用的name值是:name2
            </div>
        )
    
    return (
            //AppContext.Provider数据共享组件,来确定共享范围,通过value来分发内容
          <AppContext.Provider value=name1:'jack',name2:'Bob'>
              <Achild></Achild>
              <Bchild></Bchild>
          </AppContext.Provider>
        );

export default App;

useEffect()

useEffect()可以检测数据更新 。,可以用来更好的处理副作用,比如异步请求等。

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect()的依赖项。

只要数组发生改变,useEffect()就会执行。

当第二项省略不填时,useEffect()会在每次组件渲染时执行,这一点类似于componentDidMount。

useEffect回调在dom渲染完毕之后执行 和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了

举例:

第二个参数省略时:

import React,useState,useEffect from 'react'
import './App.css';
function App()
  const [loading,setLoading] = useState(true)
  //相当于componentDidMount
  //useEffect()第二个参数未填
  useEffect(()=>
    setTimeout(()=>
      setLoading(false)
    ,3000)
  )
  //loadling为true时显示Loading... 3秒后loading变成了false,显示内容加载完毕
  return (
    loading?<div>Loading</div>:<div>内容加载完毕</div>
  )

export default App;

第二个参数存在时:
name改变时会触发useEffect()函数,

import React,useState,useEffect from 'react'
import './App.css';
function AsyncPage(name)
    const [loading,setLoading] = useState()
    const [person,setPerson] = useState()
    //useEffect()函数在组件初始化执行一次,之后,name改变时才会执行
    //组件渲染时,两秒后从Loading变为Bob
    //name改变时,先从Bob变为Loading,两秒后变为指定名字
    useEffect(()=>
      setTimeout(()=>
        setLoading(false)
        setPerson(name)
      ,2000)
    ,[name])
    return(
      loading?<div>Loading...</div>:<div>person.name</div>
    )
  
  
function App()
  const [name,setName] = useState('Bob')
  const changeName = (name)=>
    setName(name)
  
  return (
    <div>
      <AsyncPage name = name/>
      <button onClick = ()=>changeName('Jack')>将名字改为jack</button>
      <button onClick = ()=>changeName('Tom')>将名字改为Tom</button>
    </div>
  )

export default App;

useEffect()返回一个函数:

当useEffect()返回一个函数时,该函数会在组件卸载时执行

举例:

当点击switch时,组件被卸载,定时器被清除,控制台不再打印。

import React,useEffect,useState from 'react'
import './App.css';

function Test ()
  useEffect(()=>
    let timer = setInterval(()=>
      console.log('定时器正在执行')
    ,1000)
    return ()=>
      //清除定时器
      clearInterval(timer)
    
  ,[])
  return(
    <div>this is test</div>
  )


function App()
  const [flag,setFlag] = useState(true)
  return (
    <div>
      flag?<Test/>:null
      <button onClick=()=>setFlag(false)>switch</button>
    </div>
  )


export default App;

useRef()

用于在函数组件中获取真实的DOM元素对象或者是组件实例。(因为函数组件没有实例,所以这里的获取组件实例指的是获取类组件实例)

使用步骤:

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

举例:

获取dom和组件实例,可以看到结果在控制台打印了出来

import React,useEffect, useRef from 'react'
import './App.css';

//组件实例 类组件(函数组件没有实例)
//dom对象 标签

class Test extends React.Component
  render()
    return (
      <div>我是类组件</div>
    )
  


function App()
  const testRef = useRef(null)
  const h1Ref = useRef(null)
  //useEffect回调在dom渲染完毕之后执行
  //和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了
  useEffect(()=>
    console.log(testRef.current)
    console.log(h1Ref.current)
  ,[])
  return(
    <div>
      /* 获取类组件实例 */
      <Test ref=testRef/>
      /* 获取DOM对象 */
      <h1 ref=h1Ref>this is h1</h1>
    </div>
  )

export default App;

自定义钩子函数

根据自己的业务需求,自行封装一个钩子函数以供自己使用。

举例:自定义一个获取表单数据的钩子函数

import React,useState from 'react'
import './App.css';


// 自定义hook(use开头)
// 重用受控表单创建state和onChange方法逻辑
/**
 * 
 * @param string | number initialValue 初始默认值
 * @returns 
 */
//获取表单数据
const useInput = (initialValue) => 
  const [value, setValue] = useState(initialValue)

  return 
    value,
    onChange: e => setValue(e.target.value)
  


// 表单组件
const  App = () => 

  const username = useInput('admin')

  const password = useInput('')

  const onSubmit = (e) => 
    //阻止默认事件发生
    e.preventDefault()
    // 获取表单值
    console.log(username.value, password.value);
  

  return (
    <form onSubmit=onSubmit >
      <input type="text" ...username />
      <input type="password" ...password />
      <button type="submit">提交</button>
    </form>
  );

export default App;

React Hooks中可以对性能进行优化的函数

useMemo()

具有缓存作用,有助于避免在每次渲染时都进行高开销的计算。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把创建函数和依赖项数组作为参数传入useMemo,当某个依赖改变时才会重新执行useMemo()函数。
如果没有提供依赖项数组,useMemo()每次渲染时都会重新执行useMemo()函数。

举例:
useMemo()监听count的值,当count的值改变时,newValue会更新。

import  useState, useMemo from 'react';

export default () => 
    const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useMemo(()=>
        console.log(`count 值为$count`)
        console.log(`num 值为 $num`)
        return count+num
    ,[count])
    return(
        <div>
            <h1>count</h1> 
            <button onClick=()=>setCount(count+1)>count + 1</button>
            <hr/>
            <h1>num</h1> 
            <button onClick=()=>setNum(num+1)>Num + 1</button>
            <hr/>
            <h2>newValue</h2>
        </div>
    )

点击5次num+1,num变为5,虽然newValue仍然为0,但是num=5已经被缓存了;点击count+1,他会计算count此时的值1与num缓存的值5的和,最终结果newValue为6。

结果如图:

useCallback()

useCallback 可以说是 useMemo 的语法糖,能用 useCallback 实现的,都可以使用 useMemo, 常用于react的性能优化

与useMemo()一样,依赖数组改变时才会重新执行useCallback()函数。
如果没有依赖数组,每次渲染都会重新执行useCallback()函数。

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

举例:

和上述useMemo()的效果一样,区别是useCallback()调用newValue时是:newValue()

import React, useState, useCallback from 'react';

function App()
  const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useCallback(()=>
        console.log(`count 值为$count`)
        console.log(`num 值为 $num`)
        return

React - 错误:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用

【中文标题】React - 错误:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用【英文标题】:React - Error: Invalid hook call. Hooks can only be called inside of the body of a function component 【发布时间】:2021-03-30 17:55:03 【问题描述】:

我想使用 "React Bootstrap Hamburger Menu" HamburgerMenu 从那里复制代码,我收到错误 errorScreen "错误:无效的钩子调用。钩子只能在函数组件的主体内部调用。这可能由于以下原因之一发生:

    您可能有不匹配的 React 版本和渲染器(例如 React DOM) 您可能违反了 Hooks 规则 您可能在同一个应用程序中拥有多个 React 副本 有关如何调试和修复此问题的提示,请参阅 https://reactjs.org/link/invalid-hook-call。 "这是我的代码

Mobile_Navbar.jsx

import React,  Component  from 'react';
import 
  MDBNavbar,
  MDBNavbarBrand,
  MDBNavbarNav,
  MDBNavItem,
  MDBNavLink,
  MDBNavbarToggler,
  MDBCollapse,
  MDBContainer
 from 'mdbreact';
import  BrowserRouter as Router  from 'react-router-dom';

class Test extends Component 
  state = 
    collapseID: ''
  ;

  toggleCollapse = collapseID => () => 
    this.setState(prevState => (
      collapseID: prevState.collapseID !== collapseID ? collapseID : ''
    ));
  ;

  render() 
    return (
      <Router>
        <MDBContainer>
          <MDBNavbar
            color='light-blue lighten-4'
            style= marginTop: '20px' 
            light
          >
            <MDBContainer>
              <MDBNavbarBrand>Navbar</MDBNavbarBrand>
              <MDBNavbarToggler
                onClick=this.toggleCollapse('navbarCollapse1')
              />
              <MDBCollapse
                id='navbarCollapse1'
                isOpen=this.state.collapseID
                navbar
              >
                <MDBNavbarNav left>
                  <MDBNavItem active>
                    <MDBNavLink to='#!'>Home</MDBNavLink>
                  </MDBNavItem>
                  <MDBNavItem>
                    <MDBNavLink to='#!'>Link</MDBNavLink>
                  </MDBNavItem>
                  <MDBNavItem>
                    <MDBNavLink to='#!'>Profile</MDBNavLink>
                  </MDBNavItem>
                </MDBNavbarNav>
              </MDBCollapse>
            </MDBContainer>
          </MDBNavbar>
        </MDBContainer>
      </Router>
    );
  


export default Test;

App.js

import React from 'react';
import './App.css';
import Test from './components/Mobile_Menu/Mobile_Navbar';

function App(props) 
    return (
        <div className="App">
            <Test />
        </div>
    );


export default App;

【问题讨论】:

我在您发布的代码中没有看到任何挂钩调用。尽管一个明显的问题是您使用的是类组件而不是函数组件。 有趣,我只是从网站上复制代码 可能是库本身的问题,或者传递依赖图中的版本不匹配。我们无法提供帮助。 【参考方案1】:

当我们使用 mdbootstrap 库 MDBNavLink 组件时,似乎出现了某种问题。不过,我并没有深入解释其中的原因。

我通过从 react-router-dom 导入 Link 组件并将其与 className='nav-link' 一起使用来解决了这个问题。

import  Link  from "react-router-dom";

//Snippet
// [....]

<MDBNavItem>
  <Link
    className='nav-link'
    exact
    to='/'
    onClick=closeCollapse('mainNavbarCollapse')
    >
    Home
  </Link>
</MDBNavItem>

// [...]

【讨论】:

以上是关于React Hooks(钩子函数)的主要内容,如果未能解决你的问题,请参考以下文章

React Hooks(钩子函数)

React - 错误:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用

没有什么是React Hooks(钩子函数)解决不了的

React Hooks Mobx:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用

React 17:错误:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用

为什么叫 React Hooks