前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】3条简单的React状态管理规则

【译】3条简单的React状态管理规则

作者头像
张张
修改2020-03-09 17:24:36
2.1K0
修改2020-03-09 17:24:36
举报
文章被收录于专栏:前端全栈开发者

React组件内部的状态是在渲染之间保持不变的封装数据。useState()是React钩子,负责管理功能组件内部的状态。

我喜欢useState()确实使状态处理变得非常容易。但是我经常遇到类似的问题:

  • 我应该将组件的状态划分为小状态,还是保持复合状态?
  • 如果状态管理变得复杂,我应该从组件中提取它吗?怎么做?
  • 如果useState()用法是如此简单,那么何时需要useReducer()

这篇文章介绍了3条简单的规则,可以回答上述问题并帮助您设计组件的状态。

1.一个关注点

高效状态管理的首要原则是:让一个状态变量负责一个关注点。

让一个状态变量负责一个关注点使它符合单一责任原则。让我们来看一个复合状态的例子,即一个包含多个状态值的状态。

代码语言:javascript
复制
const [state, setState] = useState({
  on: true,
  count: 0
});

state.on // => true
state.count // => 0

状态由一个普通的JavaScript对象组成,该对象具有属性oncount

第一个属性state.on包含一个布尔值,表示开关。state.count保存一个表示计数器的数字,例如,用户单击按钮的次数。

然后,假设您要将计数器增加1:

代码语言:javascript
复制
// 复合状态更新
setUser({
  ...state,
  count: state.count + 1
});

您必须将整个状态保持在附近才能更新计数。这是一个需要调用的大型构造来简单地增加一个计数器:因为一个状态变量负责两个关注点:开关和计数器。

解决方案是将复合状态分成2个原子状态并计数:

代码语言:javascript
复制
const [on, setOnOff] = useState(true);
const [count, setCount] = useState(0);

on状态变量仅负责存储开关状态。同样的方法,count变量仅负责计数器。

现在,让我们尝试更新计数器:

代码语言:javascript
复制
setCount(count + 1);
// 或者使用回调
setCount(count => count + 1);

计数状态仅负责计数,易于推理,分别易于更新和读取。

不必担心调用多个useState()为每个关注点创建状态变量。

但是请注意,如果您过多使用useState()变量,则很有可能您的组件违反了“单一职责原则”。只需将此类组件拆分为较小的组件即可。

2.提取复杂的状态逻辑

将复杂的状态逻辑提取到自定义钩子中。

将复杂的状态操作保留在组件中是否有意义?

创建React Hook是为了将组件从复杂的状态管理和副作用中隔离出来。因此,由于组件应该只关心要呈现的元素和要附加的一些事件侦听器,所以应该将复杂的状态逻辑提取到自定义Hook中。

让我们考虑一个管理产品列表的组件。用户可以添加新的产品名称。约束是产品名称必须唯一。

第一次尝试是将产品名称列表的设置程序直接保留在组件内部:

代码语言:javascript
复制
function ProductsList() {
  const [names, setNames] = useState([]);
  const [newName, setNewName] = useState('');
  const map = name => <div>{name}</div>;

  const handleChange = event => setNewName(event.target.value);
  const handleAdd = () => {
    const s = new Set([...names, newName]);
    setNames([...s]);
  };

  return (
    <div className="products">
      {names.map(map)}
      <input type="text" onChange={handleChange} />
      <button onClick={handleAdd}>Add</button>
    </div>
  );
}

names变量保存产品名称,单击Add按钮时将调用addNewProduct()事件处理程序。

addNewProduct()中,使用一个Set对象来保持产品名称的唯一性。组件应该关注这个实现细节吗?不。

最好将复杂的状态设置器逻辑隔离到自定义Hook中。

新的自定义HookuseUnique()负责保持项目的唯一性:

