Практика сохранения состояния после перехода на страницу маршрутизации

React.js

«Сохранение состояния после перехода на страницу маршрутизации» относится к ситуации, которая часто встречается в процессе разработки бизнес-требований — после того, как страница (обычно страница формы) выполняет жестокую операцию, щелкните элемент. Для получения подробной информации перейдите на другую страницу, а затем вернуться (нажмите кнопку «Назад»), предыдущая страница также может сохранить исходное состояние (страница, условия поиска и т. д.).

Зачем сохранять состояние страницы

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

пример кода

Для удобства демонстрации исходный компонент здесь называется «Компонент А», а компонент, через который выполняется переход, называется «Компонент Б».

Подготовьте здесь небольшой проект и используйте react-router для имитации прыжков компонентов маршрутизации.

демо

test

код
function App() {
  return (
    <Router>
      <Switch>
        <Route path="/B" component={B} />
        <Route path="/" component={A} />
      </Switch>
    </Router>
  );
}

export default App;

const A = ({ history }) => {
  const [count, setCount] = useState(0);
  const handleChangeStatus = () => setCount(count + 1);

  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 A 组件</h1>
        <p>当前计数:{count}</p>
        <br />
        <button onClick={handleChangeStatus}>点我 +1</button>
        <br />
        <button onClick={() => history.push("B")}>跳转到 B 组件</button>
      </header>
    </div>
  );
};

const B = ({ history }) => {
  return (
    <div className="App">
      <header className="App-header">
        <p>这里是 B 组件</p>
        <br />
        <button onClick={() => history.goBack()}>点我返回</button>
      </header>
    </div>
  );
};

В этом примере состояние в компоненте A будет потеряно после возврата в компонент B, давайте восстановим его~

image-20200322224403581

Способы сохранить состояние страницы

Поскольку необходимо поддерживать состояние страницы (на самом деле, состояние компонента), произойдет следующие две ситуации:

  • Прежний компонент будет удален
  • Прежний компонент не будет удален

Затем мы можем получить следующие методы в соответствии с этими двумя ситуациями:

Компонент будет удален:

1. Сохраняем состояние в LocalStorage/SessionStorage

Применение

Этот метод прост, просто нужно уничтожить компонент, который вот-вот будет уничтожен.componentWillUnmountВ LocalStorage/SessionStorage вы можете сохранить состояние текущего компонента через JSON.stringify(). Здесь следует отметить, чтоКогда компонент обновляет свое состояние.

Например, когда мы переходим от компонента B к компоненту A, компоненту A необходимо обновить свое состояние. Но если мы перепрыгнем с другого компонента на компонент B, мы на самом деле хотим, чтобы компонент B перерендерился, то есть не считывал информацию из Storage.

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

код
const A = ({ history }) => {
  const [count, setCount] = useState(0);
  const handleChangeStatus = () => setCount(count + 1);

  useEffect(() => {
    // 组件挂载时读取 Storage 状态
    const status = JSON.parse(sessionStorage.getItem("_STATUS_A") || "{}");
    const { count = 0, flag = false } = status;
    flag && setCount(count);
    return () => {
      // 组件卸载时设置 flag
      changeStorageStatus("_STATUS_A", "flag", false);
    };
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 A 组件</h1>
        <p>当前计数:{count}</p>
        <br />
        <button onClick={handleChangeStatus}>点我 +1</button>
        <br />
        <button
          onClick={() => {
            // 前往 B 组件时保持状态
            sessionStorage.setItem("_STATUS_A", JSON.stringify({ count }));
            history.push("/B");
          }}
        >
          跳转到 B 组件
        </button>
      </header>
    </div>
  );
};

const changeStorageStatus = (key = "", statusKey = "", status = "") => {
  const preStatus = JSON.parse(sessionStorage.getItem(key) || "{}");
  preStatus[statusKey] = status;
  sessionStorage.setItem(key, JSON.stringify(preStatus));
};

const B = ({ history }) => {
  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 B 组件</h1>
        <br />
        <button
          onClick={() => {
            // 返回 A 组件时设置 flag
            changeStorageStatus("_STATUS_A", "flag", true);
            history.goBack();
          }}
        >
          点我返回
        </button>
      </header>
    </div>
  );
};
демо

Mar-22-2020 16-39-43

преимущество
  • Совместимость хорошая, никаких дополнительных библиотек или инструментов не требуется.
  • Простой и быстрый, он может удовлетворить большинство потребностей.
недостаток
  • Состояние сохраняется методом JSON (эквивалент глубокой копии), и если в состоянии есть особые случаи (например, объекты Date, объекты Regexp и т. д.), вместо исходного значения будет получена строка. (Для получения дополнительной информации см. недостатки глубокого копирования с помощью JSON)
  • Если компонент B возвращается назад или переход на следующую страницу не является предыдущим компонентом, то суждение о флаге будет недействительным, в результате чего компонент A будет повторно считывать хранилище при входе на страницу компонента A с других страниц, что вызовет очень странное явление.

2. Значение прохождения маршрута

Применение

Эффект передачи параметров между маршрутами может быть достигнут с помощью компонента Link реактивного маршрутизатора - to.

чтобы принять объект, параметры могут включать:

  • pathname: A string representing the path to link to.
  • search: A string representation of query parameters.
  • hash: A hash to put in the URL, e.g. #a-hash.
  • state: State to persist to the location

