ReactHooks(二)

上期在这~

ReactHooks【二】

  • 一.useReducer
    • 1.1 useReducer 的语法格式
    • 1.2 定义组件的基础结构
    • 1.3 定义 useReducer 的基础结构
      • 1.3.1按需导入 useReducer 函数
      • 1.3.2定义初始数据
      • 1.3.3 定义 reducer 函数根据旧状态,进行一系列处理,最终返回新状态:
      • 1.3.4 在 Father 组件中,调用 useReducer(reducerFn, 初始状态) 函数,并得到 reducer 返回的状态
      • 1.3.5为 reducer 中的 initState 指定数据类型
    • 1.4 使用 initAction 处理初始数据
      • 1.4.1 定义名为 initAction 的处理函数
      • 1.4.2 在 Father 组件中,使用步骤1声明的 initAction 函数如下:
    • 1.5 在 Father 组件中点击按钮修改 name 的值
      • 1.5.1为了能够触发 reducer 函数的重新执行,我们需要在调用 useReducer() 后接收返回的 dispatch 函数
      • 1.5.2 接收并处理dispatch参数
    • 1.6 使用 Immer 编写更简洁的 reducer 更新逻辑
      • 1.6.1 安装Immer
      • 1.6.2 从 use-immer 中导入 useImmerReducer
      • 1.6.3 case代码块汇总不再需要return
  • 二.useContext
    • 2.1 语法格式(使用步骤)
    • 2.2 createContext 配合 useContext 使用
    • 2.3 以非侵入的方式使用 Context【封装Context】

一.useReducer

1.1 useReducer 的语法格式

useReducer 的基础语法如下:

const [state, dispatch] = useReducer(reducer, initState, initAction?)

其中:

  • reducer 是一个函数,类似于 (prevState, action) => newState。形参 prevState 表示旧状态,形参 action 表示本次的行为,返回值 newState 表示处理完毕后的新状态。
  • initState 表示初始状态,也就是默认值。
  • initAction 是进行状态初始化时候的处理函数,它是可选的,如果提供了 initAction 函数,则会把 initState 传递给 initAction 函数进行处理,initAction 的返回值会被当做初始状态。
  • 返回值 state 是状态值。dispatch 是更新 state 的方法,让他接收 action 作为参数,useReducer 只需要调用 dispatch(action) 方法传入的 action 即可更新 state。

1.2 定义组件的基础结构

定义名为 Father 的父组件如下:

import React from 'react'// 父组件
export const Father: React.FC = () => {return (<div><button>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}

定义名为 Son1 和 Son2 的两个子组件如下:

// 子组件1
const Son1: React.FC = () => {return <div className="son1"></div>
}// 子组件2
const Son2: React.FC = () => {return <div className="son2"></div>
}

在 index.css 中添加对应的样式:

.father {display: flex;justify-content: space-between;width: 100vw;
}.son1 {background-color: orange;min-height: 300px;flex: 1;padding: 10px;
}.son2 {background-color: lightblue;min-height: 300px;flex: 1;padding: 10px;
}

1.3 定义 useReducer 的基础结构

1.3.1按需导入 useReducer 函数

import React, { useReducer } from ‘react’

1.3.2定义初始数据

const defaultState = { name: ‘test’, age: 16 }

1.3.3 定义 reducer 函数根据旧状态,进行一系列处理,最终返回新状态:

const reducer = (prevState) => {console.log('触发了 reducer 函数')return prevState
}

1.3.4 在 Father 组件中,调用 useReducer(reducerFn, 初始状态) 函数,并得到 reducer 返回的状态

// 父组件
export const Father: React.FC = () => {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state] = useReducer(reducer, defaultState)console.log(state)return (<div><button>修改 name 的值</button><div className="father"><Son1 /><Son2 /></div></div>)
}

1.3.5为 reducer 中的 initState 指定数据类型

在 Father 组件中使用 state 时,就可以出现类型的智能提示啦

// 定义状态的数据类型
type UserType = typeof defaultStateconst defaultState = { name: 'test', age: 16 }// 给 initState 指定类型为 UserType
const reducer = (prevState: UserType) => {console.log('触发了 reducer 函数')return prevState
}

1.4 使用 initAction 处理初始数据

1.4.1 定义名为 initAction 的处理函数

如果初始数据中的 age 为小数、负数、或 0 时,对 age 进行非法值的处理:

//形参:初始状态
//返回值:处理好的初始状态
const initAction = (initState: UserType) => {// 把 return 的对象,作为 useReducer 的初始值return { ...initState, age: Math.round(Math.abs(initState.age)) || 18 }
}

1.4.2 在 Father 组件中,使用步骤1声明的 initAction 函数如下:

