如何优雅的使用useState

197 阅读4分钟

React Hooks中的useState是功能强大的一个hook,它允许我们在不使用class组件的情况下实现组件内部state的管理。但是如果没有正确和优雅的使用useState,也很容易造成代码混乱和难以维护。

1. 用多个state而不是一个复杂的state对象

刚开始使用useState时,我们可能倾向于只定义一个state对象来存储所有数据:

const [state, setState] = useState({
  name: '',
  age: 0,
  address: ''  
});

这样的问题在于,每次更新任何一个字段时,我们都需要先拷贝一份完整的state对象,修改对应字段,然后再调用setState。代码会变得冗余且难以维护。

相比之下,拆分成多个独立的state会更加简单明了:

const [name, setName] = useState('');
const [age, setAge] = useState(0); 
const [address, setAddress] = useState('');

只需要更新相关数据即可。但这种使用方法也只适用于组件体积小,需要定义的state较少时使用,当我们的组件较为复杂,需要定义的state较多时,上述方法就会导致下面的一系列问题:

  1. 组件状态难以管理:多个 useState 意味着需要管理多个独立的状态与设置状态的函数,状态之间的关系容易混乱,导致难以管理。
  2. 性能问题: 使用多个 useState 会增加渲染次数,可能会对性能造成影响。每次状态改变都会引起组件的重新渲染。
  3. 冗余代码:多个 useState 需要定义多个状态变量和设置状态的函数,导致冗余代码增多。
  4. 逻辑分散:大量 useState 钩子分散在组件各处,导致相关状态逻辑也分散,不利于管理和理解。
  5. 增加复杂性:组件变得更复杂,可读性和可维护性下降。

2. 封装自定义hooks复用逻辑

如果单个组件较为复杂,组件内部定义state较多时,我们可以通过封装如下自定义hooks来解决。

import { useMemoizedFn, useSafeState } from 'ahooks';
import { isFunction, isObjectLike } from 'lodash-es';
import React, { useMemo } from 'react';

function useSimpleState<T>(s: T) {
  const [state, setState] = useSafeState(s);
  const stateChange = useMemoizedFn((d: Partial<T>) => setState((old) => ({ ...old, ...d })));
  const isObject = useMemo(() => isObjectLike(s) || (isFunction(s) && isObjectLike(s())), []);

  const result = useMemo(
    () => [state, isObject ? stateChange : setState, setState] as [T, (data: Partial<T>) => void, React.Dispatch<React.SetStateAction<T>>],
    [isObject, setState, state, stateChange],
  );
  return result;
}

export default useSimpleState;

以上自定义hook中使用到的ahooks和lodash的方法,可以到ahooks和lodash的官方文档去了解,这里就不作过多的赘述。 我们在定义state时就可以使用当前封装的自定义hook useSimpleState。

function ComponentA() {

const [state, updateState] = useSimpleState({name:string, age:number, sex:string}); 

//... 
//修改状态
updateState({name:'小明'})

}

在多个组件中如果存在重复的业务逻辑,我们可以通过自定义hooks将其抽象成可重用的逻辑单元。

例如可以定一个useProfile hook来初始化和管理用户配置文件:

import {useState} from 'react';

function useProfile() {
  const [name, setName] = useState('');  
  const [age, setAge] = useState(0);
  const [avatar, setAvatar] = useState(''); 
  
  // 省略业务逻辑...
  
  return {
    name, 
    age,
    avatar,
    setName,
    setAge,
    setAvatar
  };
}

export default useProfile;

不同组件可以轻松复用这个逻辑:

function ComponentA() {
  
  const {name, age, setName, setAge} = useProfile();
  
  //...
  
}

function ComponentB() {

  const {avatar, setAvatar} = useProfile(); 
  
  //...

}

这种方式提高了代码的抽象级别,也使测试和维护更为方便。

3. useReducer优化复杂逻辑

当业务逻辑较为复杂时,可以使用useReducer hook代替多个useState。useReducer允许我们通过reducer函数集中管理组件状态逻辑。

import {useReducer} from 'react';

const initialState = {
  name: '',
  age: 0,    
  friends: []
};

const reducer = (state, action) => {
  switch(action.type) {
    case 'SET_NAME': 
      return {...state, name: action.payload};
     
    case 'SET_AGE':
      return {...state, age: action.payload};
    
    case 'ADD_FRIEND': 
      return {...state, friends: [...state.friends, action.payload]};
    
    // 省略其他action         
  }
}

function Component() {
  
  const [state, dispatch] = useReducer(reducer, initialState); 
  
  const setName = (name) => {
    dispatch({type: 'SET_NAME', payload: name}); 
  }; 
  
  // 其他action creator函数
  
  return (
    <div>
      { /* 使用state和action creators */ }
    </div>
  )
}

这种方式可以避免定义多个useState同时又保持代码解耦,很好的管理了组件内的状态逻辑复杂性。

结语: useState 是 React 中非常强大且灵活的状态管理工具。通过合理地使用 useState,我们可以更加优雅地管理组件的状态,并提升代码的可维护性和可读性。希望这篇文章可以帮助大家更好地理解和使用 useState。