受保护的路由 React Router 4 无法使用存储在 Redux 中的身份验证状态
Protected Routes React Router 4 not working with auth state stored in Redux
我正在尝试按照此 在 React Router v4 中创建经过身份验证的路由。显示后代代码:
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => (!!authed)
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
我的身份验证状态 (authed
) 在 reducer 中被初始化为一个空对象,它是从 Redux 存储派生的。这就是我的 App.js 的样子:
class App extends Component {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
return (
<Router>
<div>
<PrivateRoute authed={this.props.authed} path='/dashboard' component={Dashboard} />
/>
</div>
</Router>
);
}
}
问题是 authed
状态开始时未定义,然后在安装路由器组件后,它会将状态更新为 true
。然而,这有点晚了,因为用户已经被重定向回登录页面。我还尝试用 componentWillMount()
替换 componentDidMount()
生命周期方法,但这也没有解决问题。
您会建议哪些策略?
更新 1:我解决这个问题的唯一方法是在返回 <Route />
组件之前测试 authed
状态,例如:
render() {
if (!!this.props.authed) {
return (
<Router>
<div>
...
更新 2:我正在使用 Redux Thunk 中间件来分派操作。状态作为 props 正确传递——我在 PrivateRoute
组件中使用 console.log()
方法来验证状态是否正确变化。问题当然是它变异晚了,而且 Route 已经在重定向用户了。
正在粘贴 reducer 和 action 的代码...
操作:
export const fetchUser = () => async dispatch => {
dispatch({ type: FETCHING_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
} catch (err) {
// dispatch error action types
}
};
减速器:
const initialState = {
authed: {},
isFetching: false
};
...
case FETCH_USER: // user authenticated
return { ...state, isFetching: false, authed: action.payload };
如果您的 App
组件连接到 redux 存储,您可能是指 this.props.authed
而不是 this.state.authed
My authentication state (authed), which is initialized as an empty
object at the reducer, is derived from a Redux store
所以您是在此处将空对象与 true
进行比较:(props) => authed === true
?为什么不用 false
初始化它?
您确定 this.props.fetchUser
操作正在将状态切换到 true
吗?
也许你最好也 post 你的 action 和 reducer 文件
理论上,您需要从 NODE API 调用中获得您现在未获得的承诺。您需要进行架构更改。我建议您使用 redux-promise-middleware this is a redux middleware. I have a sample project in my github 帐户。如果您对 this.props.fetchUser() 的调用完成与否,您将在哪里收到通知,基于使用 Promise 您可以处理您遇到的这个异步问题。如果需要帮助,请转到那个 repo。
我遇到了同样的问题,根据我的理解,您的更新 #1 应该是答案。但是,经过进一步调查,我认为这是一个架构问题。您的私人路线的当前实施取决于同步信息。
如果我们从务实的角度考虑,ProtectedRoute
本质上 returns 要么是重定向,要么是基于我们应用程序状态的组件。我们可以将所有路由包装在一个组件中,然后从商店中提取我们的信息,而不是用一个组件包装每个 Route
。
是的,每个受保护的路由需要编写更多代码,您需要测试这是否是一个可行的解决方案。
编辑:忘记提及这是架构问题的另一个重要原因是,如果用户刷新受保护的页面,他们将被重定向。
更新
更好的解决方案:
刷新时,如果他们通过身份验证,它将重定向到他们的目标 uri
https://tylermcginnis.com/react-router-protected-routes-authentication/
解决方案 1
//You can make this a method instead, that way we don't need to pass authed as an argument
function Protected(authed, Component, props) {
return !!authed
? <Component {...props} />
: <Redirect to='/login' />
}
class AppRouter extends React.PureComponent {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
let authed = this.props.authed
return (
<Router>
<Route path='/protected' render={(props) => Protected(authed, Component, props)} />
</Router>
)
}
}
class App extends Component {
render() {
return (
<Provider store={store}>
<AppRouter />
</Provider>
)
}
}
解决方案 2
或者我们可以只检查每个组件(是的,这很痛苦)
class Component extends React.Component {
render() {
return (
!!this.props.authed
? <div>...</div>
: <Redirect to='/' />
)
}
}
同样的问题发生在我身上,我正在使用一个临时的 hack 来解决它,方法是在 localStorage
中存储一个加密值,然后在我的 PrivateRoute
组件中解密它并检查该值是否比赛。
action.js
localStorage.setItem('isAuthenticated', encryptedText);
PrivateRoute.js
if (localStorage.getItem('isAuthenticated')) {
const isAuth = decryptedText === my_value;
return (
<Route
{...rest}
render={(props) =>
isAuth ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
} else {
return <Redirect to="/login" />;
}
由于 localStorage
速度更快,因此不会进行不必要的重定向。如果有人删除 localStorage
,他们将被重定向到 /login
注意:临时解决
我正在尝试按照此
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => (!!authed)
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
我的身份验证状态 (authed
) 在 reducer 中被初始化为一个空对象,它是从 Redux 存储派生的。这就是我的 App.js 的样子:
class App extends Component {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
return (
<Router>
<div>
<PrivateRoute authed={this.props.authed} path='/dashboard' component={Dashboard} />
/>
</div>
</Router>
);
}
}
问题是 authed
状态开始时未定义,然后在安装路由器组件后,它会将状态更新为 true
。然而,这有点晚了,因为用户已经被重定向回登录页面。我还尝试用 componentWillMount()
替换 componentDidMount()
生命周期方法,但这也没有解决问题。
您会建议哪些策略?
更新 1:我解决这个问题的唯一方法是在返回 <Route />
组件之前测试 authed
状态,例如:
render() {
if (!!this.props.authed) {
return (
<Router>
<div>
...
更新 2:我正在使用 Redux Thunk 中间件来分派操作。状态作为 props 正确传递——我在 PrivateRoute
组件中使用 console.log()
方法来验证状态是否正确变化。问题当然是它变异晚了,而且 Route 已经在重定向用户了。
正在粘贴 reducer 和 action 的代码...
操作:
export const fetchUser = () => async dispatch => {
dispatch({ type: FETCHING_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
} catch (err) {
// dispatch error action types
}
};
减速器:
const initialState = {
authed: {},
isFetching: false
};
...
case FETCH_USER: // user authenticated
return { ...state, isFetching: false, authed: action.payload };
如果您的 App
组件连接到 redux 存储,您可能是指 this.props.authed
而不是 this.state.authed
My authentication state (authed), which is initialized as an empty object at the reducer, is derived from a Redux store
所以您是在此处将空对象与 true
进行比较:(props) => authed === true
?为什么不用 false
初始化它?
您确定 this.props.fetchUser
操作正在将状态切换到 true
吗?
也许你最好也 post 你的 action 和 reducer 文件
理论上,您需要从 NODE API 调用中获得您现在未获得的承诺。您需要进行架构更改。我建议您使用 redux-promise-middleware this is a redux middleware. I have a sample project in my github 帐户。如果您对 this.props.fetchUser() 的调用完成与否,您将在哪里收到通知,基于使用 Promise 您可以处理您遇到的这个异步问题。如果需要帮助,请转到那个 repo。
我遇到了同样的问题,根据我的理解,您的更新 #1 应该是答案。但是,经过进一步调查,我认为这是一个架构问题。您的私人路线的当前实施取决于同步信息。
如果我们从务实的角度考虑,ProtectedRoute
本质上 returns 要么是重定向,要么是基于我们应用程序状态的组件。我们可以将所有路由包装在一个组件中,然后从商店中提取我们的信息,而不是用一个组件包装每个 Route
。
是的,每个受保护的路由需要编写更多代码,您需要测试这是否是一个可行的解决方案。
编辑:忘记提及这是架构问题的另一个重要原因是,如果用户刷新受保护的页面,他们将被重定向。
更新 更好的解决方案: 刷新时,如果他们通过身份验证,它将重定向到他们的目标 uri https://tylermcginnis.com/react-router-protected-routes-authentication/
解决方案 1
//You can make this a method instead, that way we don't need to pass authed as an argument
function Protected(authed, Component, props) {
return !!authed
? <Component {...props} />
: <Redirect to='/login' />
}
class AppRouter extends React.PureComponent {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
let authed = this.props.authed
return (
<Router>
<Route path='/protected' render={(props) => Protected(authed, Component, props)} />
</Router>
)
}
}
class App extends Component {
render() {
return (
<Provider store={store}>
<AppRouter />
</Provider>
)
}
}
解决方案 2 或者我们可以只检查每个组件(是的,这很痛苦)
class Component extends React.Component {
render() {
return (
!!this.props.authed
? <div>...</div>
: <Redirect to='/' />
)
}
}
同样的问题发生在我身上,我正在使用一个临时的 hack 来解决它,方法是在 localStorage
中存储一个加密值,然后在我的 PrivateRoute
组件中解密它并检查该值是否比赛。
action.js
localStorage.setItem('isAuthenticated', encryptedText);
PrivateRoute.js
if (localStorage.getItem('isAuthenticated')) {
const isAuth = decryptedText === my_value;
return (
<Route
{...rest}
render={(props) =>
isAuth ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
} else {
return <Redirect to="/login" />;
}
由于 localStorage
速度更快,因此不会进行不必要的重定向。如果有人删除 localStorage
,他们将被重定向到 /login
注意:临时解决