草庐IT

React Hooks概述及常用的Hooks介绍

橘猫吃不胖~ 2023-04-08 原文

React Hooks概述及常用的Hooks介绍

1 为什么会有Hooks

React的组件创建方式,一种是类组件,一种是纯函数组件,并且React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类

函数组件和类组件的区别
(1)函数组件没有状态(state),类组件有
(2)函数组件没有生命周期,类组件有(挂载、更新、销毁)
(3)函数组件没有this,类组件有
(4)函数组件更适合做UI展示,类组件更适合做复杂的业务逻辑组件

这就注定,函数组件更适合做UI展示的功能,涉及到状态的管理与切换,我们不得不用类组件或者redux。但我们知道类组件的也是有缺点的,比如,遇到简单的页面,代码会显得很重,并且每创建一个类组件,都要去继承一个React实例;至于Redux,更不用多说,很久之前Redux的作者就说过,“能用React解决的问题就不用Redux”。

例如,使用类组件做一个简单的计数器,它的具体代码如下:

import React from "react";

class Count extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0 // 设置初始状态为0
        }
    }
    addCount = () => { // 让count加1
        let count = this.state.count;
        this.setState({
            count: count += 1
        })
    }
    render() {
        return (
            <div>
                <p>{this.state.count}</p>
                <button onClick={this.addCount}>1</button>
            </div>
        )
    }
}

可以看出来,上面的代码确实很重。为了解决这种类组件功能齐全却很重,纯函数很轻便却有一些重大限制,React团队设计了React Hooks。

React Hooks就是加强版的函数组件,我们可以完全不使用class,就能写出一个全功能的组件。

2 Hooks的含义

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

那么Hooks要怎么用呢?“你需要写什么功能,就用什么钩子”。对于常见的功能,React为我们提供了一些常用的钩子,如果有特殊需要,我们也可以写自己的钩子。下面是React Hooks为我们提供的常用的钩子:

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

不同的钩子为函数引入不同的外部功能,上面四种钩子都带有use前缀,React Hooks约定,钩子一律使用use前缀命名。所以,自己定义的钩子都要命名为useXXX

3 Hooks的用法

3.1 useState():状态钩子

纯函数组件没有状态,useState()用于设置和使用组件的状态属性。语法如下:

const [state, setState] = useState(initialValue);
// state:初始的状态属性,指向状态当前值,类似this.state
// setState:修改状态属性值的函数,用来更新状态,类似setState
// initialValue:状态的初始值,该值会赋给state

注意:setState的命名为:set+State(初始状态名),并且采用小驼峰命名法。例如[count, setCount][name, setName]

示例:使用Hooks重写计数器

const Count = () => {
    const [count, setCount] = useState(0); // 将0设置为count的初始值
    const addCount = () => {
        let newCount = count;
        setCount(newCount += 1);
    }
    return (
        <div>
            <p>{count}</p>
            <button onClick={addCount}>1</button>
        </div>
    )
}

用函数组件实现了一个功能完全一样的计数器,代码看起来更加的轻便简洁,没有了继承,没有了渲染逻辑,没有了生命周期等。这就是hooks存在的意义。

3.2 useEffect():副作用钩子

useEffect()是副作用的钩子,可以实现特定的功能,如异步请求。语法如下:

useEffect(() => {
    // 回调函数,其中是要进行的异步操作代码
}, [array])
// [array]:useEffect执行的依赖,当该数组的值发生改变时,回调函数中的代码就会被指向
// 如果[array]省略,则表示不依赖,在每次渲染时回调函数都会执行
// 如果[array]是空数组,即useEffect第二项为[],表示只执行一次

示例:通过useEffect()模拟异步加载数据(第二项省略)。

const AsyncPage = () => {
    // 首先设置loading状态为true
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        // 2秒后将loading状态设置为false
        setTimeout(() => {
            setLoading(false);
        }, 2000);
    })
    return (
        // 判断loading是否为true,是就显示loading,不是就显示异步请求完成
        loading ? <p>loading...</p> : <p>异步请求完成</p>
    )
}


