如何在 React.js 中刷新页面后保持状态?

Posted

技术标签:

【中文标题】如何在 React.js 中刷新页面后保持状态?【英文标题】:How to maintain state after a page refresh in React.js? 【发布时间】:2015-04-03 13:28:05 【问题描述】:

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

this.setState(selectedOption: 5);

有什么方法可以在页面刷新后将this.state.selectedOption 填充为 5?

我可以使用回调将其保存在 localStorage 中,然后执行一个大的 setState 还是有一种标准的方法来做这样的事情?

【问题讨论】:

gaearon.github.io/react-hot-loader 【参考方案1】:

所以我的解决方案是在设置状态时也设置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  );

我不确定这是否可以被视为Anti-Pattern,但除非有更好的解决方案,否则它会起作用。

【讨论】:

唯一我认为有点“反模式”的部分是你的组件在 localStorage 中保持它自己的状态。如果此组件被多次使用,那么您将不得不处理键冲突。 Imo,如此重要的信息(需要持久保存到 localStorage)可能应该向上移动到应用程序架构中的更高状态或数据层......但这是完成工作的最简单方法,直到细节工作出 @CoryDanielson 那么推荐的方法是什么? 如果您想使用 localStorage 以一种跨同一组件的多个实例安全的方式保存状态,您可以为每个组件指定一个唯一 ID,然后使用该 ID 作为localStorage - 比如$this.props.id.SelectedOption 如果输入是表单(或相关输入组)的一部分,您可以将所有输入/值保存到单个对象,然后 JSON.stringify 对象并将字符串存储在本地存储 如果您将localStorage 替换为sessionStorage,您会为每个选项卡获得单独的状态,从而避免选项卡之间的冲突【参考方案2】:

您可以按照 Omar 的建议使用本地存储“持久化”状态,但是一旦设置了状态就应该这样做。为此,您需要将回调传递给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 Router(结合 History)和一个库,该库将 javascript 对象 URI 编码为可用作查询字符串的格式。

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

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

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


  selectedOption: 5

然后在 React 树的顶层,React Router 将使用 location.search 的 prop 更新该组件的 props,这是我们之前设置的编码值,因此在 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

【讨论】:

这应该是公认的解决方案,而不是保存到本地存储,这并不总是可行的并且会减慢应用程序的速度。我要试试这个,谢谢。 这可以用于大型阵列吗?【参考方案4】:

我认为 state 仅用于查看信息,并且应该在 view state 之外持续存在的数据最好存储为 props。当您希望能够链接到某个页面或将 URL 共享到应用程序的深处,但否则会使地址栏变得混乱时,URL 参数很有用。

看看 Redux-Persist(如果你正在使用 redux)https://github.com/rt2zz/redux-persist

【讨论】:

【参考方案5】:

如果存在,则从 localStorage 加载 state

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】:

使用 Hooks 和 sessionStorage:

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。

【讨论】:

为什么有两个useEffect? 嗨@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>
)

对于输入 something 这样应该可以工作:

(回复@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 />
)

(使用 typescript 会稍微复杂一些,但并不可怕)

这将“永远”存储值,因此您必须以某种方式处理重置表单。

【讨论】:

对于一个对象数组,有带有编辑选项的输入字段如何实现?我正在尝试您的代码,但在刷新值后它消失了。 @tofsjonas 我不确定我理解你的意思。你能提供一个代码示例吗? const cols = [ title: "Name", field: "name", , title: "Class", field: "class", ]; const table= [ name:'Tanvi', class:'high-school' ];这些是我正在使用的两个数组。我正在尝试使用可编辑的输入字段创建一个列表,但在刷新数据后消失 cols.map((e, index) => ) @TanviAgarwal 我进行了编辑,cmets 中的代码不那么可读..【参考方案8】:

我可能迟到了,但 react-create-app 的实际代码用于 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();

【讨论】:

加密的意义何在,因为密钥必须在前端,因此可以使用浏览器中的检查器工具找到? @MehdiSaffar 您加密的是数据而不是密钥。拥有密钥的人将获得加密的数据,但这没有任何意义,除非您也有密钥来解密数据

以上是关于如何在 React.js 中刷新页面后保持状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何在使用 jquery 刷新页面后保持复选框在模式中的选中状态?

刷新后保持折叠状态

服务器端 Blazor:刷新页面后如何保持用户身份验证?

react-router 如何在页面刷新时保持位置状态?

php - 如何在刷新后保持图像按钮状态

Vue项目保持用户登录状态(localStorage + vuex 刷新页面后状态依然保持)