React Hooks 实践:useContext 和 useReducer 的联用

前言

React Hooks 是自 16.8 版本以来推出的新特性,它提供了一种新的方式来编写 React 组件。React Hooks 的目的是为了使组件逻辑更易于阅读、测试和复用,同时还可以代替类组件(Class Components)。

在这篇文章中,我们将学习如何使用 useContext 和 useReducer 这两个 React Hooks 实现数据的传递和状态管理。我们将介绍 useContext 和 useReducer 的基本功能和用法,并且通过一个示例代码来演示如何使用这两个 Hooks 的联用。

useContext

在 React 中,如果你想在组件之间传递数据,你可以使用 props 或者 Redux 这样的状态管理库。但是,如果你想传递一些全局的数据,使用 props 就变得非常麻烦了,需要一级级地向下传递。这种情况下,使用 useContext 就是一个非常好的选择。

useContext 可以让你在 React 组件间共享数据,而不必一级级地通过 props 传递,它使用了 React 的 Context API。

基本使用方法

首先,我们需要创建一个 Context:

import React from 'react'

const ExampleContext = React.createContext()

这个 Context 提供了一个 Provider 和一个 Consumer,但是我们通常只使用 Provider。使用 Provider 可以传递一个 value 值给 Consumer 和它的子组件,从而达到在组件中共享数据的目的:

<ExampleContext.Provider value={data}>
  <MyComponent />
</ExampleContext.Provider>

在 MyComponent 中,你可以直接通过 useContext 来获取 Context 的值,无需将 props 向下传递:

import React, { useContext } from 'react'
import ExampleContext from './ExampleContext'

function MyComponent() {
  const data = useContext(ExampleContext)
  // ...
}

在 Class Component 中使用

虽然 useContext 是一个新的 React Hook,但是它可以在 Class Component 中使用,但需要一个额外的步骤。

我们需要将 Consumer 作为实例变量传递给 Class Component 的 contextType 属性:

import React, { Component } from 'react'
import ExampleContext from './ExampleContext'

class MyComponent extends Component {
  static contextType = ExampleContext

  render() {
    const data = this.context
    // ...
  }
}

一个实际应用场景

一个典型的场景是,我们想在全局共享一个全局的语言设置,以便在所有组件中使用同一种语言。我们可以创建一个 LanguageContext,它的 value 包含了语言和语言切换的方法:

import React from 'react'

const LanguageContext = React.createContext({
  language: 'zh-CN',
  setLanguage: () => {},
})

export default LanguageContext

然后,我们在顶层组件中使用这个 Context 的 Provider 来传递语言:

import React, { useState } from 'react'
import LanguageContext from './LanguageContext'

function App() {
  const [language, setLanguage] = useState('en-US')

  return (
    <LanguageContext.Provider value={{ language, setLanguage }}>
      <MyComponent />
    </LanguageContext.Provider>
  )
}

在 MyComponent 中,我们就可以获取这个语言值了:

import React, { useContext } from 'react'
import LanguageContext from './LanguageContext'

function MyComponent() {
  const { language, setLanguage } = useContext(LanguageContext)
  // ...
}

useReducer

在 React 中管理状态的方式之一是使用 setState,但是当需要处理更复杂的状态逻辑时,useState 并不总是能够满足需求。这时候就需要用到 useReducer。

使用 useReducer 进行状态管理时,需要定义一个 reducer 函数和一个初始状态。reducer 函数在接收一个 action 对象后返回一个新的状态。当需要更新状态的时候,我们通过 dispatch 函数来触发 reducer 函数的执行。

使用 useReducer 的好处在于它能够更好地处理一些拥有复杂操作的状态,提高代码可读性,降低代码的耦合度。

基本使用方法

我们创建一个 reducer 函数,该函数将接收两个参数——state 和 action,并返回新的 state,这类似于 Redux 中的 reducer 函数。我们还需要提供一个初始值:

import React, { useReducer } from 'react'

const initialState = {
  count: 0
}