代码语言:javascript
复制
// useUnique.js
export function useUnique(initial) {
  const [items, setItems] = useState(initial);
  const add = newItem => {
    const uniqueItems = [...new Set([...items, newItem])];
    setItems(uniqueItems);
  };
  return [items, add];
};

将自定义状态管理提取到一个Hook后,ProductsList组件就变得轻松多了:

代码语言:javascript
复制
import { useUnique } from './useUnique';

function ProductsList() {
  const [names, add] = useUnique([]);
  const [newName, setNewName] = useState('');
  const map = name => <div>{name}</div>;

  const handleChange = event => setNewName(e.target.value);
  const handleAdd = () => add(newName);

  return (
    <div className="products">
      {names.map(map)}
      <input type="text" onChange={handleChange} />
      <button onClick={handleAdd}>Add</button>
    </div>
  );
}

const [names, addName] = useUnique([])是启用自定义Hook的原因。组件不再与复杂的状态管理混杂在一起。

如果您想在列表中添加新名称,则只需调用add('新产品名称')

最重要的是,将复杂的状态管理提取到自定义Hook中的好处是:

  • 组件不再需要状态管理细节
  • 自定义钩子可以重用
  • 可以很容易地在隔离状态下测试自定义Hook

3.提取多个状态操作

将多个状态操作提取到一个reducer中。

继续使用ProductsList的示例,让我们添加一个Delete操作,该操作从列表中删除一个产品名称。

现在,您必须编码2个操作:添加和删除产品。处理这些操作,就可以创建一个 reducer 并使组件摆脱状态管理逻辑。

这种方法也符合 hook 的思想:从组件中提取复杂的状态管理。

这是添加和删除产品的 reducer 的可能实现:

代码语言:javascript
复制
function uniqueReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...new Set([...state, action.name])];
    case 'delete':
      return state.filter(name => name === action.name);
    default:
      throw new Error();
  }
}

然后可以通过调用 React 的 useReducer() Hook 在产品列表中使用uniqueReducer()

代码语言:javascript
复制
function ProductsList() {
  const [names, dispatch] = useReducer(uniqueReducer, []);
  const [newName, setNewName] = useState('');
  const handleChange = event => setNewName(event.target.value);
  const handleAdd = () => dispatch({ type: 'add', name: newName });

  const map = name => {
    const delete = () => dispatch({ type: 'delete', name });
    return (
      <div>
        {name}
        <button onClick={delete}>Delete</button>
      </div>
    );
  }

  return (
    <div className="products">
      {names.map(map)}
      <input type="text" onChange={handleChange} />
      <button onClick={handleAdd}>Add</button>
    </div>
  );
}

const [names, dispatch] = useReducer(uniqueReducer,[])启用了uniqueReducer。names是保存产品名称的状态变量,dispatch是要使用操作对象调用的函数。

单击添加按钮后,处理程序将调用dispatch({type:'add',name:newName})。调度添加操作使减速器uniqueReducer向状态添加新产品名称。

同样,单击“删除”按钮时,处理程序将调用dispatch({type:'delete',name})。调度删除操作会将产品名称从名称状态中删除。

4.总结

状态变量应该负责一个关注点。

如果状态具有复杂的更新逻辑,则将该逻辑从组件中提取到自定义Hook中。

同样,如果状态需要多个操作,请使用 reducer 合并这些操作。

无论您使用什么规则,状态都应尽可能简单和分离。该组件不应被状态更新的细节所困扰:它们应该是自定义Hook或 reducer 的一部分。

严格遵循这3个简单规则将使您的状态逻辑易于理解、维护和测试。


原文:https://dmitripavlutin.com/react-state-management/

首发于博客:https://cloud.tencent.com/developer/article/1595714

如果对你有一点点帮助,可以点个关注。

作者简介:做工程不做码农(微信公众号同名),Web前端工程师,坐标杭州,聚焦大前端技术的公众号,分享我的原创或精选文章,欢迎关注。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.一个关注点
  • 2.提取复杂的状态逻辑
  • 3.提取多个状态操作
  • 4.总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档