前言
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