玩转React: Hooks 高级用法与最佳实践

React: Hooks 高级用法与最佳实践

概念深度

为什么要引入React Hook?

其实跟Composition API一样都是为了解决传统版本的遗留问题,这里React Hook 也是为了解决传统 React 类组件的问题:

  1. 逻辑服用困难

    在传统的类组件当中,复用逻辑通常需要使用高阶组件(HOC)。比如说,有一个计算器的功能需要多个组件都去使用,如果使用类组件,可能需要创建一个高阶组件来处理计算器的逻辑。会导致组件之间的嵌套,代码复杂。

    举例:

    在传统的类组件中

    // 高阶组件
    const withCounter = (WrappedComponent) => {
      return class extends React.Component {
        constructor(props) {
          super(props);
          this.state = { count: 0 };
        }
    
        increment = () => {
          this.setState({ count: this.state.count + 1 });
        };
    
        render() {
          return <WrappedComponent count={this.state.count} increment={this.increment} {...this.props} />;
        }
      };
    };
    
    // 使用高阶组件的组件
    class MyComponent extends React.Component {
      render() {
        return (
          <div>
            <p>Count: {this.props.count}</p>
            <button onClick={this.props.increment}>Increment</button>
          </div>
        );
      }
    }
    
    const MyComponentWithCounter = withCounter(MyComponent);
    
  2. 代码冗余:在传统的类组件当中需要定义许多生命周期函数处理各种逻辑,这可能导致代码分散在不同的方法中,使得代码的可读性和可维护性降低。在React Hook当中我们

正是由于使用传统的类组件会出现上面的问题,所以React 又推出了React Hook它有什么优势呢?

  1. 性能优化:开发者可以使用React Hook比如useMemouseCallbackHook 可以更精细地控制组件的重新渲染,提高应用的性能。

  2. 增强逻辑复用性:上面的例子当中,使用React Hook 来实现计数器功能,代码更加简洁和直观。

    使用自定义React Hook实现

    import { useState } from'react';
    
    const useCounter = () => {
      const [count, setCount] = useState(0);
    
      const increment = () => {
        setCount(count + 1);
      };
    
      return { count, increment };
    };
    
    function MyComponent() {
      const { count, increment } = useCounter();
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={increment}>Increment</button>
        </div>
      );
    }
    
  3. 简洁的代码结构:使用 Hook 可以将相关的逻辑集中在一个函数组件中,避免了类组件中复杂的生命周期方法,使代码更加简洁、易读和可维护。

React Hook 的关键模块有什么?

React Hook 包括以下几个关键的模块:

状态管理 Hooks:

React中我们使用“状态”来描述对应的创建的变量。

  1. useState:
    • 对于这个hookReact中我们的描述是用于添加组件状态。
    • 它接受一个初始值作为参数,并返回一个数组,其中第一个元素是当前状态值,第二个元素是用于更新状态的函数。
  2. useReducer:
    • 用于管理复杂的状态逻辑。
    • 它接受一个 reducer 函数和一个初始状态作为参数,并返回当前的状态以及一个 dispatch函数,用于触发状态的更新。
  3. useContext:
    • 用于在组件树中共享数据。
    • 它接受一个 Context 对象作为参数,并返回该 Context 对象的值。

