React Hook | 必 学 的 9 个 钩子

Posted 大新

tags:

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

React Hook 指南

什么是 Hook ?

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook 本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。

  • useState
  • useEffect
  • useContext
  • useReducer

Hook 出现解决了什么 ?

  • [ ] 组件之间状态复用, 例如:使用useContext 可以很好的解决状态复用问题,或者自定义 Hook 来定制符合自己业务场景遇到的状态管理。
  • [ ] 在函数组件中 生命周期的使用,更好的设计封装组件。在函数组件中是不能直接使用生命周期的,通过 Hook 很好的解决了此问题。
  • [ ] 函数组件与 class 组件的差异,还要区分两种组件的使用场景。 使用 Hook 完全不用去想这些,它可以使用更多 React 新特性。

什么时候使用 Hook ?

  1. 在函数组件顶层调用

  2. 在 函数中使用 / 自定义 Hook 中使用

React 内置的 Hook

    1. useState 状态管理
    1. useEffect 生命周期管理
    1. useContext 共享状态数据
    1. useMemo 缓存值
    1. useRef 获取Dom 操作
    1. useCallback 缓存函数
    1. useReducer redux 相似
    1. useImperativeHandle 子组件暴露值/方法
    1. useLayoutEffect 完成副作用操作,会阻塞浏览器绘制

useState 状态管理

class 组件中,我们获取 state 是 通过 this.state 来获取的。

而在 函数组件中, 是没有 this 的, 我们可以使用 Hook 提供的 useState 来管理和维护 state .

useState 定义 / 使用

const [state, setState] = useState(initialState)

  • setState 为更新 satate 方法
  • useState(initialState) initialState 为初始值

完整栗子

import {useState} from \'react\';

