首页>>前端>>JavaScript->React Hooks通关指南,不再做菜鸟

React Hooks通关指南,不再做菜鸟

时间:2023-12-01 本站 点击:0

前言

目前团队中使用的都是ReactHooks写法,对于从来没接触过ReactHooks实战项目的小凌来说的确是不小的挑战。为了让更多新手能够快速入门ReactHooks,特此来写一篇ReactHooks的学习心路。

为什么要学ReactHooks?

Hooks的出现起初是为了让函数式组件(Function)也能支持状态的管理,但在学习过程中我认识到了很多Hooks的优点。

Function和Class的纠结: 函数式组件(Function)更高效。类组件(Class)有状态管理,方便后期拓展。学了Hooks之后我们不必在纠结。

繁琐的生命周期: 在刚学React的时候我们总需要清楚的记住一个组件的生命周期和执行时机。在学了Hooks之后我们可以完全抛开生命周期了。

this指向问题: React的this指向问题总是令人头疼,网上虽然了解决方案,但是额外的代码总让人觉得麻烦。在学了Hooks之后,我们再也不用考虑这个问题了。

更简洁的代码: 相比传统方法而言,实现一个功能Hooks方法所用的代码量将更少。

与传统实现对比(Example)

原始方法实现一个计数器

import React, { Component } from 'react';class Example extends Component {constructor(props) {super(props);this.state = {count:0}}render() {    return (        <div>            <div>点击我{this.state.count} 了次</div>            <button onClick={()=>{this.addCount() }} >点击我</button>            {/* <button onClick={this.addCount.bind(this)} >点击我</button> */}        </div>    );}// 计数器    addCount(){      this.setState({count: this.state.count + 1 })    }}export default Example;

使用Hooks实现一个计数器

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;

相比于传统方法Hooks的实现直接减少了三分之一的代码量。在代码上更简洁,也更容易维护。

接下来我们来介绍一下ReactHooks的主要模块以及用法。

useState(状态管理)

useState使普通函数组件也有了状态管理的能力。其实我们在上方的代码里已经使用了useState,在这一模块里将详细介绍它的用法。我们从声明一个state开始:

左侧数组部分为ES6的解构赋值,在后续的讲解中我们将大量用到此类语法。

数组第1个参数为状态值(用于控制页面的展示和逻辑处理),第2个参数是更改值的方法。useState方法需要一个参数,这个参数就是状态值的默认值。

注意点

不要在条件语句中声明hooks

✖错误案例

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');

控制台报错

Hooks渲染是从上到下依次执行,在if语句内使用的话,由于第一次未执行到useState,后面render时却又突然检测到了,就会导致控制台报错(可以理解成渲染时突然发现一个未知的useState,老版本不会报错,但是新版本将这个问题修复了所以控制台会报错)。

为什么我更改了对象state视图却没刷新?

当我们设置状态为数组或对象且只想改变其中一项属性时。

✖错误案例

const [ info, setInfo ] = useState({name:'小凌', age:26});const changeAge = () => {    let setInfo = info;    setInfo.age = 18    setInfo(setInfo)}return (    <div>        <div>姓名:{info.name} 年龄:{info.age}</div>        <button onClick={changeAge} >设置年龄</button>    </div>)

发现视图并没有刷新,因为info的指针指向未变。

正确方法是使用解构赋值或是深浅拷贝的形式。

let setInfo = info;setInfo.age = 18setInfo({...setInfo})

useEffect(副作用)

useEffect主要用于监听状态值变化、在构建组件时进行监听、在销毁组件时进行监听。像极了类组件的

componentDidUpdatecomponentDidMountcomponentWillUnmount生命周期函数钩子。

之前我也会用类组件的生命周期来类比Hooks的执行时机。后来发现这并不是一个很好的方法。首先这个方法根本没有办法准确类比,其次React18中新增了API,OffScreen可以对Hooks的执行时机造成影响。

Function Component 仅描述 UI 状态,React 会将其同步到 DOM,仅此而已。

关于参数

useEffect方法有两个参数,第1个参数是要执行的函数,第2个参数是一个依赖项数组(根据具体需要监听什么状态值来决定数组内要填写什么)。

参数详解

1、不传递

useEffect不传递第二个参数会导致每次渲染都会运行useEffect。

useEffect(() => {  console.log('使用useEffect')  // 所有更新都执行})

2、传递空数组

仅在挂载和卸载的时候执行,如果是多个就是其中某个更改时调用

useEffect(()=>{    console.log('使用useEffect')},[]) // 仅在挂载和卸载的时候执行

3、传递非空数组

在监听值更新时才会触发

useEffect(()=>{    console.log(count)},[count]) // count更新时执行

3、返回函数

在组件销毁时调用函数

const time = null;useEffect(() => {    const time = setInterval(() => {        setCount(count + 1);    }, 1000);    return () => clearInterval(time); // 这个方法在组件销毁的时候会被调用}, []);

useContext(跨组件通信)

useContext主要用于父子组件之间状态的跨级传递,实现了状态的共享(类似于Vue的Vuex)。

在认识useContext之前,与孙组件的状态传递是通过props。

我们可以看到,当层级一多参数传递就变得复杂。使用useContext后,父组件产生的state将直接被孙组件消费。

具体实现

父组件

export const CountContext = createContext()function Example(){    const [count,setCount] = useState(0)    return (        <div>            <div>useContext点击我{count} 了次</div>            <button onClick={()=>{setCount( count + 1 )}} >点击我</button>            <CountContext.Provider value={count}>                <Counter />            </CountContext.Provider>        </div>    )}

子孙组件

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;0

注意点

新创建的DOM节点将在Provider之外。

当我们通过document.createElement创建Dom并将我们的内容挂载上去时,该节点将不会享有上下文的状态管理(也就是该节点在Provider之外)。

useMemo(状态缓存)

useMemo 是以缓存状态的形式来对渲染上进行性能优化的手段。

我们都知道只要状态更改了,那么组件视图就会从新渲染。

查看以下案例:

父组件代码

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;1

子组件代码

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;2

我们期望只有当name发生改变时候,才触发子组件的changeName方法。但当我们改变content时,也触发了changeName。因为父组件的重新渲染也重新渲染了子组件。

使用useMemo对状态进行缓存,只有改变的时候才触发相应方法。具体代码如下

优化之后的子组件

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;3

只在 React 函数中调用 Hook

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;4

当我们文件中有两个组件都需要一个context时,简化代码的思想促使我们感觉需要将它提取出来。

发现了如上报错。这是万万不可取的。在官网中我们也可以找到相应的提示。必须在React的函数组件中使用Hooks。

useCallback(方法缓存)

和之前学的useMemo一样,useCallback也是用来进行性能优化的,不同的是useCallback缓存的是方法。在小凌刚学Hooks的时候就写过几次死循环的代码。

大家先看如下代码:

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;5

我们使用setTimeout来模拟对后端进行请求。

在这种场景下,没有useCallback什么事,组件本身是高内聚的。如果涉及到组件通讯,情况就不一样了:

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;6

App渲染Child,将valgetData传进去

Child使用useEffect获取数据。因为对getData有依赖,于是将其加入依赖列表

getData执行时,调用setVal,导致App重新渲染

App重新渲染时生成新的getData方法,传给Child

Child发现getData的引用变了,又会执行getData

3 -> 5 是一个死循环

在我们明确异步方法只执行一次的情况下,我们可以使用useCallback对其进行固定。

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;7

数组内的参数和之前学的useeffect一样,是它的依赖项目。

useReducer

useReducer可以说是管理useState的集合。利用了Redux的理念,将多个state合并为了一个。

案列

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;8

实现Redux

结合我们之前所学的useContext们边可以实现一个简单的Redux。

案列如下

当我们点击AddColor组件时,为文字添加颜色。当我们点击AddBack组件时,为文字添加背景色。

核心Provider

import React, { useState } from 'react'function Example(){// 声明一个值 [ 数值, 设置函数 ] 0为初始值 (声明不能存在于条件判断中)const [ count, setCount ] = useState(0);    return (    <div>        <div>useState点击我{count} 了次</div>        <button onClick={()=>{setCount( count + 1 )}} >点击我</button>    </div>    )}export default Example;9

useRef

useRef主要用于建立中间值或是用来调用子组件方法。

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

比如我们设定一个每次按一次+1的count属性,当count为奇数时隐藏。这个显示和隐藏不能单独作为一个state,这时候我们就可以用useRef去控制。

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');0

父组件调用子组件方法

父组件

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');1

子组件

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');2

如此我们就可以调用子组件方法了。

自定义Hooks

自定义Hooks在实现上也非常简单。你需要建立一个useXXX的函数。这个函数在形式上和普通函数没有区别,你

可以传递任意参数给这个Hooks。与普通函数的区别在于内部有没有实现其他Hooks。若内部没有实现其他Hooks,这个函数就不是自定义Hooks。

如下例子,我们声明一个组件。

给调用方开启组件方法、关闭组件方法、组件内容的参数。

其中,开启关闭方法都是设置组件自己的state。

声明自定义Hooks

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');3

使用自定义Hooks

const  [ name, setName ] = useState('小凌');const isShowAge = false;if(isShowAge){  const [ age, setAge ] = useState(26);}const  [ hoppy, setHobby ] = useState('做菜');4

至此,所有Hooks都过完啦。官网还提供了其他的Hooks。

参考文章与项目地址

《useCallback》

本文章代码地址:rh-project

原文:https://juejin.cn/post/7099045192962932750


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/6432.html