setVisible(false)}
/>
```
# :star:useReducer
## 基础使用
作用: 让 React 管理多个**相对关联**的状态数据
```jsx
import { useReducer } from 'react'
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
{state}
>
)
}
export default App
```
## 更新流程

## 分派action传参
> 做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数
```jsx
// 定义reducer
import { useReducer } from 'react'
// 1. 根据不同的action返回不同的新状态
function reducer(state, action) {
console.log('reducer执行了')
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
case 'UPDATE':
return state + action.payload
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
{state}
>
)
}
export default App
```
## useReducer对比useState
useState
- **简单性**:`useState`非常直接,适用于简单的状态更新场景。它接受一个初始状态作为参数,并返回一个数组,其中第一个元素是当前状态,第二个元素是一个更新状态的函数。
- **易用性**:对于单一状态的更新,`useState`提供了直观的API,易于理解和维护。
- **局限性**:当状态逻辑变得复杂,特别是当状态更新依赖于前一个状态或需要执行复杂的操作时,`useState`可能无法很好地表达这种逻辑。
useReducer
- **复杂状态管理**:`useReducer`基于Redux的reducer模式,适合处理复杂的状态转换,尤其是当状态更新依赖于多个动作类型时。
- **可预测性**:通过定义一个reducer函数,你可以明确地控制状态如何根据不同的动作类型变化,这使得状态管理更加可预测和可测试。
- **复用性**:`useReducer`允许你将状态逻辑封装成可复用的组件,可以在多个地方重用同一个reducer,而不需要复制状态更新的逻辑。
- **性能优化**:在某些情况下,`useReducer`可以利用React的性能优化,如避免不必要的渲染,尤其是在状态更新较为复杂的情况下。
# useMemo(类比vue计算属性)
作用:它在每次重新渲染的时候能够**缓存计算的结果**
## 看个场景
下面我们的本来的用意是想**基于count的变化计算斐波那契数列之和**,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费
```jsx
// useMemo
// 作用:在组件渲染时缓存计算的结果
import { useState } from 'react'
function fib(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
const sum = fib(count)
const [num, setNum] = useState(0)
console.log('App组件渲染了')
return (
<>
{sum}
>
)
}
export default App
```
## useMemo缓存计算结果
> 思路: 只有count发生变化时才重新进行计算
```jsx
// useMemo
// 作用:在组件渲染时缓存计算的结果
import { useMemo, useState } from 'react'
function fib(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sum = fib(count)
// 通过useMemo缓存计算结果,只有count发生变化时才重新计算
const sum = useMemo(() => {
return fib(count)
}, [count])
const [num, setNum] = useState(0)
console.log('App组件渲染了')
return (
<>
{sum}
>
)
}
export default App
```
# React.memo
作用:允许组件在props没有改变的情况下跳过重新渲染

## 组件默认的渲染机制
> 默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染
```jsx
// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染
import { useState } from 'react'
function Son() {
console.log('子组件被重新渲染了')
return
this is son
}
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
return (
<>
{count}
>
)
}
export default App
```
## 使用React.memo优化
> 机制:只有props发生变化时才重新渲染
> 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
```jsx
/**
* memo
* 作用:允许组件在props没有改变的情况下跳过重新渲染
* 使用memo进行缓存,只有props改变时子组件才会重新渲染(未考虑context情况下)
*/
import { useState } from 'react'
import React from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is son
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
return (
<>
{/*
*/}
{count}
>
)
}
export default App
```
## props变化重新渲染
```jsx
import { useState } from 'react'
import React from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is son
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
return (
<>
{/*
*/}
{/* 将count传给子组件,引发子组件props变化,子组件重新渲染 */}
{count}
>
)
}
export default App
```
## props的比较机制
> 对于props的比较,进行的是‘浅比较’,底层使用 `Object.is` 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性

### 传递简单类型
```react
// 1.传递一个简单类型的prop prop变化时,组件重新渲染
// 2.传递一个引用类型的prop prop的引用变化时,组件才会重新渲染
import { useState } from 'react'
import React from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is son
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
return (
<>
{/* 传递简单类型 */}
{count}
>
)
}
export default App
```
### 传递引用类型
```react
// 2.传递一个引用类型的prop prop的引用变化时,组件才会重新渲染
import { useState } from 'react'
import React from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is son
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
// 注意!!!当父组件App重新渲染时,实际上形成的是一个新的数组引用,所有子组件都会重新渲染
const list = [1, 2, 3]
return (
<>
{/* 传递引用类型 */}
{count}
>
)
}
export default App
```
说明:
虽然`const list = [1, 2, 3]`,但是因为修改了count引发组件App重新渲染,生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染
### 传递引用类型(保证引用不变)
```jsx
// 3.传递一个引用类型(使用useMemo或useState确保引用不变)注意:如果使用setList,list引用会变化,所以需要使用useMemo缓存引用
import { useState, useMemo } from 'react'
import React from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is son
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件重新渲染了')
// 保证引用不变
// useState方式
// const [list, setList] = useState([1, 2, 3])
// useMemo方式
const list = useMemo(() => {
return [1, 2, 3]
}, [])
return (
<>
{/* 传递引用类型 */}
{count}
>
)
}
export default App
```
## 自定义比较函数
> 如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
```jsx
import React, { useState } from 'react'
// 自定义比较函数
function arePropsEqual(oldProps, newProps) {
console.log(oldProps, newProps)
return (
oldProps.list.length === newProps.list.length &&
oldProps.list.every((oldItem, index) => {
const newItem = newProps.list[index]
console.log(newItem, oldItem)
return oldItem === newItem
})
)
}
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return
this is span
}, arePropsEqual)
function App() {
console.log('父组件重新渲染了')
const [list, setList] = useState([1, 2, 3])
return (
<>
>
)
}
export default App
```
# useCallback(类似useMemo)
- useMemo:缓存值
- useCallback:缓存函数
## 看个场景
上一小节我们说到,当给子组件传递一个`引用类型`prop的时候,即使我们使用了`memo` 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 `useCallback`缓存回调函数
```jsx
// useCallBack
import { memo, useState } from 'react'
const MemoSon = memo(function Son() {
console.log('Son组件渲染了')
return
this is son
})
function App() {
const [, forceUpate] = useState()
console.log('父组件重新渲染了')
const onGetSonMessage = (message) => {
console.log(message)
}
return (
)
}
export default App
```
## useCallback缓存函数
> useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
```jsx
// useCallBack
import { memo, useCallback, useState } from 'react'
const MemoSon = memo(function Son() {
console.log('Son组件渲染了')
return
this is son
})
function App() {
const [, forceUpate] = useState()
console.log('父组件重新渲染了')
const onGetSonMessage = useCallback((message) => {
console.log(message)
}, [])
return (
)
}
export default App
```
# forwardRef(暴露子组件DOM节点)
forwardRef作用:允许组件使用ref将一个DOM节点暴露给父组件
useRef回顾:
- 使用useRef创建ref对象,并与JSX绑定
- 在DOM可用时,通过ref.current属性拿到DOM对象
```react
function App(){
// 1.创建ref对象
const inputRef = useRef(null)
const onChange = ()=>{
// 3.通过current属性拿到DOM对象
console.log(inputRef.current.value)
}
return (
)
}
```
forwardRef演示:
```jsx
/**
* forwardRef
* 作用:允许组件使用ref将一个DOM节点暴露给父组件
*/
import { forwardRef, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
// 暴露DOM给父组件
return
}, [])
function App() {
const ref = useRef(null)
const focusHandle = () => {
console.log(ref.current)
}
return (
)
}
export default App
```
# useImperativeHandle(暴露子组件内部方法)
useImperativeHandle作用:如果我们并不想暴露子组件中的DOM而是想**暴露子组件内部的方法**
```jsx
import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
// 实现内部的聚焦逻辑
const inputRef = useRef(null)
const focus = () => inputRef.current.focus()
// 暴露子组件内部的聚焦方法
useImperativeHandle(ref, () => {
return {
focus,
}
})
return
})
function App() {
const ref = useRef(null)
const focusHandle = () => ref.current.focus()
return (
)
}
export default App
```
# Class API编写React组件
顾名思义,Class API就是使用ES6支持的原生Class API来编写React组件
## 基础体验
通过一个简单的 Counter 自增组件看一下组件的基础编写结构
```jsx
// class API
import { Component } from 'react'
class Counter extends Component {
// 状态变量
state = {
count: 0,
}
// 事件回调
clickHandler = () => {
// 修改状态变量 触发UI组件渲染
this.setState({
count: this.state.count + 1,
})
}
// UI模版
render() {
return
}
}
function App() {
return (
)
}
export default App
```
## 类组件的组件生命周期
概念:组件从创建到销毁的**各个阶段自动执行的函数**就是生命周期函数

- `componentDidMount`:组件挂载完毕自动执行
- 适用于发送ajax请求获取数据(异步数据获取 )
- `componentWillUnmount`: 组件卸载时自动执行
- 适用于清理副作用、清理定时器、清除事件绑定等
## 类组件通信
概念:类组件和Hooks编写的组件在组件通信的思想上完全一致
- 父传子:通过prop绑定数据
- 子传父:通过prop绑定父组件中的函数,子组件调用
- 兄弟通信:状态提升,通过父组件做桥接
### 父传子
```jsx
// class API
import { Component } from 'react'
class Son extends Component {
render() {
const { count } = this.props
return
this is Son, {count}
}
}
class App extends Component {
// 状态变量
state = {
count: 0,
}
setCount = () => {
this.setState({
count: this.state.count + 1,
})
}
// UI模版
render() {
return (
<>
>
)
}
}
export default App
```
### 子传父
```jsx
// class API
import { Component } from 'react'
class Son extends Component {
render() {
const { msg, onGetSonMsg } = this.props
return (
<>
this is Son, {msg}
>
)
}
}
class App extends Component {
// 状态变量
state = {
msg: 'this is initail app msg',
}
onGetSonMsg = (msg) => {
this.setState({ msg })
}
// UI模版
render() {
return (
<>
>
)
}
}
export default App
```
更多阅读
[Component – React 中文文档](https://zh-hans.react.dev/reference/react/Component)
# Zustand(极简的状态管理工具)
## 快速上手
[Zustand Documentation](https://docs.pmnd.rs/zustand/getting-started/introduction)
```shell
npm install zustand
```
store/index.js - 创建store
```javascript
import { create } from 'zustand'
const useStore = create((set) => {
return {
count: 0,
inc: () => {
set(state => ({ count: state.count + 1 }))
}
}
})
export default useStore
```
app.js - 绑定组件
```jsx
import useStore from './store/useCounterStore.js'
function App() {
const { count, inc } = useStore()
return
}
export default App
```
## 异步支持
对于异步操作的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后把接口的数据放到set函数中返回即可
store/index.js - 创建store
```javascript
import { create } from 'zustand'
const URL = 'http://geek.itheima.net/v1_0/channels'
const useStore = create((set) => {
return {
count: 0,
ins: () => {
return set(state => ({ count: state.count + 1 }))
},
channelList: [],
fetchChannelList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({channelList: jsonData.data.channels})
}
}
})
export default useStore
```
app.js - 绑定组件
```jsx
import { useEffect } from 'react'
import useChannelStore from './store/channelStore'
function App() {
const { channelList, fetchChannelList } = useChannelStore()
useEffect(() => {
fetchChannelList()
}, [fetchChannelList])
return (
{channelList.map((item) => (
- {item.name}
))}
)
}
export default App
```
## 切片模式
场景:当我们**单个store比较大的时候**,可以采用一种`切片模式`进行模块拆分再组合(类似于模块化的手段)
### 拆分并组合切片
```javascript
import { create } from 'zustand'
// 创建counter相关切片
const createCounterStore = (set) => {
return {
count: 0,
setCount: () => {
set(state => ({ count: state.count + 1 }))
}
}
}
// 创建channel相关切片
const createChannelStore = (set) => {
return {
channelList: [],
fetchGetList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({ channelList: jsonData.data.channels })
}
}
}
// 组合切片
const useStore = create((...a) => ({
...createCounterStore(...a),
...createChannelStore(...a)
}))
```
### 组件使用
```jsx
function App() {
const {count, inc, channelList, fetchChannelList } = useStore()
useEffect(() => {
fetchChannelList()
}, [])
return (
<>
{channelList.map((item) => (
- {item.name}
))}
>
)
}
export default App
```
## 对接DevTools
> 简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具
### 安装调试包
```bash
npm i simple-zustand-devtools -D
```
### 配置调试工具
```javascript
import create from 'zustand'
// 导入核心方法
import { mountStoreDevtool } from 'simple-zustand-devtools'
// 省略部分代码...
// 开发环境开启调试
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('channelStore', useChannelStore)
}
export default useChannelStore
```
### 打开 React调试工具

# useState——自动推导
## 简单场景
> 简单场景下,可以使用TS的自动推断机制,不用特殊编写类型注解,运行良好
```typescript
const [val, toggle] = React.useState(false)
// `val` 会被自动推断为布尔类型
// `toggle` 方法调用时只能传入布尔类型
```
## 复杂场景(传入泛型参数)
> 复杂数据类型,useState支持通过`泛型参数`指定初始参数类型以及setter函数的入参类型
```typescript
type User = {
name: string
age: number
}
const [user, setUser] = React.useState
({
name: 'jack',
age: 18
})
// 执行setUser
setUser(newUser)
// 这里newUser对象只能是User类型
```
## 没有具体默认值(初始值为null)
> 实际开发时,有些时候useState的初始值可能为null或者undefined,按照泛型的写法是不能通过类型校验的,此时可以通过完整的类型联合null或者undefined类型即可
```typescript
type User = {
name: String
age: Number
}
const [user, setUser] = React.useState(null)
// 上面会类型错误,因为null并不能分配给User类型
const [user, setUser] = React.useState(null)
// 上面既可以在初始值设置为null,同时满足setter函数setUser的参数可以是具体的User类型
```
# useRef
> 在TypeScript的环境下,`useRef` 函数返回一个`只读` 或者 `可变` 的引用,只读的场景常见于获取真实dom,可变的场景,常见于缓存一些数据,不跟随组件渲染,下面分俩种情况说明
## 获取dom
> 获取DOM时,通过泛型参数指定具体的DOM元素类型即可
```tsx
function Foo() {
// 尽可能提供一个具体的dom type, 可以帮助我们在用dom属性时有更明确的提示
// divRef的类型为 RefObject
const inputRef = useRef(null)
useEffect(() => {
inputRef.current.focus()
})
return etc
}
```
如果你可以确保`divRef.current` 不是null,也可以在传入初始值的位置
```typescript
// 添加非null标记
const divRef = useRef(null!)
// 不再需要检查`divRef.current` 是否为null
doSomethingWith(divRef.current)
```
## 稳定引用存储器
> 当做为可变存储容器使用的时候,可以通过`泛型参数`指定容器存入的数据类型, 在还为存入实际内容时通常把null作为初始值,所以依旧可以通过联合类型做指定
```tsx
interface User {
age: number
}
function App(){
const timerRef = useRef(undefined)
const userRes = useRef (null)
useEffect(()=>{
timerRef.current = window.setInterval(()=>{
console.log('测试')
},1000)
return ()=>clearInterval(timerRef.current)
})
return this is app
}
```
# props配合TS的使用
## 为Props添加类型
> props作为React组件的参数入口,添加了类型之后可以限制参数输入以及在使用props有良好的类型提示
为组件prop添加类型,本质是给**函数的参数做类型注解**,可用使用**type对象类型或者interface接口**来做注解
### 使用interface接口
```tsx
interface Props {
className: string
}
export const Button = (props:Props)=>{
const { className } = props
return
}
```
### 使用自定义类型Type
```tsx
type Props = {
className: string
}
export const Button = (props:Props)=>{
const { className } = props
return
}
```
## 为Props的children属性添加类型
> children属性和props中其他的属性不同,它是React系统中内置的,其它属性我们可以自由控制其类型,children属性的类型最好由React内置的类型提供,兼容多种类型
补充说明:
- chilren属性(类似与vue的默认插槽)
- 当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接收该内容
```tsx
type Props = {
children: React.ReactNode
}
export const Button = (props: Props)=>{
const { children } = props
return
}
```
说明:React.ReactNode是一个React内置的联合类型,包括 `React.ReactElement` 、`string`、`number` `React.ReactFragment` 、`React.ReactPortal` 、`boolean`、 `null` 、`undefined`
## 为事件prop添加类型

```tsx
/**
* props配合TS - 为事件prop添加类型
*/
type Props = {
onGetMsg?: (msg: string) => void
}
function Son(props: Props) {
const { onGetMsg } = props
const clickHandler = () => {
onGetMsg?.('this is msg')
}
return
}
function App() {
const getMsgHandler = (msg: string) => {
console.log(msg)
}
return (
<>
{/* 内联绑定:能自动推导出类型 */}
console.log(msg)} />
{/* 单独绑定:需要自己声明类型 */}
>
)
}
export default App
```
## 为事件handle添加类型
> 为事件回调添加类型约束需要使用React内置的泛型函数来做,比如最常见的鼠标点击事件和表单输入事件:
```tsx
function App(){
const changeHandler: React.ChangeEventHandler = (e)=>{
console.log(e.target.value)
}
const clickHandler: React.MouseEventHandler = (e)=>{
console.log(e.target)
}
return (
<>
>
)
}
```