// 父组件
export const Father: React.FC = () => {// useReducer(fn, 初始数据, 对初始数据进行处理的fn)const [state] = useReducer(reducer, defaultState, initAction)// 省略其它代码...
}

可以在定义 defaultState 时,为 age 提供非法值,可以看到非法值在 initAction 中被处理掉了。

1.5 在 Father 组件中点击按钮修改 name 的值

注意禁止直接修改state数据源。

注意:这种用法是错误的,因为不能直接修改 state 的值
因为存储在 useReducer 中的数据都是“不可变”的!
要想修改 useReducer 中的数据,必须触发 reducer 函数的重新计算,
根据 reducer 形参中的旧状态对象(initState),经过一系列处理,返回一个“全新的”状态对象
state.name = ‘escook’

1.5.1为了能够触发 reducer 函数的重新执行,我们需要在调用 useReducer() 后接收返回的 dispatch 函数

// Father 父组件
const [state, dispatch] = useReducer(reducer, defaultState, initAction)
在 button 按钮的点击事件处理函数中,调用 dispatch() 函数,从而触发 reducer 函数的重新计算:// Father 父组件
const onChangeName = () => {dispatch()
}
点击 Father 组件中如下的 button 按钮:<button onClick={onChangeName}>修改 name 的值</button>
会触发 reducer 函数的重新执行,并打印 reducer 中的 console.log(),代码如下:const reducer = (prevState: UserType) => {console.log('触发了 reducer 函数')return prevState
}

1.5.2 接收并处理dispatch参数

// 1. 定义 action 的类型
type ActionType = { type: 'UPDATE_NAME'; payload: string }// 2. 为 action 指定类型为 ActionType
const reducer = (prevState: UserType, action: ActionType) => {console.log('触发了 reducer 函数', action)// 3. 删掉之前的代码,再重复编写这段逻辑的时候,会出现 TS 的类型提示,非常 Niceswitch (action.type) {case 'UPDATE_NAME':return { ...prevState, name: action.payload }default:return prevState}
}
//同时,在 Father 组件的 onChangeName 处理函数内,调用 dispatch() 时也有了类型提示:const onChangeName = () => {dispatch({ type: 'UPDATE_NAME', payload: '测试' })
}

注意:在今后的开发中,正确的顺序是先定义 ActionType 的类型,再修改 reducer 中的 switch…case… 逻辑,最后在组件中调用 dispatch() 函数哦!这样能够充分利用 TS 的类型提示。

1.6 使用 Immer 编写更简洁的 reducer 更新逻辑

1.6.1 安装Immer

npm install immer use-immer -S

1.6.2 从 use-immer 中导入 useImmerReducer

// 1. 导入 useImmerReducer
import { useImmerReducer } from 'use-immer'
// 父组件
export const Father: React.FC = () => {// 2. 把 useReducer() 的调用替换成 useImmerReducer()const [state, dispatch] = useImmerReducer(reducer, defaultState, initAction)
}

1.6.3 case代码块汇总不再需要return

Immer 内部会复制并返回新对象

