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

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

this.setState({selectedOption: 5});

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

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

【问题讨论】:

标签: javascript reactjs


【解决方案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 您加密的是数据而不是密钥。拥有密钥的人将获得加密的数据,但这没有任何意义,除非您也有密钥来解密数据
        猜你喜欢
        相关资源
        最近更新 更多
        热门标签