举例

  1. 使用 useState

    import { Button } from 'antd'
    import { useState } from 'react'
    
    // 使用 useState创建一个基本的计数器组件
    const About = () => {
      const [count, setCount] = useState(0)
    
      return (
        <div>
          <p>{count}</p>
          <Button
            type="primary"
            onClick={() => {
              setCount(count + 1)
            }}
          >
            +
          </Button>
          <Button
            onClick={() => {
              setCount(count - 1)
            }}
          >
            -
          </Button>
        </div>
      )
    }
    
    export default About
    
  2. 使用 useReducer

    import { Button } from 'antd'
    import { useReducer } from 'react'
    
    // 定义reducer函数
    const todoReducer = (state: any, action: any) => {
      switch (action.type) {
        case 'ADD_TODO':
          return [...state, { id: action.id, text: action.text, completed: false }]
        case 'TOGGLE_TODO':
          return state.map((todo: any) =>
            todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
          )
        case 'DELETE_TODO':
          return state.filter((todo: any) => todo.id !== action.id)
        default:
          return state
      }
    }
    
    const UseReducerDemo = () => {
      // 使用 useReducer 管理状态
      const [todos, dispatch] = useReducer(todoReducer, [])
    
      const addTodo = (text: any) => {
        const newTodo = { id: Date.now(), text, completed: false }
        dispatch({ type: 'ADD_TODO', id: newTodo.id, text })
      }
    
      const toggleTodo = (id: any) => {
        dispatch({ type: 'TOGGLE_TODO', id })
      }
    
      const deleteTodo = (id: any) => {
        dispatch({ type: 'DELETE_TODO', id })
      }
    
      return (
        <div>
          <Button onClick={() => addTodo('New Task')}>Add Task</Button>
          {todos.map((todo: any) => (
            <div key={todo.id}>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              <span>{todo.text}</span>
              <button onClick={() => deleteTodo(todo.id)}>Delete</button>
            </div>
          ))}
        </div>
      )
    }
    
    export default UseReducerDemo
    
  3. 使用 useContext

    import React, { createContext, useContext, useState } from 'react'
    
    // 定义上下文的类型
    interface ThemeContextType {
      theme: string
      setTheme: React.Dispatch<React.SetStateAction<string>>
    }
    
    // 创建主题上下文
    const ThemeContext = createContext<ThemeContextType>({
      theme: 'light',
      setTheme: () => {},
    })
    
    const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({
      children,
    }) => {
      const [theme, setTheme] = useState('light')
    
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          {children}
        </ThemeContext.Provider>
      )
    }
    
    const Button = () => {
      const { theme, setTheme } = useContext(ThemeContext)
    
      const toggleTheme = () => {
        setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'))
      }
    
      return (
        <button
          style={{ backgroundColor: theme === 'light' ? 'white' : 'black' }}
          onClick={toggleTheme}
        >
          Toggle Theme
        </button>
      )
    }
    
    function App() {
      return (
        <ThemeProvider>
          <Button />
        </ThemeProvider>
      )
    }
    
    export default App
    

问题:

  1. 关于useState我在这里就不详细介绍了,就是当我们要使用的时候就去直接就调用对应的状态即可,当我们要去修改的时候我们就直接调用setXXX()去修改状态即可。

  2. 对于这个useReducer的使用的流程是不是感觉有点看懂了又好像有点陌生呢?

    • 首先在使用useReducer的时候它会要求你去定义一个 reducer 函数,这个函数会要求你传入两个参数一个是维护的状态,另外一个参数是对应的动作。

    • 在用户触发对应的操作之后通过dispatch()函数去触发reducer函数的触发,比如说 dispatch({ type: 'ADD_TODO', id: newTodo.id, text }),它会去reducer函数寻找对应的type,一般在reducer函数中实现的switch,根据传入的不同类型,返回对应不同的状态。

  3. 可以使用useState不使用useReducer吗?

    • 其实在上面那个例子中你是可以通过useState来管理状态的,但是你会发现你用useState来管理状态的话,你在对应不同的动作action的时候需要去做对应的变量修改,这样会显得非常的冗余,且代码不好维护。

    • 当你使用useReducer时,你会发现这种模式使得状态的改变非常清晰和可预测,因为每一次状态的更新都是由一个特定的动作触发的。而且也便于你扩展对应的功能,只需要在reducer函数添加对应的动作即可。

  4. useContext的使用流程:

    • 首先我们需要确定useContext是做什么用的?它能实现什么效果。

      它能够在组件中共享数据。

    • 要使用useContext的话,我们必须要先创建一个上下文,接着我们通过上下文.Provider标签配合 value属性值即共享的值。作为父组件包裹子组件,这样就实现了组件创建的上下文的值的共享。

  5. useReduceruseContext使用有什么好处呢?使用的场景是什么呢?

    useReducer根据上面的例子我们会发现当状态更新逻辑较为复杂,涉及多个子操作或依赖多个条件时, useReducer 可以是逻辑更加清晰和可维护。

    useContext的实现效果就是共享数据,那么它的好处不言而喻肯定可以避免层层传递props,只需要在外层创建(createContext),多个组件直接通过useContext使用。

  6. 网上我看有一个说法是useReduceruseContext配合就是redux。这句话是有问题的。因为它们其实是有区别的。

    确实它们是有相似点,比如说可以集中管理状态都可以实现将状态的管理集中在一个地方。通过定义特定的函数(如 reducer 函数)来控制状态的更新方式,确保状态更新的逻辑清晰和可预测。

    但是它们也是有区别的:

    Redux 通常被用于大型和复杂的应用,它具有更丰富的中间件、严格的架构模式和更多的工具支持。

    useReducer + useContext 组合在小型到中型项目中可能更简洁和轻量。

    你可以想象如果你要使用很多的 useReducer + useContext 组合,后面随着组件的复杂程度,肯定是非常难维护的。但是你在使用Redux 它有明确的实现架构,代码逻辑也十分清晰,但是你的useReducer + useContext 组合是嵌套在组件内部的。