示例:useEffect()依赖第二项数组变化

const AsyncPage = ({name}) => {
    const [loading, setLoading] = useState(true); // 设置loading状态为true
    const [person, setPerson] = useState({}); // 设置person状态为空对象
    
    useEffect(() => {
        // 首先设置loading为true,2秒后改为false,name改成传过来的参数
        setLoading(true);
        setTimeout(() => {
            setLoading(false);
            setPerson({name});
        }, 2000);
    }, [name]); // 表示当name修改才会执行回调函数
    return (
        <>
            {loading ? <p>Loading...</p> : <p>{person.name}</p>}
        </>
    )
}

const PersonPage = () => {
    // 设置初始state为空字符串
    const [state, setState] = useState("");
    const changeName = (name) => { // 修改name的函数
        setState(name);
    }
    return (
        <>
            {/*首先将state传给name*/}
            <AsyncPage name={state}/>
            <button onClick={() => { // 点击按钮后将张三传给name
                changeName("张三")
            }}>张三
            </button>
            <button onClick={() => {
                changeName("李四")
            }}>李四
            </button>
        </>
    )
}

useEffect和useLayoutEffect的区别

useEffect()useLayoutEffect()主要的区别是调用时机不同

useLayoutEffect()componentDidMount()componentDidUpate()一致,再react完成DOM更新后马上同步调用代码,它会阻塞页面的渲染,而useEffect()则会在页面渲染完后才会异步调用。

在实际使用中如果项避免页面抖动,可以把需要操作DOM的代码放在useLayoutEffect()中,在该函数中做DOM操作,这些DOM修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流重绘的代价。

3.3 useCallback():记忆函数

useCallback()为记忆函数,它可以防止因为组件重新渲染,导致方法被重新创建,起到缓存作用。语法如下:

useCallback(() => {
    // 回调函数,当array改变后,该函数才会重新声明
}, [array])
// 如果[array]为空数组,那么就是第一次创建后就被缓存,如果后期array改变了,拿到的还是老的array
// 如果不传入第二个参数,每次都会重新声明一次,拿到的就是最新的array

比如说下面一段代码中,我们可以看到有很多的函数,当我们在return中修改一个状态,就会导致整个页面重新渲染,那么这些函数(handleChange1handleChange2…)也会被重新创建,这样会造成性能的浪费,因此可以使用useCallback将这些函数缓存下来,这样下一次页面重新渲染的时候,某些函数就不会重新被创建了。

        const UseCallback = function () {
            const handleChange1 = () => {
                // 具体代码
            }
            const handleChange2 = () => {
                // 具体代码
            }
            const handleChange3 = () => {
                // 具体代码
            }
            const handleChange4 = () => {
                // 具体代码
            }
            return (
                <div>
                    {/*具体代码*/}
                </div>
            )
        }

使用useCallback()时,只需要将其写在整个函数外部即可,上面代码使用useCallback()后效果如下,每当依赖项改变时,该函数才会被重新创建,如果依赖项不变,则不会重新创建。

        const UseCallback = function () {
            const handleChange1 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange2 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange3 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            const handleChange4 = useCallback(
                () => {
                    // 具体代码
                }, [依赖项]
            )
            return (
                <div>
                    {/*具体代码*/}
                </div>
            )
        }

3.4 useMemo():记忆组件

useCallback()的功能可以由useMemo()所替代,useMemo()也可以返回一个记忆函数,语法如下:

useMemo(() => fn, [])
// useCallback(fn, []) = useMemo(() => fn, [])

useCallback()useMemo()的区别:

useCallback()不会执行第一个参数函数,而是将其返回,useMemo()会执行第一个函数并且将函数执行结果返回给你。useCallback()常用记忆时间按函数,生成记忆后的时间函数传递给子组件使用,useMemo()更适合经过函数计算得到一个确定的只,比如记忆组件。