Здесь нам нужно использовать параметр состояния.В компоненте B мы можем получить значение состояния через history.location.state и сохранить его. При возврате к компоненту A состояние снова передается для достижения эффекта сохранения состояния маршрутизации.

код
const A = ({ history }) => {
  const { count: preCount = 0 } = history.location.state || {};
  const [count, setCount] = useState(preCount); // 读取回传状态
  const handleChangeStatus = () => setCount(count + 1);

  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 A 组件</h1>
        <p>当前计数:{count}</p>
        <br />
        <button onClick={handleChangeStatus}>点我 +1</button>
        <br />
        <button>
          <Link
            to={{
              pathname: "/B",
              state: {
                count,
              },
            }}
          >
            跳转到 B 组件
          </Link>
        </button>
      </header>
    </div>
  );
};

const B = ({ history }) => {
  const [prePageStatus, setPrePageStatus] = useState();

  // 保存 A 组件状态
  useEffect(() => {
    const { state } = history.location;
    state && setPrePageStatus(state);
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 B 组件</h1>
        <br />
        <button>
          <Link
            to={{
              pathname: "/",
              state: prePageStatus,
            }}
            replace
          >
            点我返回
          </Link>
        </button>
      </header>
    </div>
  );
};
преимущество
  • Просто и быстро, не загрязняет LocalStorage/SessionStorage.
  • Можно передавать специальные объекты, такие как Date, RegExp (не беспокойтесь о недостаточности JSON.stringify/parse)
недостаток
  • Если компонент A может переходить к нескольким компонентам, то пропишите одинаковую логику в каждом компоненте перехода.

Компонент не будет удален:

1. Рендеринг одной страницы

Применение

Переключаемый компонент отображается в полноэкранном режиме как дочерний компонент, а состояние страницы обычно сохраняется в родительском компоненте.

код
const PAGE = {
  A: Symbol(),
  B: Symbol(),
};

const A = () => {
  const [page, setPage] = useState(PAGE.A);
  const [count, setCount] = useState(0); // 读取回传状态
  const handleChangeStatus = () => setCount(count + 1);
  const changePage = (page) => () => {
    setPage(page);
  };

  switch (page) {
    case PAGE.A:
      return (
        <div className="App">
          <header className="App-header">
            <h1>这里是 A 组件</h1>
            <p>当前计数:{count}</p>
            <br />
            <button onClick={handleChangeStatus}>点我 +1</button>
            <br />
            <button onClick={changePage(PAGE.B)}>跳转到 B 组件</button>
          </header>
        </div>
      );
    case PAGE.B:
      return <B onChangePage={changePage(PAGE.A)} />;
    default:
      return null;
  }
};

const B = ({ onChangePage }) => {
  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 B 组件</h1>
        <br />
        <button onClick={onChangePage}>点我返回</button>
      </header>
    </div>
  );
};
преимущество
  • небольшое количество кода
  • Нет необходимости учитывать ошибки при передаче состояния
недостаток
  • Увеличение затрат на техническое обслуживание компонента А
  • Необходимо передать дополнительные реквизиты компоненту B
  • Не удалось найти страницу с маршрутизацией

2. Рендеринг одной страницы + сопоставление маршрутов

Применение

Улучшение на основе предыдущего примера с использованием маршрутизации для сопоставления компонентов.

код
const A = ({ history }) => {
  const [count, setCount] = useState(0);
  const handleChangeStatus = () => setCount(count + 1);

  const { pathname } = history.location;

  const isPage = (path) => {
    const regExp = new RegExp(`^${path}`);
    return regExp.test(pathname);
  };

  if (isPage("/B")) {
    return <B />;
  }

  if (isPage("/")) {
    return (
      <div className="App">
        <header className="App-header">
          <h1>这里是 A 组件</h1>
          <p>当前计数:{count}</p>
          <br />
          <button onClick={handleChangeStatus}>点我 +1</button>
          <br />
          <button>
            <Link to="/B">跳转到 B 组件</Link>
          </button>
        </header>
      </div>
    );
  }

  return null;
};

const B = () => {
  return (
    <div className="App">
      <header className="App-header">
        <h1>这里是 B 组件</h1>
        <br />
        <button>
          <Link to="/">点我返回</Link>
        </button>
      </header>
    </div>
  );
};
преимущество
  • Нет необходимости передавать дополнительные реквизиты компоненту B
  • Вы можете использовать маршрутизацию для поиска страниц
недостаток
  • Компоненты связаны

Суммировать

строить планы преимущество недостаток
Storage Хорошая совместимость, никаких дополнительных библиотек или инструментов не требуется. Дата не поддерживает другие специальные объекты; множество дополнительных кодов
маршрутизация по значению Поддерживает специальные объекты, не загрязняет Хранилище Еще дополнительный код
рендеринг одной страницы Меньше лишнего кода, не нужно беспокоиться об ошибках в процессе передачи данных Невозможно использовать маршрутизацию для поиска страниц; компоненты сильно связаны
Одностраничный рендеринг + совпадения маршрутов Вы можете использовать маршрутизацию для поиска страниц, уменьшения связанности компонентов (по сравнению с методами одностраничного рендеринга). Компонент имеет проблемы с соединением

В общем, лучший вариант по-прежнему — маршрут по значению или одностраничный рендеринг + сопоставление маршрутов. В основном это зависит от сложности бизнес-обработки.Если компонентам необходимо перейти на меньшее количество страниц, можно использовать схему одностраничного рендеринга + сопоставления маршрутизации. Если наоборот, то честно используйте схему передачи значений, предоставляемую react-router.