当我们通过上面的例子的使用以及问题的讲解之后,希望您会对上面三个常用的状态管理 Hooks有更加深入的理解。

副作用管理 Hooks:

副作用指的是那些影响组件外部的操作,比如数据获取、DOM 操作等。

  1. useEffect:
    • 用于处理组件的副作用操作。
    • 可以在函数组件中执行副作用逻辑,支持在组件挂载、更新和卸载时执行不同的操作。
    • 接收一个回调函数和一个依赖数组作为参数,通过依赖数组控制副作用的触发时机。
  2. useLayoutEffect:
    • 类似于 useEffect,但在浏览器进行布局和绘制之前同步执行副作用。
    • 适用于那些需要在页面布局更新前立即执行的副作用操作。
问题:

对于上面的副作用管理,我们不用像上面的来举具体的例子,把下面的几个问题讲解一下应该就能够领会这其中奥秘。

  1. 两者区别

    useEffect来说:

    • 执行时机useEffect 是在浏览器完成渲染后,但在浏览器开始新的帧之前执行的。这意味着它允许浏览器先完成当前的渲染队列,然后才执行副作用操作。
    • 异步性:由于它在渲染之后执行,因此它是异步的。这意味着即使 useEffect 中的代码抛出错误,也不会阻止页面的渲染或破坏用户界面。
    • 用途useEffect 通常用于数据获取、订阅/取消订阅、执行网络请求或任何不会直接影响当前渲染结果的副作用操作。

    useLayoutEffect来说:

    • 执行时机useLayoutEffect 是在浏览器完成渲染更新但未重绘之前同步执行。这意味着它在浏览器进行布局(重排)和重绘之前发生。
    • 同步性:因为 useLayoutEffect 在同一个动画帧内执行,所以它是同步的。如果在此钩子中抛出错误,它可能会影响当前的渲染流程,甚至导致渲染失败。
    • 用途useLayoutEffect 更适合那些需要立即访问最新的 DOM 状态或需要在 DOM 更新后立即执行的操作,例如测量 DOM 元素的尺寸、在 DOM 更新后立即修改样式或进行精确的 DOM 操作。
  2. 选择哪个钩子?

    • 如果你的副作用操作不需要在 DOM 更新的瞬间立刻执行,或者可能会阻塞浏览器渲染,那么你应该使用 useEffect
    • 如果你的副作用操作依赖于最新的 DOM 状态,并且需要在浏览器重排和重绘之前完成,那么你应该使用 useLayoutEffect
  3. useEffect中它有两个参数:一个副作用函数和一个依赖数组。依赖数组是 useEffect 的第二个参数,它告诉 React 何时重新运行副作用函数。依赖数组可以为空数组 [],也可以包含一个或多个值。

    • 空依赖数组 []

      当依赖数组为空时,副作用函数只会在组件首次渲染后运行一次,并且在组件卸载时运行清理函数(如果有)。这是类似于 componentDidMountcomponentWillUnmount 的组合。

    • 非空依赖数组

      当依赖数组包含一个或多个值时,副作用函数将在这些值变化时重新运行。这意味着每当依赖项中的任何一个值发生改变,useEffect 的副作用函数就会重新执行。

    • 特殊情况:省略依赖数组

      如果省略了依赖数组,useEffect 将在每次组件渲染后运行副作用函数。当你使用这种方式多了的话,会造成性能缺陷,因为它会导致不必要的重新计算和渲染。