3.5 useRef():保存引用值

useRef()等价于类组件中的React.createRef(),语法如下:

const loginRef = useRef();

使用useRef()创建了一个值后,就可以将其绑定到DOM节点上,给DOM节点增加一个ref属性,将loginRef传入,则可以通过其current属性获取到DOM节点的值,语法如下:

<input ref={loginRef}/>

除此之外,我们都知道useState()可以保存一个状态,那么另一个方法就是使用useRef(),为useRef()传入一个初始值,它可以帮助我们记住这个状态。

3.6 useContext():共享状态钩子

useContext()可以共享状态,作用是进行状态的分发(React16.x以后的版本支持),避免了使用Props进行数据的传递。语法如下:

// 第一步:创建全局的Context
const AppContext = React.createContext([初始化参数])

// 第二步:通过全局的Context进行状态值的共享
<AppContext.Provider value={{ 属性名:}}>
    <组件1 />
    <组件2 />
</AppContext>

示例:A组件和B组件共享一个状态

const Count = () => {
    const AppContext = React.createContext({});
    const A = () => {
        const {name} = useContext(AppContext);
        return (
            <div>
                我是A组件,我的名字是:{name}
            </div>
        )
    }
    const B = () => {
        const {name} = useContext(AppContext);
        return (
            <div>
                我是B组件,我的名字是:{name}
            </div>
        )
    }
    return (
        <AppContext.Provider value={{name: "橘猫吃不胖"}}>
            <A/>
            <B/>
        </AppContext.Provider>
    )
}

3.7 useReducer():Action钩子

在使用React的过程中,如遇到状态管理,一般会用到Redux,而React本身是不提供状态管理的。而useReducer()提供了状态管理。

useReducer()useState()的替代方案。首先,关于redux我们都知道,其原理是通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。而Reducer的形式是(state, action) => newstate,返回当前的 state 以及与其配套的 dispatch 方法。。Hooks的useReducer()是这样的:

const [state, dispatch] = useReducer(reducer, initialState)

它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数。

例如:使用useReducer()实现一个计数器

const Count = () => {
    const reducer = (state, action) => {
        if (action.type == "add") {
            return {
                ...state,
                count: state.count + 1
            }
        } else {
            return state
        }
    }
    const addCount = () => {
        dispatch({
            type: "add"
        })
    }
    const [state, dispatch] = useReducer(reducer, {count: 0});
    return (
        <>
            <p>{state.count}</p>
            <button onClick={addCount}>1</button>
        </>
    )
}

通过代码可以看到,使用useReducer()代替了Redux的功能,但useReducer无法提供中间件等功能,假如有这些需求,还是需要用到redux。

4 创建自己的Hooks

以上介绍了四种最常用的react提供给我们的默认React Hooks,有时候我们需要创建我们自己想要的Hooks,来满足更便捷的开发,就是根据业务场景对以上四种Hooks进行组装,从而得到满足自己需求的钩子。

用户自定义的Hooks:
A、命名的要求:用use开头,后跟名称(首字母大写)
B、作用:根据具体业务的需求,对Hooks中默认的钩子函数进行封装,使代码的结构更加清晰,便于使用和维护

示例:封装自己的Hooks

// 封装自己的Hooks
const usePerson = ({name}) => {
    const [loading, setLoading] = useState(true);
    const [person, setPerson] = useState({});
    useEffect(() => {
        setLoading(true);
        setTimeout(() => {
            setLoading(false)
            setPerson({name})
        }, 2000)
    }, [name]) //第二个参数为默认

    return [loading, person];
}

const AsyncPage = (name) => { // 只进行UI展示
    const [loading, person] = usePerson(name)
    return (
        <>
            {loading ? <p>Loading...</p> : <p>{person.name}</p>}
        </>
    )
}

