Пять лучших практик, которые помогут хорошо структурировать состояние

Когда мы пишем в React компонент, который содержит какое-то состояние, нам придется выбирать, сколько переменных состояния мы хотим и как его структурировать.

Хотя можно написать работающий код даже со стандартной структурой состояний, вот несколько шагов, которые помогут вам сделать лучший выбор.

1. Связанное с группой состояние

Если две переменные состояния изменяются вместе, рекомендуется объединить их в одну переменную состояния.

Пример:

Объедините две переменные:

const [x, setX] = useState(0);
const [y, setY] = useState(0);

В приведенную ниже структуру:

const [position, setPosition] = useState({ x: 0, y: 0 });

Технически оба имеют одинаковый подход. но это уменьшает количество строк кода.

Примечание.Если ваше состояние является объектом, вы не можете обновить только одно поле в нем, поэтому вы можете сделать что-то вроде этогоsetPosition({...position, x:100}).

2. Избегайте дублирования в состоянии

Когда одни и те же данные дублируются между несколькими переменными состояния или внутри вложенных объектов, их трудно синхронизировать.

Пример:

import { useState } from 'react';
const initialItems = [
  { id: 0, name: 'Indian Cuisine', },
  { id: 1, name: 'Italian Cuisine', },
  { id: 2, name: 'Chinese Cuisine' },
];
export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );
return (
    <>
      <h2>What's your Favourite Cuisine?</h2>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name}
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.name}.</p>
    </>
  );
}

В настоящее время он сохраняет выбранный элемент как объект в переменной состояния selectedItem. Однако это не очень хорошо: содержимое selectedItem — это тот же объект, что и один из элементов в списке items. Это означает, что информация о самом элементе дублируется в двух местах.

Хотя вы также можете обновить selectedItem, более простое решение — удалить дублирование. В приведенном ниже примере вместо объекта selectedItem (который создает дублирование объектов внутри items) вы сохраняете selectedId в состоянии и затем получаете selectedItem путем поиска в массиве items элемента с этим ИДЕНТИФИКАТОР .

Пример:

import { useState } from 'react';
const initialItems = [
  { id: 0, name: 'Indian Cuisine', },
  { id: 1, name: 'Italian Cuisine', },
  { id: 2, name: 'Chinese Cuisine' },
];
export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(0);
const selectedItem = items.find(item =>
    item.id === selectedId
  );
function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          name: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }
return (
    <>
      <h2>What's your travel snack?</h2>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.name}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedId(item.id);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.name}.</p>
    </>
  );
}

Вам не нужно сохранять выбранный элемент в состоянии, поскольку важен только выбранный идентификатор.

3. Избегайте избыточного состояния

Если вы можете вычислить некоторую информацию из свойств компонента или его существующих переменных состояния во время рендеринга, вы не должны помещать эту информацию в состояние этого компонента.

Пример:

import { useState } from 'react';
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }
function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }
return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

Эта форма имеет три переменные состояния: firstName, lastName и fullName. Однако fullName является избыточным. Вы всегда можете вычислить fullName из firstName и lastName во время рендеринга, поэтому удалите его из состояния.

Вот как вы можете это сделать:

import { useState } from 'react';
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }
function handleLastNameChange(e) {
    setLastName(e.target.value);
  }
return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

4. Избегайте противоречий в состоянии

Когда состояние структурировано таким образом, что несколько частей состояния могут противоречить друг другу, у вас, вероятно, есть шанс совершить ошибку. Постарайтесь избежать этого.

Пример:

import { useState } from 'react';
export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  const [isSent, setIsSent] = useState(false);
async function handleSubmit(e) {
    e.preventDefault();
    setIsSending(true);
    await sendMessage(text);
    setIsSending(false);
    setIsSent(true);
  }
if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }
return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}
// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

Хотя этот код работает, он оставляет вас в замешательстве относительно того, какое именно состояние нужно обновить. Например, если вы забудете назвать setIsSent и setIsSending вместе, вы можете оказаться в ситуации, когда и isSending, и isSent будут true одновременно.

