如何在React.js中维护页面刷新后的状态

How to maintain state after a page refresh in React.js?

本文关键字:刷新 状态 维护 React js      更新时间:2023-09-26

假设我有一个代码,为上一页上选择的选择框设置状态:

this.setState({selectedOption: 5});

有没有办法在页面刷新后用5填充this.state.selectedOption

是否有回调可以用于将其保存在localStorage中,然后执行一个大型setState,或者是否有这样的标准方法?

因此,我的解决方案是在设置状态时也设置localStorage,然后在getInitialState回调中再次从localStorage获取值,如下所示:

getInitialState: function() {
    var selectedOption = localStorage.getItem( 'SelectedOption' ) || 1;
    return {
        selectedOption: selectedOption
    };
},
setSelectedOption: function( option ) {
    localStorage.setItem( 'SelectedOption', option );
    this.setState( { selectedOption: option } );
}

我不确定这是否可以被视为反模式,但除非有更好的解决方案,否则它是有效的。

我们有一个应用程序,允许用户在页面中设置"参数"。我们所做的是使用React Router(与History结合使用)和一个库在URL上设置这些参数,该库将JavaScript对象URI编码为可以用作查询字符串的格式。

当用户选择一个选项时,我们可以使用将其值推送到当前路线上

history.push({pathname: 'path/', search: '?' + Qs.stringify(params)});

CCD_ 6可以是当前路径。在您的情况下,params看起来像:

{
  selectedOption: 5
}

然后在React树的顶层,React Router会用我们之前设置的编码值location.search来更新该组件的props,所以componentWillReceiveProps中会有一些东西,比如:

params = Qs.parse(nextProps.location.search.substring(1));
this.setState({selectedOption: params.selectedOption});

然后,该组件及其子组件将使用更新后的设置重新渲染。由于信息在URL上,它可以被添加书签(或通过电子邮件发送——这是我们的用例),刷新后会使应用程序处于相同状态。这对我们的应用程序非常有效。

React路由器:https://github.com/reactjs/react-router

历史记录:https://github.com/ReactTraining/history

查询字符串库:https://github.com/ljharb/qs

您可以像Omar Suggest一样使用本地存储来"持久化"状态,但应该在设置状态后进行。为此,您需要将回调传递给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))
  });
}

带挂钩和会话存储:

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(如果您仍然喜欢的话)。

我认为状态是只用于视图的信息,并且应该在视图状态之外持久存在的数据最好作为道具存储。当你想链接到一个页面或在应用程序深处共享URL,但又想扰乱地址栏时,URL参数很有用。

看看Redux-Persist(如果您使用的是Redux)https://github.com/rt2zz/redux-persist

带挂钩:

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 && onChange(e)
    setInputValue(e.target.value)
    sessionStorage.setItem(name, e.target.value)
  }
  return <input ref={ref} {...props} value={inputValue} onChange={handleOnChange} />
})

(打字有点复杂,但并不可怕)

这将存储值"0";"永远";,所以你必须以某种方式处理重置表单。

从localStorage加载状态(如果存在):

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);
};

我可能迟到了,但react的实际代码为react创建了应用程序>16版本。每次更改后的状态都保存在sessionStorage(而不是localStorage)中,并通过crypto-js进行加密。刷新时(当用户通过单击刷新按钮要求刷新页面时)从存储器加载状态。我还建议不要在构建中使用sourceMaps,以避免关键短语的可读性。

我的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__ && 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();