function reducer(state, action) {
  switch(action.type) {
    case 'increment': 
      return { count: state.count + 1 }
    case 'decrement': 
      return { count: state.count - 1 }
    default: 
      throw new Error(`Unknown action type: ${action.type}`)
  }
}

然后,我们在组件中调用 useReducer 来获取当前的 state 和 dispatch 函数:

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

每次点击“+”或“-”按钮都会触发一个 dispatch 函数,该函数会将 action 传递给 reducer 函数,然后生成新的 state。组件会重新渲染并显示最新的状态。

初始化数据

有时候我们需要从后端获取一些初始数据,而我们需要保证获取成功后再去渲染组件。在这种情况下,我们可以在组件中使用 useReducer,并且使用前两个参数(reducer 和 initialState),但我们还需要传递一个回调函数来从后端获取数据。

下面是一个例子:

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState, init)

  function init(initialState) {
    fetch('/my/api')
      .then(res => res.json())
      .then(data => dispatch({ type: 'SET_DATA', payload: data }))
  }

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

在这个例子里,我们传递了第三个参数 init 函数。这个 init 函数将在 useReducer 第一次调用的时候被执行,并且会接收到初始状态 initialState。在 init 函数中,我们可以调用一个异步请求来获取数据,并且使用 dispatch 函数将数据放入状态中去。

useContext 和 useReducer 的联用

接下来,我们来学习如何使用 useContext 和 useReducer 之后的联用,以达到更优秀的状态管理效果。

一个实际应用场景

假设有这么一个需求:在全局保存一个用户列表的状态,我们需要从后端获取数据并在应用中共享。

我们按照下列步骤来实现:

定义一个 UserListContext,它的 value 包含了一个用户列表和一个操作这个列表的 reducer:

import React, { createContext, useReducer, useEffect } from 'react'

const UserListContext = createContext()

const reducer = (state, action) => {
  switch (action.type) {
    case 'set': {
      return {
        userList: action.userList,
      }
    }
    case 'add': {
      return {
        userList: [...state.userList, action.user],
      }
    }
    default: {
      throw new Error(`Unsupported action type: ${action.type}`)
    }
  }
}

const initialState = {
  userList: [],
}

const UserListProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)

  useEffect(() => {
    fetch('/api/user-list')
      .then(res => res.json())
      .then(data => {
        dispatch({ type: 'set', userList: data })
      })
  }, [])

  return (
    <UserListContext.Provider value={{ state, dispatch }}>
      {children}
    </UserListContext.Provider>
  )
}

export { UserListContext, UserListProvider }

UserListProvider 使用了 useEffect 来获取数据并 dispatch 到 UserListContext 中。我们还定义了一个 reducer 函数,它支持两个 action:set 和 add。set 用来设置整个用户列表,add 用来添加一个用户。

然后,在 App 中使用这个 Provider 来传递用户列表:

function App() {
  return (
    <UserListProvider>
      <MyComponent />
    </UserListProvider>
  )
}

最后,在 MyComponent 中可以通过 useContext 和 useReducer 来访问 UserListContext 中的数据:

import React, { useContext } from 'react'
import { UserListContext } from './UserListContext'

function MyComponent() {
  const { state, dispatch } = useContext(UserListContext)

  function handleAddUser() {
    const user = { name: `user-${state.userList.length + 1}` }
    dispatch({ type: 'add', user })
  }

  return (
    <div>
      <ul>
        {state.userList.map(user => (
          <li key={user.name}>{user.name}</li>
        ))}
      </ul>
      <button onClick={handleAddUser}>Add User</button>
    </div>
  )
}

我们通过 useContext 获取了 UserListContext 中传递的值,并声明了一个名为 handleAddUser 的函数。该函数使用 dispatch 函数将一个新用户添加到用户列表中,并触发 MyComponent 的重新渲染。

总结

使用 useContext 和 useReducer 可以更方便地管理 React 组件中的状态。在实际应用中,我们可以通过在全局中共享状态,优化组件访问和更新状态的方式,提高代码的可读性,降低代码的重复和耦合度。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65966b73eb4cecbf2da3e79e


纠错反馈