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 + Router - 我应该为所有页面/组件使用一个状态/存储吗?
REACT + REDUX:在 redux 状态更改时 mapStateToProps 更新状态,但视图未按照新状态呈现