#javascript #reactjs #context-api
Вопрос:
Допустим, у меня есть код, который задает состояние для поля выбора, выбранного на предыдущей странице:
this.setState({selectedOption: 5});
Есть ли какой-либо способ this.state.selectedOption
заполнить 5 после обновления страницы?
Есть ли обратные вызовы, которые я могу использовать, чтобы сохранить это в localStorage, а затем выполнить один большой setState
или есть стандартный способ сделать что-то подобное?
Комментарии:
Ответ №1:
Поэтому мое решение состояло в том, чтобы также установить localStorage
при настройке моего состояния, а затем localStorage
снова получить значение внутри getInitialState
обратного вызова, например так:
getInitialState: function() {
var selectedOption = localStorage.getItem( 'SelectedOption' ) || 1;
return {
selectedOption: selectedOption
};
},
setSelectedOption: function( option ) {
localStorage.setItem( 'SelectedOption', option );
this.setState( { selectedOption: option } );
}
Я не уверен, можно ли считать это Анти-шаблоном, но это работает, если нет лучшего решения.
Комментарии:
1. Единственная часть этого, которую я бы счел немного «анти-шаблоном», заключается в том, что ваш компонент сохраняет свое собственное состояние внутри localStorage. Если бы этот компонент использовался несколько раз, вам пришлось бы иметь дело с конфликтами ключей. Имо, информация, которая является такой важной (должна сохраняться в локальном хранилище), вероятно, должна быть перемещена в более высокое состояние или уровень данных в архитектуре приложений… но это самый простой способ выполнить работу до тех пор, пока не будут проработаны детали
2. @CoryDanielson, каков тогда должен быть рекомендуемый способ?
3. Если вы хотите использовать localStorage для сохранения состояния таким образом, чтобы это было безопасно для нескольких экземпляров одного и того же компонента, вы можете присвоить каждому компоненту уникальный идентификатор, а затем использовать этот идентификатор в качестве ключа в localStorage, как
${this.props.id}.SelectedOption
4. Если входные данные являются частью формы (или группы связанных входных данных), вы можете сохранить все входные данные/значения в одном объекте, а затем JSON.stringify объект и сохранить строку в локальном хранилище
5. Если вы замените
localStorage
наsessionStorage
, вы получите отдельные состояния для каждой вкладки, избегая столкновений между вкладками
Ответ №2:
Вы можете «сохранить» состояние, используя локальное хранилище, как предлагает Омар, но это следует сделать, как только состояние будет установлено. Для этого вам нужно передать setState
функцию обратного вызова, а также сериализовать и десериализовать объекты, помещенные в локальное хранилище.
constructor(props) {
super(props);
this.state = {
allProjects: JSON.parse(localStorage.getItem('allProjects')) || []
}
}
addProject = (newProject) => {
...
this.setState({
allProjects: this.state.allProjects.concat(newProject)
},() => {
localStorage.setItem('allProjects', JSON.stringify(this.state.allProjects))
});
}
Ответ №3:
У нас есть приложение, которое позволяет пользователю устанавливать «параметры» на странице. Что мы делаем, так это устанавливаем эти параметры на URL-адрес, используя маршрутизатор React (в сочетании с историей) и библиотеку, которая кодирует объекты JavaScript с помощью URI в формат, который можно использовать в качестве строки запроса.
Когда пользователь выбирает опцию, мы можем перенести ее значение на текущий маршрут с помощью:
history.push({pathname: 'path/', search: '?' Qs.stringify(params)});
pathname
может быть текущий путь. В вашем случае params
это выглядело бы примерно так:
{
selectedOption: 5
}
Затем на верхнем уровне дерева React маршрутизатор React обновит props
этот компонент с помощью реквизита location.search
, кодированное значение которого мы установили ранее, так что будет что-то componentWillReceiveProps
вроде:
params = Qs.parse(nextProps.location.search.substring(1));
this.setState({selectedOption: params.selectedOption});
Затем этот компонент и его дочерние элементы будут повторно отрисованы с обновленными настройками. Поскольку информация указана на URL — адресе, ее можно добавить в закладки (или отправить по электронной почте-это был наш вариант использования), и обновление оставит приложение в том же состоянии.
Это действительно хорошо работает для нашего приложения.
Маршрутизатор реакции: https://github.com/reactjs/react-router
История: https://github.com/ReactTraining/history
Библиотека строк запроса: https://github.com/ljharb/qs
Комментарии:
1. Это должно быть приемлемым решением, а не сохранением в локальном хранилище, что не всегда возможно и замедляет работу приложения. Я попытаюсь применить это, спасибо.
2. Может ли это работать для больших массивов?
Ответ №4:
Я считаю, что состояние предназначено только для просмотра информации, а данные, которые должны сохраняться за пределами состояния представления, лучше хранить в качестве реквизитов. Параметры URL-адресов полезны, когда вы хотите иметь возможность ссылаться на страницу или делиться URL-адресом в приложении, но в противном случае загромождаете адресную строку.
Взгляните на Redux-Сохраняйтесь (если вы используете redux) https://github.com/rt2zz/redux-persist
Ответ №5:
Состояние загрузки из локального хранилища, если оно существует:
constructor(props) {
super(props);
this.state = JSON.parse(localStorage.getItem('state'))
? JSON.parse(localStorage.getItem('state'))
: initialState
переопределение this.setState
для автоматического сохранения состояния после каждого обновления :
const orginial = this.setState;
this.setState = function() {
let arguments0 = arguments[0];
let arguments1 = () => (arguments[1], localStorage.setItem('state', JSON.stringify(this.state)));
orginial.bind(this)(arguments0, arguments1);
};
Ответ №6:
С крючками и хранилищем сеансов:
const [count, setCount] = useState(1);
useEffect(() => {
setCount(JSON.parse(window.sessionStorage.getItem("count")));
}, []);
useEffect(() => {
window.sessionStorage.setItem("count", count);
}, [count]);
return (
<div className="App">
<h1>Count: {count}</h1>
<button onClick={() => setCount(count 1)}> </button>
</div>
);
Замените sessionStorage на localStorage, если это то, что вы все еще предпочитаете.
Комментарии:
1. Почему два эффекта использования?
2. Привет @Quimbo, кто может вспомнить… Потому что (код внутри) первого должен запускаться при каждом рендеринге, включая обновление страницы, в то время как второй должен запускаться только при
count
изменении
Ответ №7:
С крючками:
const MyComponent = () => {
const [selectedOption, setSelectedOption] = useState(1)
useEffect(() => {
const storedSelectedOption = parseInt(sessionStorage.getItem('selectedOption') || '1')
setSelectedOption(storedSelectedOption)
}, [])
const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedOption(parseInt(e.target.value))
sessionStorage.setItem('selectedOption', e.target.value)
}
return (
<select onChange={handleOnChange}>
<option value="5" selected={selectedOption === 5}>Five</option>
<option value="3" selected={selectedOption === 3}>Three</option>
</select>
)
}
По-видимому, это тоже работает:
const MyComponent = () => {
const [selectedOption, setSelectedOption] = useState<number>(() => {
return parseInt(sessionStorage.getItem('selectedOption') || '1')
})
const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedOption(parseInt(e.target.value))
sessionStorage.setItem('selectedOption', e.target.value)
}
return (
<select onChange={handleOnChange}>
<option value="5" selected={selectedOption === 5}>Five</option>
<option value="3" selected={selectedOption === 3}>Three</option>
</select>
)
}
В современных браузерах:
return (
<select onChange={handleOnChange} value={selectedOption}>
<option value="5">Five</option>
<option value="3">Three</option>
</select>
)
Для ввода что-то вроде этого должно работать:
(В ответ на вопрос в комментарии от @TanviAgarwal)
const MyInput = React.forwardRef((props, ref) => {
const { name, value, onChange } = props
const [inputValue, setInputValue] = React.useState(() => {
return sessionStorage.getItem(name) || value || ''
})
const handleOnChange = (e) => {
onChange amp;amp; onChange(e)
setInputValue(e.target.value)
sessionStorage.setItem(name, e.target.value)
}
return <input ref={ref} {...props} value={inputValue} onChange={handleOnChange} />
})
(С машинописным текстом это немного сложнее, но не так ужасно)
Это сохранит значение «навсегда», поэтому вам придется как-то обрабатывать сброс формы.
Комментарии:
1. Для массива объектов существуют поля ввода с параметрами редактирования, как это можно сделать возможным? Я пробую ваш код, но после обновления значения он уходит. @tofsjonas
2. Я не уверен, что понимаю, что вы имеете в виду. Не могли бы вы привести пример кода?
3. const cols = [ { название: «Имя», поле: «имя», }, { название: «Класс», поле: «класс», } ]; таблица const= [ {имя: «Танви», класс: «старшая школа»}]; это два массива, которые я использую. Я пытаюсь создать список с полями ввода, которые можно редактировать, но после обновления данные удаляются
4. {cols.карта((e, индекс) => ><div><div><Кнопка ListItem><Кнопка ListItem><Первичный текст ListItemText={e.заголовок} /> <Первичный текст ListItemText={e.заголовок} /><div><div><ListItemSecondaryAction><ListItemSecondaryAction><Заполнитель ввода=»Введите» вариант=»в общих чертах» размер=»маленький» onChange={handleAdd} значение={Оценка редактирования} /><Заполнитель ввода=»Введите» вариант=»очерченный» размер=»маленький» onChange={handleAdd} значение={Оценка редактирования} /><Кнопка очерчена=»истинный» вариант=»содержащийся» цвет=»основной» onClick={() => Сохранить(поле)}<Кнопка очерчена=»истинный» вариант=»содержащийся» цвет=»основной» onClick= {() = > Сохранить<Кнопка очерчена=»истинный» вариант= «содержащийся» цвет= «основной» onClick= {() = > ></Кнопка ></Кнопка></ListItemSecondaryAction></ListItemSecondaryAction></div></div></ListItem>)}
5. @TanviAgarwal Я внес правку, код в комментариях не так читаем..
Ответ №8:
Я могу опоздать, но фактический код для react-create-app для react > 16 версий. После каждого изменения состояние сохраняется в хранилище сеансов (не локальном хранилище) и шифруется с помощью crypto-js. При обновлении (когда пользователь требует обновления страницы, нажав кнопку обновить) состояние загружается из хранилища. Я также рекомендую не использовать исходные карты в сборке, чтобы избежать удобочитаемости ключевых фраз.
мой index.js
import React from "react";
import ReactDOM from "react-dom";
import './index.css';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';
import {createStore} from "redux";
import {Provider} from "react-redux"
import {BrowserRouter} from "react-router-dom";
import rootReducer from "./reducers/rootReducer";
import CryptoJS from 'crypto-js';
const key = CryptoJS.enc.Utf8.parse("someRandomText_encryptionPhase");
const iv = CryptoJS.enc.Utf8.parse("someRandomIV");
const persistedState = loadFromSessionStorage();
let store = createStore(rootReducer, persistedState,
window.__REDUX_DEVTOOLS_EXTENSION__ amp;amp; window.__REDUX_DEVTOOLS_EXTENSION__());
function loadFromSessionStorage() {
try {
const serializedState = sessionStorage.getItem('state');
if (serializedState === null) {
return undefined;
}
const decrypted = CryptoJS.AES.decrypt(serializedState, key, {iv: iv}).toString(CryptoJS.enc.Utf8);
return JSON.parse(decrypted);
} catch {
return undefined;
}
}
function saveToSessionStorage(state) {
try {
const serializedState = JSON.stringify(state);
const encrypted = CryptoJS.AES.encrypt(serializedState, key, {iv: iv});
sessionStorage.setItem('state', encrypted)
} catch (e) {
console.log(e)
}
}
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>,
document.getElementById('root')
);
store.subscribe(() => saveToSessionStorage(store.getState()));
serviceWorker.unregister();
Комментарии:
1. в чем смысл шифрования, поскольку ключ должен быть на интерфейсе, поэтому его можно найти с помощью инструментов инспектора в браузере?
2. @MehdiSaffar Вы шифруете данные, а не ключ. Кто-то с ключом получит зашифрованные данные, но это ничего не значит, если у вас также нет секретного ключа для расшифровки данных