React进阶之路(三)-- Hooks

文章目录

  • Hooks概念理解
    • 什么是Hooks
    • Hooks解决了什么问题
  • useState
    • 基础使用
    • 状态的读取和修改
    • 组件的更新过程
    • 使用规则
    • 回调函数作为参数
  • useEffect
    • 什么是函数副作用
    • 基础使用
    • 依赖项控制执行时机
    • 清理副作用
    • 发送网络请求
  • useRef
  • UseContext

Hooks概念理解

在这里插入图片描述

什么是Hooks

Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”

在这里插入图片描述
注意点:

  1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
  2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
  3. hooks只能在函数组件中使用

Hooks解决了什么问题

Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题

  1. 组件的逻辑复用:在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
  2. class组件自身的问题:class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的’快艇’

useState

在这里插入图片描述

基础使用

作用

useState为函数组件提供状态(state)

使用步骤:

  1. 导入 useState 函数
  2. 调用 useState 函数,并传入状态的初始值
  3. 从useState函数的返回值中,拿到状态和修改状态的方法
  4. 在JSX中展示状态
  5. 调用修改状态的方法更新状态

代码实现:

import { useState } from 'react'function App() {// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)const [count, setCount] = useState(0)return (<button onClick={() => { setCount(count + 1) }}>{count}</button>)
}
export default App

状态的读取和修改

读取状态

该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用

修改状态

  1. setCount是一个函数,参数表示最新的状态值
  2. 调用该函数后,将使用新值替换旧值
  3. 修改状态后,由于状态发生变化,会引起视图变化

注意事项

  1. 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型

在这里插入图片描述

组件的更新过程

函数组件使用 useState hook 后的执行过程,以及状态值的变化

  • 组件第一次渲染
    • 从头开始执行该组件中的代码逻辑
    • 调用 useState(0) 将传入的参数作为状态初始值,即:0
    • 渲染组件,此时,获取到的状态 count 值为: 0
  • 组件第二次渲染
    • 点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
    • 组件重新渲染时,会再次执行该组件中的代码逻辑
    • 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
    • 再次渲染组件,此时,获取到的状态 count 值为:1

重点一句话:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值

import { useState } from 'react'function App() {const [count, setCount] = useState(0)// 在这里可以进行打印测试console.log(count)return (<button onClick={() => { setCount(count + 1) }}>{count}</button>)
}
export default App

使用规则

  • useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态 (也就是每个状态互不影响)

    function List(){// 以字符串为初始值const [name, setName] = useState('cp')// 以数组为初始值const [list,setList] = useState([])
    }
    
  • useState 注意事项

    • 只能出现在函数组件或者其他hook函数中
    • 不能嵌套在if/for/其它函数中,只能在函数组件的最外层去调用(react按照hooks的调用顺序识别每一个hook)
      let num = 1
      function List(){num++if(num / 2 === 0){const [name, setName] = useState('cp') }const [list,setList] = useState([])
      }
      // 俩个hook的顺序不是固定的,这是不可以的!!!
      

回调函数作为参数

使用场景

如果初始 state 需要通过计算才能获得,则可以传入一个函数。参数(也就是回调函数)只会在组件的初始渲染中起作用,后续渲染时会被忽略。

语法

const [name, setName] = useState(()=>{   // 编写计算逻辑    return '计算之后的初始值'
})

语法规则

  1. 回调函数return出去的值将作为 name 的初始值
  2. 回调函数中的逻辑只会在组件初始化的时候执行一次

语法选择
3. 如果就是初始化一个普通的数据 直接使用 useState(普通数据) 即可
4. 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{})

例如我们来一个需求:
在这里插入图片描述

import { useState } from 'react'function Counter(props) {const [count, setCount] = useState(() => {return props.count})return (<div><button onClick={() => setCount(count + 1)}>{count}</button></div>)
}function App() {return (<><Counter count={10} /><Counter count={20} /></>)
}export default App

useEffect

在这里插入图片描述

什么是函数副作用

什么是副作用?

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)