优化相关 Hooks:

用于优化组件的性能和渲染效率。

  1. useMemo:
    • 用于缓存计算结果。
    • 只有当依赖项发生变化时,才会重新计算结果,避免不必要的重复计算。
  2. useCallback:
    • 用于缓存函数。
    • 只有当依赖项发生变化时,才会重新创建函数,避免子组件不必要的重新渲染。

举例

  1. 使用 useMemo

    import { Button } from 'antd'
    import React, { useState, useMemo } from 'react'
    
    const MyComponent = () => {
      const [numbers, setNumbers] = useState([1, 2, 3, 4, 5])
      const [extraValue, setExtraValue] = useState(0)
    
      // 不使用 useMemo
      function sumWithoutMemo() {
        console.log('不使用 useMemo')
        return numbers.reduce((acc, curr) => acc + curr, 0)
      }
    
      // 使用 useMemo
      const sumWithMemo = useMemo(() => {
        console.log('使用 useMemo')
        return numbers.reduce((acc, curr) => acc + curr, 0)
      }, [numbers])
    
      return (
        <div>
          <h1>不使用 useMemoo: {sumWithoutMemo()}</h1>
          <h1>使用 useMemo: {sumWithMemo}</h1>
          <Button onClick={() => setNumbers([...numbers, extraValue])}>新增</Button>
          <Button onClick={() => setExtraValue(extraValue + 1)}>
            设置extraValue
          </Button>
        </div>
      )
    }
    
    export default MyComponent
    

    运行结果

    界面在这里插入图片描述

    打印在这里插入图片描述

    当点击新增的时候:

    界面在这里插入图片描述

    打印外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    当点击设置extraValue的时候:

    界面在这里插入图片描述

    打印在这里插入图片描述

    但是当我们再次去点击新增的时候:

    界面在这里插入图片描述

  2. 使用 useCallback

    import React, { useState, useCallback, useEffect } from 'react'
    import { Button } from 'antd'
    
    const CounterComponent = () => {
      const [countNoUseCallback, setCountNoUseCallback] = useState(0)
      const [countUseCallback, setCountUseCallback] = useState(0)
    
      let prevIncrementNoUseCallback: any = null
      let prevIncrementUseCallback: any = null
    
      // 不使用 useCallback
      const incrementNoUseCallback = () => {
        setCountNoUseCallback(countNoUseCallback + 1)
      }
    
      // 使用 useCallback
      const incrementUseCallback = useCallback(() => {
        setCountUseCallback(countUseCallback + 1)
      }, [countUseCallback])
    
      useEffect(() => {
        if (prevIncrementNoUseCallback !== incrementNoUseCallback) {
          console.log('不使用 useCallback')
          prevIncrementNoUseCallback = incrementNoUseCallback
        }
      }, [incrementNoUseCallback])
    
      useEffect(() => {
        if (prevIncrementUseCallback !== incrementUseCallback) {
          console.log('使用 useCallback')
          prevIncrementUseCallback = incrementUseCallback
        }
      }, [incrementUseCallback])
    
      return (
        <div>
          <h2>不使用 useCallback:</h2>
          <p>Count: {countNoUseCallback}</p>
          <Button onClick={incrementNoUseCallback}>
            新增 (不使用 useCallback)
          </Button>
    
          <h2>使用 useCallback:</h2>
          <p>Count: {countUseCallback}</p>
          <Button onClick={incrementUseCallback}>新增 (使用 useCallback)</Button>
        </div>
      )
    }
    
    export default CounterComponent
    

    运行结果

    点击新增 (不使用 useCallback)按钮:

    在这里插入图片描述

    点击新增 (使用 useCallback)按钮:

    在这里插入图片描述