Поскольку isSending и isSent никогда не должны быть true одновременно, лучше заменить их одной переменной состояния status, которая может принимать одно из трех допустимых состояний: 'typing' (начальное), 'sending' и 'sent'.

import { useState } from 'react';
export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [status, setStatus] = useState('typing');
async function handleSubmit(e) {
    e.preventDefault();
    setStatus('sending');
    await sendMessage(text);
    setStatus('sent');
  }
const isSending = status === 'sending';
  const isSent = status === 'sent';
if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }
return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}
// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

Чем сложнее ваш компонент, тем сложнее будет понять, что произошло.

5. Избегайте глубоко вложенных состояний

Глубоко иерархическое состояние не очень легко обновлять, поэтому рекомендуется сделать структуру состояния плоской.

Пример:

import { useState } from 'react';
const initialTravelPlan = {
  id: 0,
  title: '(Root)',
  childPlaces: [{
    id: 1,
    title: 'Earth',
    childPlaces: [{
      id: 2,
      title: 'Africa',
      childPlaces: [{
        id: 3,
        title: 'Botswana',
        childPlaces: []
      }, {
        id: 4,
        title: 'Egypt',
        childPlaces: []
      }, {
        id: 5,
        title: 'Kenya',
        childPlaces: []
      }, {
        id: 6,
        title: 'Madagascar',
        childPlaces: []
      }, {
        id: 7,
        title: 'Morocco',
        childPlaces: []
      }, {
        id: 8,
        title: 'Nigeria',
        childPlaces: []
      }, {
        id: 9,
        title: 'South Africa',
        childPlaces: []
      }]
    }, {
      id: 10,
      title: 'Asia',
      childPlaces: [{
        id: 11,
        title: 'China',
        childPlaces: []
      }, {
        id: 12,
        title: 'Hong Kong',
        childPlaces: []
      }, {
        id: 13,
        title: 'India',
        childPlaces: []
      }, {
        id: 14,
        title: 'Singapore',
        childPlaces: []
      }, {
        id: 15,
        title: 'South Korea',
        childPlaces: []
      }, {
        id: 16,
        title: 'Thailand',
        childPlaces: []
      }, {
        id: 17,
        title: 'Vietnam',
        childPlaces: []
      }]
    },{
    id: 18,
    title: 'Mars',
    childPlaces: [{
      id: 19,
      title: 'Corn Town',
      childPlaces: []
    }, {
      id: 20,
      title: 'Green Hill',
      childPlaces: []      
    }]
  }]
};
function PlaceTree({ id, placesById }) {
  const place = placesById[id];
  const childIds = place.childIds;
  return (
    <>
      <li>{place.title}</li>
      {childIds.length > 0 && (
        <ol>
          {childIds.map(childId => (
            <PlaceTree
              key={childId}
              id={childId}
              placesById={placesById}
            />
          ))}
        </ol>
      )}
    </>
  );
}
export default function TravelPlan() {
  const [plan, setPlan] = useState(initialTravelPlan);
  const root = plan[0];
  const planetIds = root.childIds;
  return (
    <>
      <h2>Places to visit</h2>
      <ol>
        {planetIds.map(id => (
          <PlaceTree
            key={id}
            id={id}
            placesById={plan}
          />
        ))}
      </ol>
    </>
  );
}

Вы можете сколько угодно вкладывать состояния, но если сделать их «плоскими», это может решить множество проблем. Это упрощает обновление состояния и помогает избежать дублирования в разных частях вложенного объекта.

Заключение

Выбор государственной структуры очень важен, чтобы избежать путаницы и дублирования. Забота о второстепенных вещах при написании кода приводит к идеальному оптимизированному коду.

Создавайте компонуемые веб-приложения

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых фреймворках, таких как React или Node. Создавайте масштабируемые интерфейсы и серверные части с мощным и приятным опытом разработки.

Перенесите свою команду в Bit Cloud, чтобы совместно размещать и совместно работать над компонентами, а также значительно ускорить, масштабировать и стандартизировать разработку в команде. Начните с компонуемых интерфейсов, таких как Design System или Micro Frontends, или исследуйте компонуемый сервер. Попробуйте →

Узнать больше