前端技术React 重新学习

React 文档再学习 3 管理状态 - 正确的组织状态

2022-11-20  本文已影响0人  吴摩西

保留或者重设状态

对于以下的组件

import { useState } from 'react';
export default function Chat({ contact }) {
  const [text, setText] = useState('');
  return (
    <section>
      <textarea
        value={text}
        placeholder={`Chat to ${contact.name}`}
        onChange={e => setText(e.target.value)}
      />  
    </section>
  );
}

如果重置了 contact ,组件内部的 <textarea /> 的值不会被重置。如果需要重置整个 <textarea />,当然可以通过 useEffect 实现。不过如 React 的文档所示,React 提供了方法来覆盖这种默认行为,强制组件重置。即给组件传递 key 属性。例如 <Chat key={email} />。这样会告知 React,如果 key 的值变动,可以认为是一个完全不同的 Chat 组件。Chat 会被重新从数据上从零创建。这样如果 key 变动了, textarea 内的值就会被重置。这种方法对于某些场景下,强制动画等行为很有帮助。

将 state 的相关逻辑抽离成 reducer

当 state 过多时,将它抽离成 reducer 应该会较有帮助。可以保持较好的代码聚合性。

const initialTasks = [];
function tasksReducer(tasks, action) {
  switch(action.type) {
    case ’add‘: {
      return [
        ...tasks, {
          id: action.id,
          text: action.text,
          done: false
        }
      ];
    }
  }
}
export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

   // some component
   return <button onClick={() => dispatch({ type: 'add', id: 1, text: 'Task' })}></button>;
}

可以把 contextreducer结合来较好的管理复杂状态

声名式 UI

  1. 识别组件的不同的状态
  2. 决定状态的变化是如何变迁的
  3. 在内存中使用 useState 表述状态
  4. 去除不重要的状态变量

选择状态结构

好的状态结构可以让修改、debug组件变得容易。不好的状态结构是 bug 的来源。虽然次优的状态结构也能工作,不过还是有一些状态结构的原则。

  1. 组合相关状态。如果两个状态总是同时更新,考虑把他们合并成一个状态变量。
  2. 避免状态矛盾 (contradiction)
  3. 避免冗余状态,如果有的状态可以通过其他状态计算得来,就不应该存储在状态树中。
  4. 避免深度嵌套的状态。过于结构化的状态难以管理,如果可能,应当选择扁平的状态结构。

参考 数据库范式 Database normalization description

  1. 如果数据存储在多个地方,就要保证同时更新他们,并且更新的时候要保证相同的逻辑。维护起来就会很困难。
  2. 不一致的依赖: 例如把一个员工的工资存储在他关联的客户表里,可能导致数据获取数据的路径遗矢或断裂。
  3. 数据库范式 一般有三种。在前端工程中,难有相关的约束。大量的数据被重复以方便显示。

组合状态

例如有状态跟踪鼠标位置,应该把鼠标的 x, y 坐标组合存储在一对象里,而不是存储在两个变量 x, y 中。

// 这样的代码难以维护
function MouseTrace() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
}
// 将状态进行组合,会让代码容易维护
function MousePositionTrace() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
}

避免状态矛盾

例如下面的代码跟踪数据是否发送

export function FeedbackForm() {
  const [val, setVal] = useState('');
  // 数据发送中
  const [isSending, setIsSending] = useState(false);
  // 数据已经发送
  const [isSent, setIsSent] = useState(false);
}

虽然上述的代码可以工作,不过却留下了 不可能 状态的可能,就是 isSending=true, isSent=true, 让组件处在矛盾的状态中。正确的用法是:

export function FeedbackForm() {
  const [val, setVal] = useState('');
  const [status, setStatus] = useState('typing');
  const isSending = status === 'sending';
  const isSent = status === 'sent';
}

isSending, isSent 可以从状态中派生,不再是组件状态,从而保证他们不会不一致。

避免重复的状态

const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];
function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(items[0]);
  // 修改 items,可能会导致 selectedItem 与 items 不一致。
}

正确的方法是 item 只存储在 items 中,选中的状态中只有一个 id。

const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];
function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(0);
  // 选中的数据应该只使用 id
  const selectedItem = items.find(item => item.id == selectedId);
}

避免深入嵌套的状态

在树形结构中,使用 id 引用子数据,全局数据中存储 KV 结构。例如

const tree = {
  0: {
    id: 0,
    name: 'root',
    children: [1, 2 ,3]
  },
  1: {
    id: 1,
    name: 'child_1',
    children: []
   },
   2: {
    id: 2,
    name: 'child_2',
    children: []
   },
   3: {
    id: 3,
    name: 'child_3',
    children: []
   }
};

方便数据的组织和更新。

后记

在实际项目中,笔者似乎很少有这些思考。这些状态组织的原则,应该会对代码的组织有一些指导作用。值得在实际项目中实践。

上一篇下一篇

猜你喜欢

热点阅读