常见的副作用

  1. 数据请求 ajax发送
  2. 手动修改dom
  3. localstorage操作

useEffect函数的作用就是为react函数组件提供副作用处理的!

你可以把useEffect看作是React生命周期方法的一种替代,useEffect可以模拟componentDidMount,componentDidUpdate,和componentWillUnmount的行为。你可以在一个组件中使用多个useEffect,以便将不同的副作用分开处理。useEffect是一个Hook,所以你只能在组件的顶层或者你自定义的Hook中调用它,不能在循环或条件语句中调用它。如果你不需要与外部系统同步,你可能不需要使用useEffect。

基础使用

作用

为react函数组件提供副作用处理

使用步骤

  1. 导入 useEffect 函数
  2. 调用 useEffect 函数,并传入回调函数
  3. 在回调函数中编写副作用处理(例如:dom操作)
  4. 修改数据状态
  5. 检测副作用是否生效
import { useEffect, useState } from 'react'function App() {const [count, setCount] = useState(0)useEffect(()=>{// dom操作document.title = `当前已点击了${count}`})return (<button onClick={() => { setCount(count + 1) }}>{count}</button>)
}export default App

依赖项控制执行时机

1.不添加依赖项

组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行

  1. 组件初始渲染
  2. 组件更新 (不管是哪个状态引起的更新)
useEffect(()=>{console.log('副作用执行了')
})

2.添加空数组

组件只在首次渲染时执行一次

useEffect(()=>{console.log('副作用执行了')
},[])

3.添加特定依赖项

副作用函数在首次渲染时执行,在依赖项发生变化时重新执行

function App() {  const [count, setCount] = useState(0)  const [name, setName] = useState('zs') useEffect(() => {    console.log('副作用执行了')  }, [count])  return (    <>      <button onClick={() => { setCount(count + 1) }}>{count}</button>      <button onClick={() => { setName('cp') }}>{name}</button>    </>  )
}

注意事项:

  • useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
  • useEffect回调是在dom渲染之后执行
  • useEffect和vue里的watch有点像,但是执行时机是不同的

清理副作用

如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑。

注意执行时机为:

  1. 组件卸载时自动执行
  2. 组件更新时,下一个useEffect副作用函数执行之前自动执行
import { useEffect, useState } from "react"const App = () => {const [count, setCount] = useState(0)useEffect(() => {const timerId = setInterval(() => {setCount(count + 1)}, 1000)return () => {// 用来清理副作用的事情clearInterval(timerId)}}, [count])return (<div>{count}</div>)
}export default App

发送网络请求

使用场景

如何在useEffect中发送网络请求,并且封装同步 async await操作

语法要求

useEffect中不能是一个异步函数的原因是,useEffect的第一个参数应该是一个返回undefined或者一个清理函数的函数,而不是一个返回Promise的函数。异步函数会返回一个Promise,这个Promise不能被当作一个清理函数来调用。这样做会导致useEffect的行为不符合预期,可能会出现内存泄漏或者数据不一致的问题。

在这里插入图片描述

正确写法

如果你想在useEffect中使用异步函数,你有两种方法:

  • 在useEffect内部定义一个异步函数,并立即调用它。
  • 在useEffect外部定义一个异步函数,并在useEffect内部调用它。但是这样做的话,你需要把这个异步函数放到useEffect的依赖数组中,并且用useCallback来包裹它,以防止不必要的调用。
useEffect(() => {// 定义一个异步函数const fetchData = async () => {// 你可以在这里使用awaitconst response = await MyAPI.getData(someId);// ...}// 立即调用这个异步函数fetchData();
}, [someId]); // 或者[]如果副作用不需要props或state
// 在useEffect外部定义一个异步函数,并用useCallback包裹它
const fetchData = useCallback(async () => {// 你可以在这里使用awaitconst response = await MyAPI.getData(someId);// ...
}, [someId]); // 这里的依赖项要和useEffect的一致useEffect(() => {// 在useEffect内部调用这个异步函数fetchData();
}, [fetchData]); // 这里的依赖项要包含这个异步函数

useRef

在这里插入图片描述

使用场景

在函数组件中获取真实的dom元素对象或者是组件对象

使用步骤

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

代码实现:

获取dom

import { useEffect, useRef } from 'react'
function App() {  const h1Ref = useRef(null)  useEffect(() => {    console.log(h1Ref)  },[])  return (    <div>      <h1 ref={ h1Ref }>this is h1</h1>    </div>  )
}
export default App

获取组件实例

函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件

class Foo extends React.Component {  sayHi = () => {    console.log('say hi')  }  render(){    return <div>Foo</div>  }
}export default Foo
import { useEffect, useRef } from 'react'
import Foo from './Foo'
function App() {  const h1Foo = useRef(null)  useEffect(() => {    console.log(h1Foo)  }, [])  return (    <div> <Foo ref={ h1Foo } /></div>  )
}
export default App

UseContext

在这里插入图片描述

在这里插入图片描述
UseContext的作用是让你在组件中使用Context,Context是一种在组件树中传递数据的方式,它可以让你在不使用props的情况下,让深层嵌套的组件访问一些全局的状态。使用UseContext的步骤如下:

  • 首先,你需要使用createContext创建一个Context对象,并给它一个默认值。
  • 然后,你需要使用Context.Provider组件包裹你的组件树,并给它一个value属性,这个value就是你要传递的数据。
  • 最后,你可以在任何子组件中使用useContext(Context)来获取这个value,这样你就可以在组件中使用这个数据了。

使用UseContext的好处是,你不需要通过props来一层一层地传递数据,这样可以避免一些不必要的渲染和复杂的逻辑。你也可以在Context中存储一些函数,以便在组件中调用它们。使用UseContext的注意事项是,你需要保证你使用的Context对象是同一个,否则你可能会得到undefined的值。你也需要注意useContext的依赖关系,如果你的Context的value发生变化,那么所有使用了这个Context的组件都会重新渲染,除非你使用了memo或者useMemo来优化性能。

代码实现:

import { createContext, useContext } from 'react'
// 1.创建Context对象
const Context = createContext()function Foo() {  return <div>Foo <Bar/></div>
}function Bar() {  // 3.底层组件通过useContext函数获取数据  const name = useContext(Context)  return <div>Bar {name}</div>
}function App() {  return (    // 2.顶层组件通过Provider 提供数据    <Context.Provider value={'this is name'}>     <div><Foo/></div>    </Context.Provider>  )
}export default App

同时我们们还可以使用Context机制完成依赖注入

Context是React提供的一种在组件树间传递数据的方法,它可以让我们避免通过中间元素传递props,从而简化组件的结构和逻辑。Context的设计目的是为了共享那些对于一个组件树而言是“全局”的数据,比如主题、语言、用户信息等。

依赖注入是一种编程理念,它可以让我们将依赖的对象或者服务从外部注入到组件中,而不是让组件自己创建或者寻找。依赖注入可以提高代码的可测试性、可重用性和可维护性,同时也降低了组件之间的耦合度。

在React中,我们可以使用Context机制来实现依赖注入的效果,即将需要共享的数据或者逻辑封装在一个Context对象中,然后在组件树的上层使用Provider组件来提供这个Context对象,再在下层的组件中使用Consumer组件或者useContext Hook来消费这个Context对象。这样,我们就可以在不同的组件中访问和操作同一个Context对象,而不需要通过props来传递。

一个简单的例子是,我们可以创建一个ThemeContext对象,用来存储主题相关的数据,比如颜色、字体等。然后在根组件中使用ThemeContext.Provider来提供这个对象,并在子组件中使用ThemeContext.Consumer或者useContext(ThemeContext)来获取这个对象。这样,我们就可以在子组件中根据主题的数据来渲染不同的样式,而不需要从根组件一层一层地传递主题的数据。代码如下:

// 创建一个ThemeContext对象
const ThemeContext = React.createContext({color: 'black',font: 'Arial'
});// 根组件
function App() {// 使用ThemeContext.Provider来提供ThemeContext对象return (<ThemeContext.Provider value={{color: 'blue', font: 'Helvetica'}}><div className="App"><Header /><Content /></div></ThemeContext.Provider>);
}// 子组件
function Header() {// 使用useContext(ThemeContext)来获取ThemeContext对象const theme = useContext(ThemeContext);// 根据ThemeContext对象来渲染样式return (<div className="Header" style={{color: theme.color, font: theme.font}}><h1>React Context Example</h1></div>);
}// 子组件
function Content() {// 使用ThemeContext.Consumer来获取ThemeContext对象return (<div className="Content"><ThemeContext.Consumer>{theme => (// 根据ThemeContext对象来渲染样式<p style={{color: theme.color, font: theme.font}}>This is some content with theme.</p>)}</ThemeContext.Consumer></div>);
}

注意:

  • 在上面的代码中我们可以看到React.createContext中指定了一个对象。这是为了给这个Context对象设置一个默认值。默认值是当组件树中没有匹配的Provider时,组件读取到的Context的值。默认值可以是任何类型,不一定是对象,但是对象可以包含多个属性,方便我们在Context中存储和传递更多的数据。默认值只是作为一个“最后的备选方案”,它是静态的,不会随着时间而改变。如果我们想要动态地改变Context的值,我们需要在组件树的上层使用Provider组件来提供Context的值。也就是说useContext会有优先从Provider的value中找,如果找不到就会从createContext方法传递过来的默认参数中找。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣每日一道系列 --- LeetCode 88. 合并两个有序数组

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构探索 ✅LeetCode每日一道 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 思路1&#xff1a;暴力求解思路2&#xff1a;原地合并 LeetCode 88. 合并两个有序数组…

修改Android Studio默认的gradle目录

今天看了一下&#xff0c;gradle在C盘占用了40多G。我C盘是做GHOST的&#xff0c;放在这里不方便。所以就要修改。 新建目录名&#xff08;似乎无必要&#xff09; ANDROID_SDK_HOMEG:\SOFTWARES\android-sdk GRADLE_USER_HOMEG:\SOFTWARES\.gradle 修改目录 File->Setti…

C# 异步日志记录类,方便下次使用,不用重复造轮子

先定义接口类&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 异常 {internal interface ILog{Task WriteErrorLog(string message);Task WriteInfoLog(string message);Task W…

Git 安全警告修复手册:解决 `fatal: detected dubious ownership in repository at ` 问题 ️

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

2.HTML中常用浏览器

2.常用浏览器 2.1 什么是浏览器 浏览器是网页显示&#xff0c;运行的平台。常用的浏览器有IE&#xff0c;火狐&#xff0c;谷歌&#xff0c;Safari和Opera等 平时成为五大浏览器 2.2 浏览器内核 浏览器内核&#xff08;渲染引擎&#xff09;&#xff1a;负责读取网页内容&…

Java算法(五):手写数组逆置API方法,实现数组逆置。 while实现 for循环实现

Java算法&#xff08;五&#xff09; while 循环实现 需求&#xff1a; 已知一个数组&#xff0c;arr {11, 22, 33, 44, 55};使用程序实现把数组中的元素交换位置。 交换后的数组为 arr {55, 44, 33, 22, 11}; 并在控制台输出交换后的数组元素。 代码示例 package com.…

【Proteus仿真】【Arduino单片机】数码管显示

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用TM1637、共阳数码管等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示数字、字符。 二、软件设计 /* 作者&#xff1a;嗨小易&am…

Spring Cloud Config、Apollo、Nacos和Archaius对比

一、适应场景 Spring Cloud Config、Apollo、Nacos、Archaius这四个配置中心在功能和使用场景上有所差异。 1.Spring Cloud Config Spring Cloud Config是Spring Cloud官方提供的分布式系统的外部配置中心。它提供了服务器和客户端支持&#xff0c;可以集中管理不同环境、不同集…

AI时代如何提升自己晋升力

要在AI时代提升职场晋升力&#xff0c;采取以下详细策略&#xff1a; 终身学习的实践&#xff1a; 专业课程&#xff1a; 定期参加在线课程或研讨会&#xff0c;如Coursera、edX等&#xff0c;学习最新的AI技术和行业动态。行业资讯&#xff1a; 订阅相关的行业杂志、博客&…

基于SSM的学院就业信息网设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

奇异矩阵、非奇异矩阵

对于一个方阵A&#xff1a; 如果A的行列式等于0&#xff0c;称矩阵A为奇异矩阵如果A的行列式不等于0&#xff0c;称A 非奇异矩阵 也就是说&#xff0c;对于方阵A&#xff0c;如果它是满秩的&#xff0c;即它的秩等于矩阵的阶数&#xff0c;就是非奇异矩阵&#xff1b;如果秩小…

​怎么测试websocket接口

在部分业务中&#xff0c;我们需要使用长连接&#xff0c;我们可以使用http长连接或者websocket&#xff0c;开发结束后难免会遇到测试问题&#xff0c;这里推荐2个&#xff0c;一个是postman&#xff0c;一个是网站 postman 测试网站 测这边推荐测试网站&#xff0c;支持ws/w…

【123. 买卖股票的最佳时机 III】

目录 一、题目描述二、算法原理三、代码实现 一、题目描述 二、算法原理 三、代码实现 class Solution { public:const int Init-0x3f3f3f3f;int maxProfit(vector<int>& prices) {int nprices.size();vector<vector<int>> f(n,vector<int>(3,Ini…

送水服务预约小程序内容该如何做

无论小区还是办公楼等场景&#xff0c;送水服务往往有较高需求&#xff0c;同时该服务属于长期稳定性的&#xff0c;因此对品牌来说&#xff0c;如何打造品牌获取更多用户及转化非常重要&#xff0c;然而在实际订水过程中&#xff0c;又会面临着一些难题&#xff1a; 1、品牌传…

机器视觉opencv答题卡识别系统 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 答题卡识别系统 - opencv python 图像识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分…

基于ssm的校园办公室报修管理系统

基于ssm的校园办公室报修管理系统 摘要 基于SSM的校园办公室报修管理系统是一个现代化的、高效的报修平台&#xff0c;它能够帮助校园内的教职工和学生更方便、更快捷地提交和处理报修请求。该系统基于Spring、SpringMVC和MyBatis&#xff08;简称SSM&#xff09;开发&#xff…

竞赛 身份证识别系统 - 图像识别 深度学习

文章目录 0 前言1 实现方法1.1 原理1.1.1 字符定位1.1.2 字符识别1.1.3 深度学习算法介绍1.1.4 模型选择 2 算法流程3 部分关键代码 4 效果展示5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计 图像识别 深度学习 身份证识别…

视频剪辑高手的秘诀:如何从视频中提取封面,提高视频点击率

在视频分享平台上&#xff0c;一个吸引人的封面往往能吸引更多的观众点击。一个好的封面可以传达视频的主题&#xff0c;吸引人们的兴趣&#xff0c;提高视频的点击率。那么&#xff0c;如何从视频中提取封面呢&#xff1f;下面&#xff0c;让我们一起来看看云炫AI智剪如何操作…

使用 Redis 实现生成分布式全局唯一ID(使用SpringBoot环境实现)

目录 一、前言二、如何通过Redis设计一个分布式全局唯一ID生成工具2.1、使用 Redis 计数器实现2.2、使用 Redis Hash结构实现 三、通过代码实现分布式全局唯一ID工具3.1、编写获取工具3.2、测试获取工具 四、总结 一、前言 在很多项目中生成类似订单编号、用户编号等有唯一性数…

打印流详解

概述 作用&#xff1a;打印流可以实现方便、高效的打印数据到文件中去。 高效体现在用到了缓冲流&#xff1a; public PrintStream(OutputStream out, boolean autoFlush, Charset charset) {super(out);this.autoFlush autoFlush;this.charOut new OutputStreamWriter(thi…