const reducer = (prevState: UserType, action: ActionType) => {console.log('触发了 reducer 函数', action)switch (action.type) {case 'UPDATE_NAME':// return { ...prevState, name: action.payload }prevState.name = action.payloadbreakcase 'INCREMENT':// return { ...prevState, age: prevState.age + action.payload }prevState.age += action.payloadbreakdefault:return prevState}
} 

二.useContext

2.1 语法格式(使用步骤)

  • 全局创建 Context 对象
  • 父组件中使用 Context.Provider 提供数据
  • 子组件中使用 useContext 使用数据
import React, { useContext } from 'react'// 全局
const MyContext = React.createContext(初始数据)// 父组件
const Father = () => {return <MyContext.Provider value={{name: 'escook', age: 22}}><!-- 省略其它代码 --></MyContext.Provider>
}// 子组件
const Son = () => {const myCtx = useContext(MyContext)return <div><p>姓名:{myCtx.name}</p><p>年龄:{MyCtx.age}</p></div>
}

2.2 createContext 配合 useContext 使用

import React, { useState, useContext } from 'react'// 声明 TS 类型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }// 1. 创建 Context 对象
const AppContext = React.createContext<ContextType>({} as ContextType)export const LevelA: React.FC = () => {const [count, setCount] = useState(0)return (<div style={{ padding: 30, backgroundColor: 'lightblue', width: '50vw' }}><p>count值是:{count}</p><button onClick={() => setCount((prev) => prev + 1)}>+1</button>{/* 2. 使用 Context.Provider 向下传递数据 */}<AppContext.Provider value={{ count, setCount }}><LevelB /></AppContext.Provider></div>)
}export const LevelB: React.FC = () => {return (<div style={{ padding: 30, backgroundColor: 'lightgreen' }}><LevelC /></div>)
}export const LevelC: React.FC = () => {// 3. 使用 useContext 接收数据const ctx = useContext(AppContext)return (<div style={{ padding: 30, backgroundColor: 'lightsalmon' }}>{/* 4. 使用 ctx 中的数据和方法 */}<p>count值是:{ctx.count}</p><button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button><button onClick={() => ctx.setCount(0)}>重置</button></div>)
}

2.3 以非侵入的方式使用 Context【封装Context】

浸入的方式就是父组件中存在MyContext.Provider代码。

核心思路:每个 Context 都创建一个对应的 Wrapper 组件,在 Wrapper 组件中使用 Provider 向
children 注入数据。
注意:Wrapper组件的命名:是由Context的名称+Wrapper构成。

// 声明 TS 类型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }
// 创建 Context 对象
const AppContext = React.createContext<ContextType>({} as ContextType)// 定义独立的 Wrapper 组件,被 Wrapper 嵌套的子组件会被 Provider 注入数据
export const AppContextWrapper: React.FC<React.PropsWithChildren> = (props) => {// 1. 定义要共享的数据const [count, setCount] = useState(0)// 2. 使用 AppContext.Provider 向下共享数据return <AppContext.Provider value={{ count, setCount }}>{props.children}</AppContext.Provider>
}

App.tsx

import React from 'react'
import { AppContextWrapper, LevelA } from '@/components/use_context/01.base.tsx'const App: React.FC = () => {return (<AppContextWrapper><!-- AppContextWrapper 中嵌套使用了 LevelA 组件,形成了父子关系 --><!-- LevelA 组件会被当做 children 渲染到 Wrapper 预留的插槽中 --><LevelA /></AppContextWrapper>)
}export default App

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

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

相关文章

Go语言教程(一看就会)

全篇文章 7000 字左右&#xff0c; 建议阅读时长 1h 以上。 Go语言是一门开源的编程语言&#xff0c;目的在于降低构建简单、可靠、高效软件的门槛。Go平衡了底层系统语言的能力&#xff0c;以及在现代语言中所见到的高级特性。它是快速的、静态类型编译语言。 第一个GO程序…

嵌入式人工智能(32-基于树莓派4B的旋转编码器-EnCoder11)

1、旋转编码器 旋转编码器是一种输入设备&#xff0c;通常用于测量和控制旋转运动。它由一个旋转轴和一系列编码器组成。旋转编码器可以根据旋转轴的位置和方向来测量旋转角度&#xff0c;并将其转化为电子信号输出。 旋转编码器通常分为两种类型&#xff1a;绝对值编码器和增…

【ai】Easy-RAG 4: 修复依赖项:numpy numba omegaconf 等

numpy 2.0.1 这个版本太高了 chromadb 0.5.5 requires numpy<2.0.0,>=1.22.5,but you have numpy 2.0.1 which is incompatible.gradio 4.29.0 requires numpy~=1.0, but you have numpy 2.0.1 which is incompatible.langchain 0.2.6 requires numpy<2,>=1; pytho…

力扣面试题(一)

1、给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾。 char * mergeAlternately(char * word1, char * word2){int len1 strlen(word1);i…

嵌入式学习Day13---C语言提升

目录 一、二级指针 1.1.什么是二级指针 2.2.使用情况 2.3.二级指针与数组指针 二、指针函数 2.1.含义 2.2.格式 2.3.注意 2.4.练习 三、函数指针 3.1.含义 3.2.格式 3.3.存储 3.4.练习 ​编辑 四、void*指针 4.1.void缺省类型 4.2.void* 4.3.格式 4.4.注…

H3CNE(OSPF动态路由)

目录 7.1 静态路由的缺点与动态路由分类 7.1.1 静态路由的缺点 7.1.2 动态路由的分类 7.2 OSPF基础 7.2.1 OSPF的区域 ​编辑 7.2.2 Router-id 7.2.3 开销-Cost or Metric 7.2.4 路由转发 7.3 OSPF邻居表建立过程 7.3.1 五种包 7.3.2 建立邻居表的第一步 7.3.3 邻居建立…

模拟实现短信登录功能 (session 和 Redis 两种代码实例) 带前端演示

目录 整体流程 发送验证码 短信验证码登录、注册 校验登录状态 基于 session 实现登录 实现发送短信验证码功能 1. 前端发送请求 2. 后端处理请求 3. 演示 实现登录功能 1. 前端发送请求 2. 后端处理请求 校验登录状态 1. 登录拦截器 2. 注册拦截器 3. 登录完整…

RocketMQ事务消息机制原理

RocketMQ工作流程 在RocketMQ当中&#xff0c;当消息的生产者将消息生产完成之后&#xff0c;并不会直接将生产好的消息直接投递给消费者&#xff0c;而是先将消息投递个中间的服务&#xff0c;通过这个服务来协调RocketMQ中生产者与消费者之间的消费速度。 那么生产者是如何…

C++里memset的使用

在C中使用memset函数涉及几个关键点&#xff0c;‌包括函数的正确调用方式、‌参数的理解以及注意事项。‌memset函数是C和C语言标准库中的一个函数&#xff0c;‌用于将内存区域设置为特定的值。‌它的基本语法如下&#xff1a;‌ void *memset(void *s, int c, size_t n); …

集合论与存在性证明问题的分类

集合论是数学的一个重要分支&#xff0c;主要研究集合及其性质、关系以及操作等。关于集合论与存在性证明问题的分类&#xff0c;可以从多个角度进行阐述。 一、集合论的分类 基础集合论 研究集合的基本概念和性质&#xff0c;包括集合的定义、集合的元素关系、集合的操作、…

昇思25天学习打卡营第19天|DCGAN生成漫画头像

DCGAN生成漫画头像总结 实验概述 本实验旨在利用深度卷积生成对抗网络&#xff08;DCGAN&#xff09;生成动漫头像&#xff0c;通过设置网络、优化器以及损失函数&#xff0c;使用MindSpore进行实现。 实验目的 学习和掌握DCGAN的基本原理和应用。熟悉使用MindSpore进行图像…

网络协议一 : 搭建tomacat,intellij IDEA Ultimate 的下载,安装,配置,启动, 访问

需要搭建的环境 1.客户端--服务器开发环境 客户端&#xff1a;浏览器&#xff08;HTMLCSSJS&#xff09; 服务器&#xff1a;JAVA 1.安装JDK&#xff0c;配置JAVA_HOME 和 PATH 2.安装Tomcat 3.安装IDE--intellij IDEA Ultimate 是旗舰版的意思。 2.TOMCAT 的下载和解…

文件操作相关的精讲

目录&#xff1a; 思维导图 一. 文件定义 二. 文件的打开和关闭 三. 文件的顺序读写操作 四. 文件的随机读写操作 五. 文本文件和二进制文件 六. 文件读取结束的判断 七.文件缓冲区 思维导图&#xff1a; 一. 文件定义 1.文件定义 C语言中&#xff0c;文件是指一组相…

Flutter 生命周期介绍与使用

Flutter 生命周期简介与使用 Flutter 是一个由 Google 开发的开源 UI 软件开发工具包&#xff0c;用于跨平台应用程序的开发。了解 Flutter 的生命周期对于构建高效且响应式的应用程序至关重要。在这篇博客中&#xff0c;我们将探讨 Flutter 的生命周期管理&#xff0c;包括 S…

Java中的二叉搜索树(如果想知道Java中有关二叉搜索树的知识点,那么只看这一篇就足够了!)

前言&#xff1a;Java 提供了丰富的数据结构来处理和管理数据&#xff0c;其中 TreeSet 和 TreeMap 是基于红黑树实现的集合和映射接口。它们有序地存储数据&#xff0c;提供高效的搜索、插入和删除操作。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主…

PHP表单验证邮件和URL

在PHP中验证表单中的电子邮件地址和URL地址是确保用户输入数据正确性的重要步骤。下面是一个详细的教程&#xff0c;介绍如何使用PHP来验证电子邮件和URL地址。 一、验证电子邮件地址 电子邮件地址的验证通常涉及检查字符串是否符合电子邮件的标准格式。虽然完全通过正则表达…

web基础,http协议,apache概念及nginx

一、web相关概念 Web&#xff0c;全称World Wide Web&#xff0c;通常简称为WWW、Web或万维网&#xff0c;是一个基于超文本和HTTP&#xff08;超文本传输协议&#xff09;的、全球性的、动态交互的、跨平台的分布式图形信息系统。它起源于1989年&#xff0c;由英国科学家蒂姆…

Doris-接入能力

1. Doris数据入库功能特性2. Doris 数据写入流程图3. 常用组件写入功能特性对比 3.1. IDU实现方式对比3.2. 写入速度对比

文本编辑三剑客(grep)

目录 正则表达式 元字符 grep 案例 我在编写脚本的时候发现&#xff0c;三个文本编辑的命令&#xff08;grep、sed、awk&#xff0c;被称为文本编辑三剑客&#xff0c;我习惯叫它三巨头&#xff09;用的还挺多的&#xff0c;说实话我一开始学的时候也有些懵&#xff0c;主要…

云 IDE 你了解多少

IDE&#xff08;Intelligent Development Environment&#xff09; 对于软件开发者来说&#xff0c;是一个非常重要的工具。好用的 IDE 可以大幅提高开发效率&#xff0c;减少不必要的重复工作。 就目前而言&#xff0c;本地的 IDE 可能依然是主流的选择。但是&#xff0c;在本…