问题:

  1. useMemo的工作流程:

    • 在使用useMemo中我们可以根据上面的界面展示和打印结果知道很多东西的。在我们第一次进入这个组件的时候,useMemo 必须被调用来初始化 memoized 值即我们渲染了组件,所以会去执行方法,在控制台打印不使用Memo使用Memo

    • 当我们去点击新增的时候因为useMemo依赖于numbers,新增会去修改numbers,所以会去执行sumWithMemo函数,打印使用Memo。因为状态改变触发重新渲染,所以在渲染的时候重新执行sumWithoutMemo函数,打印不使用Memo

    • 当我们去点击设置extraValue的时候,我们会发现区别,第一我们只会打印不使用usememo,是因为useMemo 的依赖是 [numbers] 数组, numbers 数组的引用没有变化,useMemo 就不会重新计算。我们这里能够触发渲染的只有numbers数组,这里是因为让我们观察的更加清晰这么设置的。

    • 当我们去新增的时候,通过渲染的结果我们又会发现其实设置extraValue是去做了修改的,但是useMemo是懒加载的,只有当它变化我们才会去加载。

  2. 为什么第一次渲染的时候会先输出使用Memo,在输出不使用Memo?

    • useMemo 是一个 React Hook,它会在组件的渲染阶段被调用。
    • sumWithoutMemo 函数是在组件渲染中直接被调用的。这通常发生在 useMemo 已经完成初始化之后。
    • seMemo 在组件生命周期的渲染阶段早期执行,而 sumWithoutMemo 则在渲染阶段的后期,当组件正在构建时被调用。
  3. useCallback的工作流程:

    不使用 useCallback 的情况 (incrementNoUseCallback)

    • 函数创建: 每次组件重新渲染时,incrementNoUseCallback 都会被重新定义。这是因为函数是在组件函数的主体中定义的,而组件函数每次渲染时都会重新执行。
    • 控制台输出: 当 incrementNoUseCallback 的引用发生变化时,useEffect 的依赖数组中包含的 incrementNoUseCallback 会触发 useEffect 的回调函数。在这个回调函数中,我们检查 prevIncrementNoUseCallback 是否与当前的 incrementNoUseCallback 引用不同。如果是,我们就打印 '不使用 useCallback',并且更新 prevIncrementNoUseCallback 的值。

    使用 useCallback 的情况 (incrementUseCallback)

    • 函数创建: incrementUseCallback 只会在依赖项改变或组件首次渲染时重新创建。依赖项在这个例子中是 countUseCallback。由于 useCallback 的设计,只要 countUseCallback 的值没有变化,incrementUseCallback 的引用将保持不变。
    • 控制台输出: 类似于 incrementNoUseCallback 的情况,但是 incrementUseCallback 的引用在大多数情况下保持不变,除非 countUseCallback 的值发生变化。因此,'使用 useCallback' 这条消息只会在组件首次渲染时打印,或者当 countUseCallback 发生变化时打印。
  4. useCallback的首次渲染情况:

    • 首次渲染: 当组件首次渲染时,你将看到两条消息被打印,一条表示 incrementNoUseCallback 被创建,另一条表示 incrementUseCallback 被创建。这是正常的,因为在这时所有的函数都需要被定义。
    • 后续渲染: 当你点击任意按钮或组件重新渲染时(例如,由于外部状态变化),你将只看到 '不使用 useCallback' 的消息被打印,因为 incrementNoUseCallback 的引用在每次渲染时都会改变。而 '使用 useCallback' 的消息将不会再次出现,除非 countUseCallback 的值发生变化。
  5. useCallback函数和useMemo函数的区别以及如何选择:

    useCallback:

    • useCallback 主要解决的问题确实是与性能优化相关的,尤其是在处理函数作为 prop 传递给子组件的场景下。

    • 当一个函数被作为 prop 传递给子组件时,如果这个函数在每次父组件重新渲染时都创建一个新的引用,那么即使子组件的 props 实际上没有发生变化,子组件也会被认为接收到新的 props 而重新渲染。这可能导致不必要的性能开销,尤其是当子组件较为复杂或性能敏感时。

    useMemo:

    • 它主要用于避免不必要的计算,特别是在组件渲染过程中对复杂数据结构或昂贵计算结果的重复计算。
    • useMemo 的主要作用是缓存计算结果,当依赖项没有改变时,它会返回缓存的结果,而不是重新计算。

    如何选择

    选择 useCallbackuseMemo 主要取决于你想要优化的对象类型:

    • 如果你想要优化的是函数的创建过程,避免每次渲染都创建新的函数实例,那么你应该使用 useCallback
    • 如果你想要优化的是复杂计算的结果,避免每次渲染都重新执行相同的计算,那么你应该使用 useMemo

