hooks常见的几种钩子函数

152 阅读5分钟

1、setstate Hook

import React from 'react'function Demo() {
    console.log('Demo');
    const [count,setCount] = React.useState(0);
    const [name,setName] = React.useState('Tom');
function add() {
    setCount(count+1)
}
function changeName() {
    setName('jack')
}
return(
    <div>
        <h2>{count}</h2>
        <button onClick = {add}>点我+1</button>
        <h2>{name}</h2>
        <button onClick = {changeName}>点我改名</button>
    </div>
)
}
export default Demo

2、Effect Hook

1)Effect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

2)React中副作用操作:

发ajax请求数据获取

设置订阅/启动定时器

3)语法和说明:

useEffect(() =>{
    //在此可以执行任何带副作用的操作
    return () =>{   //在组件卸载欠执行
        //再次做一些收尾工作,比如清除定时器/取消订阅等
    }
},[stateValue]) //如果指定的是[],回调函数只会在第一次render()后执行

4)可以把useEffect Hook看作如下三个函数的组合

componentDidMount()

componentDidUpdate()

componentWillUnmount()

import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'function Demo() {
    const [count,setCount] = useState(0)
    useEffect(() => {
        let timer = setInterval(() => {
            setCount(count => count+1)
        }, 1000)
        //这里返回的函数相当于componentWillUnmount钩子
        return ()=>{clearInterval(timer)}
    }, []) 
    //这里的[]为监视[]里的变量,如果里面传个变量[count],那么每当count的值发生改变时就会调用      useEffect()
    function unmount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }
return(
    <div>
        <h2>{count}</h2>
        <button onClick = {unmount}>点我卸载组件</button>
    </div>
)
}
export default Demo

总结:

1、当没有[]时,useEffect相当于componentDidmount和componentDidUpdate钩子的组合

当[]里的值为空时,相当于componentDidmount钩子

当[]里传值时,相当于componentDidmount钩子+[变量]的监视

2、componentWillUnmount相当于useEffect的返回函数:return ()=>{clearInterval(timer)}

3、ref Hook

Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据

function Demo() {
    //定义一个ref
    const myRef = React.useRef()
    function show(){
        //对被ref传值的dom节点进行操作
        alert(myRef.current.value)
    }
return(
    <div>
    //将ref值传给这个dom节点
        <input type="text" ref = {myRef}/>
        <br />
        <button onClick = {show}>点我获取数据</button>
    </div>
)
}

4、Fragment

因为每个子组件的dom节点外面都套了一层div,所以为了避免每个子组件在父组件进行展示时,有多个div,可以使用Fragment

return(
    <Fragment>
        <input type="text" ref = {myRef}/>
        <br />
        <button onClick = {show}>点我获取数据</button>
    </Fragment>
)

还可以直接写一个空标签

return(
    <>
        <input type="text" ref = {myRef}/>
        <br />
        <button onClick = {show}>点我获取数据</button>
    </>
)

唯一不同的是Fragment可以加key值,空标签加不了

5、Context

import React from 'react'
//先定义
const MyContext = React.createContext()
function A() {
   const person = {name:'zyf',age:18}
    return(
        <>
        //通过这个标签进行包裹,声明把value的值传给<B/>及其后代组件,传多个值可以传一个对象,          如<MyContext.Provider value = {{name,age}}>
        <MyContext.Provider value = {person}>
            <B/>
        </MyContext.Provider> 
        </>
    )
}
function B() {
     return(
         <>
             <C></C>
         </>
     )
 }
 function C() {
    const person = {name:'zyf',age:'male'}
     return(
         <>
             <h2>
         //后代要读取值的话需要用MyContext里的Consumer方法
                 <MyContext.Consumer>
                 {
                 value =>{
                     return`名字是:${person.name},年龄是:${person.age}`
                 }
                 }
                 </MyContext.Consumer>
            </h2>
         </>
     )
 }
export default A

类式组件也可以用上面的方法进行传值,另外类式组件比函数式组件多一种写法:

import React, { Component } from 'react'
const MyContext = React.createContext()
export default class A extends Component {
    state = {name:'zyf',age:10}
   render(){
    return(
        <>
        <MyContext.Provider value = {this.state}>
            <B/>
        </MyContext.Provider> 
        </>
    )
}
}
class B extends Component {
    render(){
     return(
         <>
             <C></C>
         </>
     )
 }
}
 class C extends Component {
     //调用值时需要先声明接收
     static contextType = MyContext
     render(){
     return(
         <>   //接收完毕后用context关键字来进行接收值
            <h3>年龄是{this.context.age}</h3>
         </>
     )
 }
}

6、useReducer

import { useReducer } from 'react';

