React/Redux 存储状态未保存

Posted

技术标签:

【中文标题】React/Redux 存储状态未保存【英文标题】:React/Redux store state is not saved 【发布时间】:2016-04-21 00:16:50 【问题描述】:

我使用 thunk 中间件并将初始状态传递给 React,但问题是当我访问其他链接时,React 状态没有保存。 登录成功后,应该会渲染仪表盘。 当用户尝试访问/login 页面时,必须将用户重定向到仪表板(即根路径,/)。

我也应该使用 redux-router 吗?

我省略了一些代码,但它几乎如下所示。

init.js我将 store 传递给 Provider

import  Provider  from 'react-redux';
import configureStore from './store/configureStore';

const store = configureStore();

function requireAuth(nextState, replace) 
  const isLoggedIn = store.getState().auth.isLoggedIn;
  if (!isLoggedIn) 
    replace(
      pathname: '/login',
      state:  nextPathname: nextState.location.pathname 
    );
  


<Provider store=store>
    <Router history=browserHistory>
      <Route path='/' component=App store=store>
        <IndexRoute
          components=
            main: MainServices,
            aside: Aside
          
          onEnter=requireAuth
          />
        <Route
          path="login"
          components=
            login: Login
          
          />
         ...
    </Router>
  </Provider>

configureStore.js

import  createStore, applyMiddleware  from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
import initialState from './initialState';

const store = applyMiddleware(thunk)(createStore);

export default function () 
  return store(rootReducer, initialState);

initialState.js

var initialState = 
  auth: 
    isLoggedIn: false,
    isLoggingIn: false,
    response: null,
  ,
;

export default initialState;

App.jsx 初始 App 的状态传递给 React 的 props

import React,  PropTypes  from 'react';
import  connect  from 'react-redux';
import Dashboard from './Dashboard';
import Login from './Login';
import  browserHistory  from 'react-router';

class App extends React.Component 
  constructor(props) 
    super(props);
  

  render() 
    return (
      <div className='appWrapper height'>
        
          this.props.auth.isLoggedIn ?
          <Dashboard ...this.props /> : <Login ...this.props />
        
      </div>
    );
  



let mapStateToProps = function(appState) 
  return 
    auth: appState.auth,
  ;
;

let mapDispatchToProps = function(dispatch) 
  return 
    logoutRequest: function() 
      console.log("logoutRequest dispatched!");
    
  
;

export default connect(mapStateToProps, mapDispatchToProps)(App);

Login.jsx

export default class Login extends React.Component 
  constructor(props) 
    super(props);
    console.log(`componentWillMount in login`);
    if (this.props.auth.isLoggedIn) 
      console.log(`you already logged in..!`);
      browserHistory.push('/');
    
  
  render() 
    return (
      <div className="login-outer">
        <Grid className="login-inner">
          <Row>
            <Col xs=12>
              <LoginHeader />
              <LoginContainer />
            </Col>
          </Row>
        </Grid>
      </div>
    );
  

LoginContainer.jsx

export default class LoginContainer extends React.Component 
  render() 
    return (
      <div className="login-container">
        <div className="outer">
          <div className="inner">
            <LoginTitle />
            <div className="login-box">
              <h2>Sign in</h2>
              <LoginInput />
            </div>
          </div>
        </div>
      </div>
    );
  

LoginInput.jsx

import React from 'react';
import  connect  from 'react-redux';
import  Input, ButtonToolbar, Button  from 'react-bootstrap';
import  spring  from 'react-motion';
import Transition from 'react-motion-ui-pack';
import Loader from 'react-loader';
import * as actions from '../../actions/loginActions';

class LoginInput extends React.Component 
  constructor(props) 
    super(props);
    this.state = 
      idText: '',
      passText: '',
      idShow: false,
      passShow: false,
      loaded: true
    ;

    this.handleIdChange = this.handleIdChange.bind(this);
    this.handlePassChange = this.handlePassChange.bind(this);
    this.loginRequest = this.loginRequest.bind(this);
  

  handleIdChange(e) 
    this.setState(
      idText: e.target.value
    );

    if (e.target.value != '') 
      this.setState(
        idShow: true
      );
     else 
      this.setState(
        idShow: false
      );
    
  

  handlePassChange(e) 
    this.setState(
      passText: e.target.value
    );

    if (e.target.value != '') 
      this.setState(
        passShow: true
      );
     else 
      this.setState(
        passShow: false
      );
    
  

  loginRequest(e) 

    this.setState(loaded: false);

    if (!this.state.idText || !this.state.passText) 
      this.setState(loaded: true);
    

    if (this.state.idText && this.state.passText) 
      this.setState(
        loaded: false,
        idText: this.state.idText,
        passText: this.state.passText,
      );
      this.props.login(this.state.idText, this.state.passText);
    

    e.preventDefault();
  

   render() 
    return (
      <form className="loginForm">
        <div className="form-group input-login id">
            <input
              type="text"
              className="form-control"
              ref="idText"
              placeholder="ID"
              value=this.state.idText
              onChange=this.handleIdChange
            />
          <Transition
            component=false
            enter=
              opacity: 1
            
            leave=
              opacity: 0
            
          >
            
              this.state.idShow &&
              <label
                htmlFor=""
                className="control-label"
              >
                ID
              </label>
            
          </Transition>
        </div>
        <div className="form-group input-login password">
          <input
            type="password"
            className="form-control"
            ref="passText"
            placeholder="Password"
            value=this.state.passText
            onChange=this.handlePassChange
          />
          <Transition
            component=false
            enter=
              opacity: 1
            
            leave=
              opacity: 0
            
          >
            
              this.state.passShow &&
              <label
                htmlFor=""
                className="control-label"
              >
                Password
              </label>
            
          </Transition>
        </div>
        <Input
          type="checkbox"
          groupClassName="checkbox-login"
          label="Keep me signed in"
        />
        <ButtonToolbar>
          <Button
            href="#"
            onClick=this.loginRequest
          >
            <div
              className="sign-arrow"
              hidden=!this.state.loaded
            >
              <h6>
                ENTER
              </h6>
              <img src="images/ic-right-arrow-2.svg"  />
            </div>
            <Loader
              className="spinner"
              loaded=this.state.loaded
              lines=10
              length=3
              width=2
              radius=4
              corners=1
              rotate=0
              direction=1
              color="#fff"
              speed=1.5
              trail=60
              shadow=false
              hwaccel=false
              scale=1
            />
          </Button>
        </ButtonToolbar>
      </form>
    );
  