export default () => {
    const [data, setData] = useState(\'微信公众号: 前端自学社区\')
    return (
        <div>
            <h1>{data}</h1>
            {/* 更新 state */}
            <button onClick={()=>{setData(\'微信公众号: 前端自学社区  666\')}}></button>
        </div>
    )
}

useEffect 生命周期管理

定义

useEffect 可以看作是 函数式 组件 的 生命周期管理。

因为在 函数式组件中无法直接使用生命周期,就必须托管 Hook 来进行管理使用了。

useEffect 可以使用的 3 个生命周期函数:

  • componentDidmount
  • componentDidUpdate
  • componentWillUnmount

无需清除 Effect 使用

什么是无需清除 Effect 使用?

React 更新 DOM 之后运行一些额外的代码

那么它就是在生命周期的 compoentDidmount componentUpdate 中执行即可。

    useEffect(() => {
        //默认会执行  
        // 这块相当于 class 组件 生命周期的
        //compoentDidmount    compoentDidUpdate
    }, [])

清除Effect 使用

1. 什么是 清除Effect

当组件进行卸载时,需要执行某些事件处理时,就需要用到 class 组件生命周期的 componentUnmount .

useEffect 中很方便使用,在内部返回一个方法即可,在方法中写相应业务逻辑

2. 为什么 要在 Effect 中返回一个函数 ?

这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
    useEffect(()=>{
        return () => {
            console.log(\'组件卸载时执行\')
        }
    })

监听 state 变化

可以通过控制 监听 state 变化来实现相应的业务逻辑。
    useEffect(() => {
        // 监听num,count  状态变化
        // 不监听时为空 [] , 或者不写
    }, [num, count])

完整栗子

import { useState, useEffect } from \'react\';

export default () => {
    const [num, setNum] = useState(0)
    const [count, setCount] = useState(1)

    useEffect(() => {
        //默认会执行  
        // 这块相当于 class 组件 生命周期的 compoentDidmount compoentDidUpdate
        console.log(`num: ${num}`)
        console.log(`count: ${count}`)

        // 组件在卸载时,将会执行 return 中内容
        return () => {
            // 相当于 class 组件生命周期的 componentWillUnMount 
            console.log(\'测试\')
        }
    }, [num])

    return (
        <div>
            <h1>{num}</h1>
            <button onClick={() => { setNum(num + 1) }}> 更新Num</button>
            <hr />
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1) }}> 更新Count</button>
        </div>
    )
}

useRef

什么是 useRef ?

useRef 返回的是一个可变的ref对象,它的属性current被初始化为传入的参数(initialValue),返回的ref对象在组件的整个生命周期内保持不变。

作用:

  1. 获取Dom操作,例如 获取 input 焦点

  2. 获取子组件的实例(只有类组件可用)

  3. 在函数组件中的一个全局变量,不会因为重复 render 重复申明

栗子

import {useRef} from \'react\';


export default () => {
    const inputRef = useRef({value:0})
    return (
        <div>
            <h1>测试</h1>
            <input type="text" ref={inputRef} />
            <button onClick={()=>{console.log(inputRef.current.value)}}>获取input 值</button>
            <button onClick={()=>{inputRef.current.focus()}}>获取input 焦点</button>
        </div>
    )
}

useContext 状态数据共享

Context 解决了什么

在日常开发中,我们父子组件都是通过 props 来进行通信,如果遇到 跨级组件通信 那么我们就不好通过 props 来处理了。

这时候可以想想怎么可以把 组件 状态 共享出去使用?

  • Context
  • Redux
  • .....

本小节通过 Context 来 达到组件数据共享

什么是 Context

数据共享,任何组件都可访问Context 数据。

React 中,组件数据通过 prop 来达到 自上而下的传递数据,要想实现全局传递数据,那么可以使用 Context .

注意:

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

创建 Context

在使用 Context 前提,必须创建它,可以为它单独创建一个文件来管理 Context,
import React from \'react\';

export const MyContext = React.createContext();

使用 Context

在使用 Context 时,它通常用在顶级组件(父组件上),它包裹的内部组件都可以享受到 state 的使用和修改。

通过 Context.Provider 来进行包裹,值通过 value = {} 传递。

子组件如何使用 Context 传递过来的值 ?

  • 通过 useContext() Hook 可以很方便的拿到对应的值.
// Context.js
import React from \'react\';

export const MyContext = React.createContext();
import { useContext } from \'react\';
import {MyContext} from \'../Context/index\'

const result = {
    code:200,
    title:\'添加数据成功\'
}
const Son = () => {
    const res = useContext(MyContext)
    return (
        <>
            <div>
                <h1>{res.code}</h1>
                <hr/>
                <h2>{res.title}</h2>
            </div>
        </>
    )
}


export default  () => {
    return (
        <MyContext.Provider value={result}>
            <div>
                <h1>前端自学社区</h1>
                <Son/>
            </div>
        </MyContext.Provider>
    )
}

useMemo 提升性能优化

定义

useMemo用于性能优化,通过记忆值来避免在每个渲染上执⾏高开销的计算。

useMemo 参数:

  • useMemo 返回值是 memoized 值,具有缓存作用
  • array 控制 useMemo重新执⾏的数组, array 中 的 state 改变时才会 重新执行 useMemo

注意:

    1. 不传数组,每次更新都会重新计算
    1. 空数组,只会计算一次
    1. 依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个 useMemo 返回的值)

栗子

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 值,页面中 newValue 的值始终显示为 0,这是为什么呢?

因为我在 useMemo 监听记录的是 count 的值,当 count 值发生变化时,页面上的 newValue 在会重新计算,虽然你点击了 5 次 更新 num ,页面没有更新,但是已经缓存起来了,当点击 更新 count 时,它会 计算 count+1 的值 和 num 缓存的值 , 最终结果 为 5。

减少了计算消耗。

useCallback 提升性能优化

定义

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

useCallback 的参数:

  • callback是一个函数用于处理逻辑
  • array 控制useCallback重新执⾏的数组,array改变时才会重新执⾏useCallback

使用

它的使用和 useMemo 是一样的,只是 useCallback 返回的函数。
import { useState, useCallback} from \'react\';


export default () => {
    const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useCallback(()=>{
        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/>
            {/* 调用useCallback 返回的值 */}
            <h2>{newValue()}</h2>
        </div>
    )
}

小结

useMemo useCallback 功能类似,都是提升性能优化。

该采用哪种方式来最佳实践,还有待探索。

欢迎 读者 与 我交流。

<br/>

网上对 useMemo useCallback 的看法 ?

useCallback 如果在函数式组件中的话,确实应该当作最佳实践来用,但是使用它的目的除了要缓存依赖未改变的回调函数之外(与 useMemo 类似),还有一点是为了能够在依赖发生变更时,能够确保回调函数始终是最新的实例,从而不会引发一些意料之外的问题,我感觉后者才是使用 useCallback 的出发点,而非缓存。因为你想啊,即使不用 useCallback,假设这个回调函数也没有任何依赖状态,我直接把这个函数声明在组件外部不也可以吗?我直接使用 ref 不是更自由吗?<br/><br/>

useMemo 本身名字就是和缓存有关联的,本质上就为了解决一个事情,在 render 里面不要直接创建对象或者方法什么的,因为组件每渲染一次,就会创建一次(比如 style 或者一些常量状态),造成不必要的资源浪费。理想情况应当是,如果存在依赖,只在依赖变化时重新创建,不存在依赖,那就只创建一次。表面上看,如果所有状态都用 useMemo,肯定没什么问题,但你还需从缓存的代价上来分析这个问题,如果使用 useMemo 缓存一个状态的代价大于它带来的优势,那是不是反而适得其反了?

大家对 useMemo useCallback 有何看法,欢迎在下方评论或者加我讨论。

useImperativeHandle

定义

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。 useImperativeHandle 应当与 forwardRef 一起使用。

useImperativeHandle 作用 :

子组件可以暴露给父组件 实例使用

格式: useImperativeHandle(ref,()=>{},[])

  • 参数1: 子组件向父组件暴露的实例
  • 参数2: 函数,传递的父组件可操作的实例和方法
  • 参数3: 监听状态,更新状态


import {useState,useImperativeHandle, forwardRef,useRef} from \'react\';


const Son = forwardRef( (props,ref) => {
    const inputRef = useRef(0)
    const domRef = useRef()
    const [state, setState] = useState(\'等待\')
    useImperativeHandle(ref,()=>({
        focus:() => {inputRef.current.focus()},
        domRef
    }))
    return (
        <div>
            <h1>{state}</h1>
            <hr/>
            <input type="text" ref={inputRef}/>
            <h2  ref={domRef}>测试---------useImperativeHandle</h2>
        </div>
    )
})


export default () => {
    const refFather = useRef(0)
    return (
        <div>
            <h1>父组件</h1>
            <Son ref={refFather} />
            <button onClick={()=>{refFather.current.focus()}}>获取子组件实例------获取input焦点</button>
            <button onClick={()=>{console.log(refFather.current.domRef.current.innerhtml)}}>获取子组件实例------获取h2 Dom</button>
        </div>
    )
}

useReducer

定义

它是 useState 的替代方案。它接收一个形如 (state, action) => newStatereducer,并返回当前的 state 以及与其配套的 dispatch 方法。

如果熟悉 Redux 使用的话,用 useReducer 就是轻车熟路了,发车了。

使用Reducer实现一个加减器

import {useReducer} from \'react\';


export default () => {
    const [state, dispatch] = useReducer((state,action)=> {
        switch (action.type){
            case \'addNum\':
                return {
                    num:state.num+1
                }
            case \'subtractNum\':
                return {
                    num:state.num-1
                }
        }
            
    },{
        num:0
    })
    return (
        <div>
            <h2>{state.num}</h2>
            <button onClick={()=>{dispatch({type:\'addNum\'})}}> 增加num</button>
            <button onClick={()=>{dispatch({type:\'subtractNum\'})}}> 减num</button>
        </div>
    )
}

结语

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

以上是关于React Hook | 必 学 的 9 个 钩子的主要内容,如果未能解决你的问题,请参考以下文章

在Wordpress中列出钩住的函数

Android Hook技术

React入门必学基础知识点

关于React hook,我做了个违背祖训的决定

vue error in created hook怎么解决?

气流插件未正确拾取