【问题标题】:React/Redux store state is not savedReact/Redux 存储状态未保存
【发布时间】: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" alt="" />
            </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;

【问题讨论】:

    标签: javascript reactjs redux redux-thunk


    【解决方案1】:

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

    【讨论】:

    • 我使用redux-save-state 解决了这个问题,这个包将应用程序状态存储在LocalStorage 中。保持这种状态好吗? npmjs.com/package/redux-save-state
    • @seoyoochan 这取决于你需要什么。从我的角度来看,您可以简单地使用 SSR(服务器端渲染)制作 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 。只是为了说清楚,以防您误解了那部分。
    猜你喜欢
    • 2020-09-15
    • 2020-05-13
    • 1970-01-01
    • 2018-01-21
    • 2016-08-15
    • 2017-09-22
    • 2022-10-20
    • 1970-01-01
    • 2018-07-14
    相关资源
    最近更新 更多