let mapStateToProps = function(appState) 
  return 
    auth: appState.auth,
  ;
;

let mapDispatchToProps = function(dispatch) 
  return 
          login: function(id, pwd) 
            dispatch(actions.login(id, pwd));
          
  
;

export default connect(mapStateToProps,mapDispatchToProps)(LoginInput);

loginActions.js

export function loginFailure(error) 
  return  error, type: ActionTypes.LOGIN_FAILURE ;


export function loginSuccess(response) 
  return dispatch => 
    dispatch( response, type: ActionTypes.LOGIN_SUCCESS );
    browserHistory.push('/');
  ;


export function loginRequest(id, pwd) 
  return 
    type: ActionTypes.LOGIN_REQUEST,
    command: 'login',
    lang: 'en',
    str: encodeCredentials(id, pwd),
    ip: '',
    device_id: '',
    install_ver: '',
  ;


export function login(id, pwd) 
  const credentials = loginRequest(id, pwd);

  return dispatch => 
    fetchJSON(`$API.ROOT_PATH$API.END_POINT.LOGIN`, 
      method: 'post',
      body: credentials,
    ).then(data => 
      dispatch(loginSuccess(data));
    ).catch(error => 
      console.log(`request failed $error`);
    );
  ;


reducers/index.js

import authReducer from './authReducer';
import  combineReducers  from 'redux';

const rootReducer = combineReducers(
  auth: authReducer,
);

export default rootReducer;

authReducer.js

import initialState from '../store/initialState';
import * as ActionTypes from '../actionTypes/authActionTypes';

const authReducer = function authReducer(state = initialState.auth, action) 
  switch (action.type) 
    case ActionTypes.LOGIN_REQUEST:
      return Object.assign(, state, 
        isLoggingIn: true,
        isLoggedIn: false,
      );
    case ActionTypes.LOGOUT:
      return Object.assign(, state, 
        isLoggedIn: false,
        isLoggingIn: false,
      );
    case ActionTypes.LOGIN_FAILURE:
      return Object.assign(, state, 
        error: action.error,
        isLoggingIn: false,
        isLoggedIn: false,
      );
    case ActionTypes.LOGIN_SUCCESS:
      return Object.assign(, state, 
        isLoggedIn: true,
        isLoggingIn: false,
        response: action.response,
      );
    default:
      return state;
  
;

export default authReducer;

【问题讨论】:

【参考方案1】:

无法在两个不同的 http 请求之间保存您的商店。唯一的方法不是重新加载页面本身,但是您可以模拟用户在用户单击链接时重新加载页面,方法是使用历史 API 进行操作,历史 API 是所有现代浏览器中的本机对象。但最好的方法是使用react-router,它会处理你在 react 应用程序中所需的一切,如果你想在你的 redux 商店中保持路由器的状态,你可以使用redux-simple-router。

【讨论】:

我使用redux-save-state 解决了这个问题,这个包将应用程序状态存储在LocalStorage 中。保持这种状态好吗? npmjs.com/package/redux-save-state @seoyoochan 这取决于你需要什么。从我的角度来看,您可以简单地使用 s-s-r(服务器端渲染)制作 SPA(单页应用程序)。看看这个例子 - github.com/erikras/react-redux-universal-hot-example。请注意,他们使用的是实验性的 redux-router,他们计划将示例迁移到 redux-simple-router。 由于升级了history包,redux-router出现了一些问题。我昨晚已经尝试了几个小时,它的学习曲线很陡峭。但我会尝试 redux-simple-router。然后我想我不应该将未登录的用户重定向到登录页面。相反,我改变了页面的外观和网址?这样我就不会请求另一个 http? 对,你从服务器获取必要的数据(使用你喜欢的任何 ajax 客户端,例如 superagent、isomorphic-fetch 等)然后告诉 react 路由器进行转换。它将自行更改浏览器中的 url,并使用获取的数据呈现适当的组件,而无需重新加载页面。 redux 路由器!== 反应路由器。 redux-router 和 redux-simple-router 都是基于 react-router 的。所以,无论如何你都需要 react-router 。只是为了说清楚,以防您误解了那部分。

以上是关于React/Redux 存储状态未保存的主要内容,如果未能解决你的问题,请参考以下文章

react/redux 应用程序:未调度到存储的操作

React + Redux + Router - 我应该为所有页面/组件使用一个状态/存储吗?

如何在 React Redux 中访问存储状态?

React Redux 状态在页面刷新时丢失

在 Redux thunk 中创建的数组未正确传递到状态树

REACT + REDUX:在 redux 状态更改时 mapStateToProps 更新状态,但视图未按照新状态呈现