ReactHooks(二)

上期在这~

一.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 defaultState

const 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 的类型提示,非常 Nice
  switch (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.payload
      break
    case 'INCREMENT':
      // return { ...prevState, age: prevState.age + action.payload }
      prevState.age += action.payload
      break
    default:
      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
  • 27
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值