Blog.

两种 useReducer 使用场景

LiuuY

我发现很多人 useReducer 使用的频率很少,大多都是使用 useState 来管理状态,但是很多情况下用 useReducer 更加合适,这里介绍两种场景:1. 组件中包含非常多的 useState;2. 全局状态管理。

组件中包含非常多的 useState

有时候,我们的组件可能会包含多个状态(state),例如设计一个用户地址组件:

import { useState } from 'react'

function UserAddress() {
  const [firstName, setFirstName] = useState('')
  const [lastName, setLastName] = useState('')
  const [phone, setPhone] = useState('')
  const [street, setStreet] = useState('')
  const [city, setCity] = useState('')
  const [state, setState] = useState('')
  const [country, setCountry] = useState('')

  const handlePhoneInput = (value) => {
    if (value[0] !== '1') {
      // 错误处理
    }
    setPhone(value)
  }

  /* ... */
  
  return <>
    <input value={phone} onChange={e => handlePhoneInput(e.target.value)} />
    {/* ... */}
  </>
}

首先很多的 useState 导致阅读不清晰。更重要的是,每个 state 都应该有相应的校验条件,例如,phone 必须以 1 开头,这样使用 useState,我们的校验逻辑只能在「Event Handler」中,例如上面代码中的 handlePhoneInput,这就导致处理逻辑分散,如果遇到相关联的字段处理也是麻烦。

我们可以使用 useReducer 的 reducer 函数,统一处理校验逻辑,使逻辑更紧凑更清晰:

import { useReducer } from 'react'

// 将所以的状态处理逻辑统一
const addressReducer = (state, action) => {
 switch (action.type) {
    case 'set_phone': {
      if (action.value[0] !== '1') {
        // 错误处理
      }
      return {
        ...state,
        phone: action.value
      }
    }
    /* ... */
  }
}

function UserAddress() {
  const [event, updateAddress] = useReducer(addressReducer, 
    { firstName: '', lastName: '',  phone: '', street: '', city: '', state: '', country: '' })
  
  return <>
    <input value={event.phone} 
      onChange={e => updateAddress({ type: 'set_phone', value: e.target.value }) } />
    {/* ... */}
  </>
}

全局状态管理

很多情况下,我们可能不想为了几个全局变量(例如主题、用户信息等等),而引入如 ReduxZusland 等等状态管理库,此时我们可以结合 useReducer 和 useContext 来完成简单的全局状态管理。

例如我们的上述的用户地址组件的各个 state, 但是在组件树的不同位置,我们可以将 useReducer 的 dispatch 方法,通过 useContext,传递到不同的组件树位置,同时保留了统一的状态管理(reducer),这就是一个简单的全局状态管理了。

const AddressContext = React.createContext(null);

function App() {
  const [event, updateAddress] = useReducer(addressReducer, 
    { firstName: '', lastName: '',  phone: '', street: '', city: '', state: '', country: '' })

  return (
    <AddressContext.Provider value={updateAddress}>
      <DeepTree address={address} />
    </AddressContext.Provider>
  );
}

可以在不同的位置调用 updateAddress 方法更新全局状态:

function PhoneInput(props) {
  const updateAddress = useContext(AddressContext);

  function handlePhoneInput(value) {
    updateAddress({ type: 'set_phone', value });
  }

  return (
    <input onChange={e => handlePhoneInput(e.target.value)} />
  );
}

总结

当遇到我们遇到多个状态,不仅仅是简单的更新,而是需要先进行某些处理之后再进行更新时,我们可以考虑使用 useReducer,即将复杂的更新逻辑统一在一个地方处理(reducer),进而使代码清晰。

同时 useReducer 搭配 useContext 使用也是一个很简洁的全局状态管理工具。