export default function HomePage() {
  // 根据action的属性值判断来用那个方法
  const [count,setDispatch] = useReducer((state,action)=>{
    switch (action.type){
      case 'Add':
        return state +1;
      case 'Sub':
        return state-1
    }
 // state的默认值为1
  },1)
  const addHandler = ()=>{
    // 调用一次就使得state值加1
    setDispatch({type:'Add'})
  }
  const subHandler = ()=>{
    // 调用一次就使得state值减1
    setDispatch({type:'Sub'})
  }
  return (
    <div>
      <button onClick={addHandler}>添加</button>
      <span>{count}</span>
      <button onClick={subHandler}>减少</button>
    </div>
  );
}

7、React.memo

父组件index.tsx

import { useState } from 'react';
import DocsPage from './docs';

export default function HomePage() {
  const [state1,setState1] = useState(true)
  const [state2,setState2] = useState(true)
  const change1 = ()=>{
    setState1(!state1)
  }
  const change2 = ()=>{
    setState2(!state2)
  }
  console.log("父组件发生渲染");
  
  return (
    <div>
      <button onClick={change1}>按钮1</button>
      <button onClick={change2}>按钮2</button>
      <DocsPage value = {state1}/>
    </div>
  );
}

子组件docs.tsx

import React from "react";
const DocsPage = (props) => {
  console.log('子组件发生渲染');
  return (
    <div>
      {props.value}
    </div>
  );
};

export default DocsPage ;

此时无论点击按钮1还是按钮2子组件和父组件都会发生渲染

截屏2023-03-29 23.00.02.png

自组件用React.memo后

docs.tsx

import React from "react";
const DocsPage = (props) => {
  console.log('子组件发生渲染');
  return (
    <div>
      {props.value}
    </div>
  );
};

export default React.memo(DocsPage) ;

点击按钮1时,父组件和子组件都发生渲染,点击按钮2时只有父组件发生渲染,所以可以得出结论为只有当props发生改变时子组件才会发生渲染

截屏2023-03-29 23.07.08.png

截屏2023-03-29 23.07.08.png

8、useCallback

index.tsx

import { useState } from 'react';
import DocsPage from './docs';

export default function HomePage() {
  const [state,setState] = useState(true)
  const [num,setNum] = useState(0)
  const test = ()=>{
    setState(state)
  }
  const onAdd = ()=>{
    setNum(num+1)
  }
  console.log("父组件发生渲染");
  
  return (
    <div>
      <button onClick={onAdd}>父组件onAdd</button>
      <button onClick={test}>test</button>
      <DocsPage value = {onAdd}/>
    </div>
  );
}

docs.tsx

import React from "react";
const DocsPage = (props) => {
  console.log('子组件发生渲染');
  return (
    <div>
      <button onClick={props.onAdd}>子组件onAdd</button>
    </div>
  );
};

export default React.memo(DocsPage) ;

此时会发现点击test按钮,子组件也会发生渲染,React.memo失效了,这是因为,当点击test按钮时,onAdd按钮也被重新赋值,且为引用值,因引用地址发生了变化,所以子组件也被渲染了

截屏2023-03-29 23.42.22.png

解决方法

index.tsx

import { useCallback, useState } from 'react';
import DocsPage from './docs';

export default function HomePage() {
  const [state,setState] = useState(true)
  const [num,setNum] = useState(0)
  const test = ()=>{
    setState(!state)
  }
  const onAdd = useCallback(()=>{
    setNum(num+1)
  },[])
  console.log("父组件发生渲染");
  
  return (
    <div>
      <button onClick={onAdd}>父组件onAdd</button>
      <button onClick={test}>test</button>
      {num}
      <DocsPage value = {onAdd}/>
    </div>
  );
}

此时点击test按钮就会发现子组件不会随着发生渲染了,因为,useCallback和useEffect一样,当第二个参数为空数组时,只有在初次渲染时才会被重新赋值,但同时引来新的一个问题,就是点击onAdd按钮时,值只会更新一次,这是因为,它的作用域没有发生变化,在它的作用域内,num就一直是为1,所以点击时就只有第一次有效

截屏2023-03-29 23.57.39.png

此时需要在第二个参数里添加依赖变量num,当num值发生改变时,onAdd函数被重新赋值,作用域也发生改变,num值也发生了变化

截屏2023-03-30 00.01.52.png

9、useMemo

useMemo的用法与useCallbackl类似,唯一的区别就是useMemo接受的是值,useCallBack返回的是函数

import { useMemo, useState } from 'react';

const func = (param:number)=>{
  console.log('hello');
  return param
}
export default function HomePage() {
  const [state,setState] = useState(1)
  const count = useMemo(()=>{
    return func(1)+state
  },[state])
  const onAdd = ()=>{
    setState(state+1)
  }
  
  return (
    <div>
      <span>{count}</span>
      <button onClick={onAdd}>父组件onAdd</button>
    </div>
  );
}

如若不用useMemo包裹起来,每次点击test按钮,都会执行func里的函数,用了useMemo如若不加监听,state值就会一直为初始值1,和之前说过的useCallback一样