引用相关 Hooks:

用于获取对 DOM 元素或其他普通变量的引用。

其实就是可以通过一个hook去对DOM元素进行操作

  1. useRef:
    • 可以获取 DOM 元素的引用,方便进行直接的 DOM 操作。
    • 也可以在组件的多次渲染之间保存不变的值。
  2. useRef的使用场景:

    • 访问 DOM 元素:我们通常使用 ref 属性和 createRef() 方法来访问和操作 DOM 节点。
    • 保存函数组件内的可变状态useRef 可以用来存储那些不需要引起组件重新渲染的可变数据。
    • 保存前一次的 props 或 state:当组件在重新渲染时,propsstate 的最新值会被更新。有时你需要访问前一次渲染的值,这时你可以使用 useRef 来存储前一次的值。

举例

import { Button, Input } from 'antd'
import React, { useRef, useEffect } from 'react'

const TextInputWithFocusButton = () => {
  // 创建一个 ref
  const inputEl: any = useRef(null)

  // 在组件挂载后聚焦到输入框
  useEffect(() => {
    inputEl.current.focus()
  }, [])

  return (
    <>
      <Input ref={inputEl} type="text" />
      <Button onClick={() => inputEl.current.focus()}>聚焦</Button>
    </>
  )
}

export default TextInputWithFocusButton

运行结果

在这里插入图片描述

点击聚焦

在这里插入图片描述

自定义 hooks:

对于react中的自定义hooks使用的方式和定义的方法是跟Vue 3中的自定义hook类似的。自定义 Hooks 是一种将这些功能封装到可重用的函数中的方式,这可以显著提高代码的可读性和可维护性。

示例:

比如说现在有一个**表单处理的 Hook**示例:

import { useState } from 'react';

function useForm(initialState) {
  const [values, setValues] = useState(initialState);

  const handleChange = event => {
    setValues({
      ...values,
      [event.target.name]: event.target.value,
    });
  };

  const handleSubmit = event => {
    event.preventDefault();
    // 处理表单提交逻辑
  };

  return {
    values,
    handleChange,
    handleSubmit,
  };
}

对于上面表单处理Hook的使用:

import React from 'react'
import useForm from './useForm'
import { Input } from 'antd'

const ContactForm = () => {
  const initialState = { name: '', email: '' }
  const { values, handleChange, handleSubmit } = useForm(initialState)

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <Input
          type="text"
          name="name"
          value={values.name}
          onChange={handleChange}
        />
      </label>
    </form>
  )
}
export default ContactForm

对于自定义 Hooks的是用来说也是十分方便的,跟使用其它的hooks一样直接引入即可。

Suspense与错误边界

Suspense:

对于Suspense,在Vue 3 中也是有涉及的,它的主要作用其实跟在Vue 3中类似用于处理异步操作(如数据获取)的一种方式。它允许你在等待数据时渲染一个“加载中”状态,并在数据可用时自动替换为实际内容。

示例:

import { Suspense, lazy } from 'react';

// 异步加载的组件或依赖于异步数据的组件
const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

当组件尚未加载完毕时,Suspense 组件会显示 fallback 属性提供的内容,直到 LazyComponent 准备好为止。

错误边界:

错误边界是一个 React 组件,它可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并渲染出备用 UI 而不是崩溃的组件树。它们像普通的 React 组件一样工作,但包含 static getDerivedStateFromError()componentDidCatch() 方法。

示例:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新状态,使得下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你可以在这里记录错误信息
    console.error('Uncaught error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 渲染降级后的 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; // 渲染子组件树
  }
}

要使用错误边界,只需将它作为其他组件的父组件:

import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyWidget from './MyWidget';

const App = () => {
  return (
    <div className="App">
      <ErrorBoundary>
        <MyWidget />
      </ErrorBoundary>
    </div>
  );
}

export default App;

如果 MyWidget 或其任何子组件抛出一个未被捕获的错误,ErrorBoundary 将捕获该错误,并渲染一个简单的错误消息,而不是崩溃整个应用。