const PersonPage = () => {
    const [userName, setUserName] = useState('')

    const changeName = (name) => {
        setUserName(name);
    }
    return (
        <>
            <AsyncPage name={userName}/>
            <br/>
            <button onClick={() => {
                changeName('张三')
            }}>张三
            </button>
            <br/>
            <button onClick={() => {
                changeName('李四')
            }}>李四
            </button>
        </>
    )
}

上面代码中,将之前的例子封装成了自己的Hooks,便于共享。其中,usePerson()为自定义Hooks,它接受一个字符串,返回一个数组,数组中包括两个数据的状态,之后在使用usePerson()时,会根据传入的参数不同而返回不同的状态,然后很简便的应用于我们的页面中。

有关React Hooks概述及常用的Hooks介绍的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  3. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  4. ruby-on-rails - 在 Rails 3 中进行身份验证最常用的方法是什么? - 2

    我需要在rail3中使用标准注册/登录/忘记密码功能进行身份验证。是否有大多数人为此使用的插件或其他东西? 最佳答案 我不确定最常用的方法是什么-但可以肯定的是,Plataformatec的“Devise”是一个非常流行的方法:http://github.com/plataformatec/devise我已经尝试了一些authgem,对我来说,它是最简单的设置和修改以满足我的需要。它内置了密码恢复、帐户确认(如果需要)和其他一些非常方便的功能。 关于ruby-on-rails-在Rail

  5. ruby-on-rails - 如何在 Rails 中添加禁用的提交按钮 - 2

    我在ruby​​表单中有一个提交按钮f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id"我想在不使用任何javascript的情况下通过ruby​​禁用此按钮 最佳答案 添加disabled:true选项。f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id",disabled:true 关于ruby-on-rails-如何在Rails中添加禁用的提交按钮,我们在St

  6. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

  7. 电脑怎么截图?进来看(8种常用截图方法) - 2

    电脑上可以截取图片吗?如果可以,该如何操作呢?相信很多小伙伴都只知道一两种截图的方式,知道的并不全面。其实,电脑上有多种方式截图的,而且非常方便。电脑怎么截图?今天我们就来教大家如何使用电脑截取图片的8种常用方式!操作环境:演示机型:Delloptiplex7050系统版本:Windows10方法一:系统自带截图具体操作:同时按下电脑的自带截图键【Windows+shift+S】,可以选择其中一种方式来截取图片:截屏有矩形截屏、任意形状截屏、窗口截屏和全屏截图。 方法二:QQ截图具体操作:在电脑登录QQ,然后同时按下【Ctrl+Alt+A】,可以任意截图你需要的界面,可以把截图的页面直接下载,

  8. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

  9. ruby - 环境 : ruby_executable_hooks: No such file or directory - 2

    尝试构建SASS时,我在SublimeText2上遇到以下错误。我从这里添加了SASS构建插件https://github.com/jaumefontal/SASS-Build-SublimeText2env:ruby​​_executable_hooks:没有那个文件或目录[在0.0秒内完成,退出代码为127]不太确定我应该在这里做什么来解决这个问题。谢谢!!反对票的数量告诉我需要添加更多信息。问题是我不太确定应该添加哪些信息。我的SASS.sublime-build文件位于/Library/ApplicationSupport/SublimeText2/Packages/SASSB

  10. Unity常用文件夹 - 2

    1.Scenes游戏场景文件夹用于放置unity的场景文件 2.Plugins插件文件夹用于放置unity的依赖文件,例如dll 3.Scripts脚本文件夹用于放置unity的c#脚本文件 4.Resources游戏资源文件夹用于放置unity的各种游戏资源,比如images,prefabs,同时只有放到Resources文件夹的游戏资源才能使用Resource.load(资源路径不加后缀)加载到游戏内存中进行使用 5.EditorUnity编辑器扩展脚本文件夹usingUnityEditor;这个名称空间就是Unity编辑器的名称空间这个名称空间提供了扩展Unity编辑器的各种类 【你所有

随机推荐