«Сохранение состояния после перехода на страницу маршрутизации» относится к ситуации, которая часто встречается в процессе разработки бизнес-требований — после того, как страница (обычно страница формы) выполняет жестокую операцию, щелкните элемент. Для получения подробной информации перейдите на другую страницу, а затем вернуться (нажмите кнопку «Назад»), предыдущая страница также может сохранить исходное состояние (страница, условия поиска и т. д.).
Зачем сохранять состояние страницы
В основном для пользовательского опыта. Представьте время, когда вы так усердно работали, чтобы потерть десятки страниц, чтобы найти нужные данные, но небрежная рука поскользнулась в другую одну точку данных, а затем вернулась к давно забытому, как большая часть страницы, такой опыт будет Большая вероятность, которая позволяет пользователям сердцем ад.
пример кода
Для удобства демонстрации исходный компонент здесь называется «Компонент А», а компонент, через который выполняется переход, называется «Компонент Б».
Подготовьте здесь небольшой проект и используйте react-router для имитации прыжков компонентов маршрутизации.
демо
код
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, давайте восстановим его~
Способы сохранить состояние страницы
Поскольку необходимо поддерживать состояние страницы (на самом деле, состояние компонента), произойдет следующие две ситуации:
- Прежний компонент будет удален
- Прежний компонент не будет удален
Затем мы можем получить следующие методы в соответствии с этими двумя ситуациями:
Компонент будет удален:
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>
);
};
демо
преимущество
- Совместимость хорошая, никаких дополнительных библиотек или инструментов не требуется.
- Простой и быстрый, он может удовлетворить большинство потребностей.
недостаток
- Состояние сохраняется методом 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 thelocation
Здесь нам нужно использовать параметр состояния.В компоненте 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.