请注意,错误边界只能捕获发生在其子组件树中的错误,不能捕获事件处理器、异步代码(例如在 setTimeoutrequestAnimationFrame 中)、服务器端渲染、或者构造函数中的错误。对于异步代码中的错误,可以考虑使用 Promise.catch() 或者 async/await 结合 try/catch 块来处理。

实战示例

状态管理

假设我们有一个计数器应用,它可以在多个页面间共享状态,我们希望能够在不同的地方增加或减少计数器的值。

第一步,做好准备工作,定义计数器上下文,定义计数器状态,定义计数器提供者

// 定义一个计数器上下文
interface CounterContextType {
  count: number
  dispatch: React.Dispatch<{ type: string; payload?: any }>
}

// 定义一个计数器上下文
const CounterContext: any = createContext<CounterContextType>({
  count: 0,
  dispatch: () => {},
})
// 定义一个计数器状态
const counterReducer = (
  state: number,
  action: { type: string; payload?: number }
) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + (action.payload || 1)
    case 'DECREMENT':
      return state - (action.payload || 1)
    default:
      return state
  }
}

// 定义一个计数器提供者
const CounterProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}: any) => {
  const [count, dispatch] = useReducer(counterReducer, 0)

  return (
    <CounterContext.Provider value={{ count, dispatch }}>
      {children}
    </CounterContext.Provider>
  )
}

第二步,在需要的组件使用上下文和reducer

const CounterDemo = () => {
  const context: any = useContext(CounterContext)

  const increment = () => {
    console.log(context)
    context.dispatch({ type: 'INCREMENT' })
  }

  const decrement = () => {
    context.dispatch({ type: 'DECREMENT' })
  }

  return (
    <>
      <Button onClick={increment}>+</Button>
      <span style={{ margin: '10px' }}>{context.count}</span>
      <Button onClick={decrement}>-</Button>
    </>
  )
}

第三步:包裹应用

// 使用 CounterProvider 包裹你的应用
const App: React.FC = () => (
  <CounterProvider>
    <CounterDemo />
  </CounterProvider>
)

export default App

实现效果

在这里插入图片描述

通过上面的代码实现了一个组合使用useContextuseReducer hooksdemo

通用深度与实例

深入响应式系统

对于Vue 3响应式系统来说它利用了 JavaScriptProxy 对象,这使得 Vue 3 的响应式系统更加高效且易于理解。

  • Proxy 对象:可以拦截并定制对象的各种操作,这使得 Vue 3 能够在不修改原始对象的情况下创建响应式代理,从而提高了性能和可维护性。

  • 响应式引用Vue 3 引入了 refreactive API。ref的话就引入了.value属性来实现响应式引用,而reactive则将一个普通对象转换为响应式对象。

  • Composition API:Vue 3 的 Composition API 允许开发者以函数式的方式组合逻辑,这与 React 的 Hooks 类似,使得代码更加模块化和可复用。

对于React Hooks来说提供了一种无需编写类组件即可使用 React 状态和生命周期的能力,这极大地简化了功能组件的使用。React Hooks 的响应式系统主要关注于状态管理和副作用处理。

  • useStateuseContextuseState用于创建组件的局部状态,而 useContext 允许在组件树中共享全局状态,这使得状态管理变得简单且可预测。
  • useEffectuseEffect 是用于处理副作用(如数据获取、订阅、清理等)的 Hook。它可以让你在函数组件中执行类似生命周期的方法,但更加灵活和可组合。
  • useReduceruseContext:对于更复杂的状态管理,useReducer 提供了一种使用 Reducer 函数处理状态更新的方式,这通常与 useContext 结合使用,以便在组件之间共享状态。

总体上面来说呢,其实Vue 3 的响应式系统能够更有效地追踪数据变化,同时减少了对数据的直接操作。因为它主要是实现的依赖记录追踪和更新。ReactHooks 提供了更细粒度的状态管理和更灵活的副作用处理。所以在使用层面来说React Hooks更加灵活,Vue 3中呢会更加的直观和易于调试。

当然关于React 中还有很多核心的内容,包括 Fiber,虚拟DOMDiff算法等等这个后面再仔细了解分享,到时候我们再玩玩它们的源码。

希望能对您的学习有帮助!如果有什么问题,欢迎您跟我一起交流交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值