关于 reactjs:React 路由器私有路由未从状态获取道具(例如身份验证状态)

React router private route not getting props from state (e.g. authentication state)

我正在尝试实现一个私有路由组件来重定向未通过身份验证的用户。问题是当我渲染像 <PrivateRoute authenticated={this.state.isAuthenticated} path='/private' component={Panel} currentUser={this.state.currentUser} 这样的组件时,私有路由会将经过身份验证的用户重定向到登录页面,而不是转到面板。

App.js 中,我渲染了所有的路由,包括 <PrivateRoute/>,我在 ComponentDidMount() 中设置了 currentUserisAuthenticated 状态变量,但我无法将它们传递给 PrivateRoute

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//imports...
class App extends Component {

    state = {
        currentUser: null,
        isAuthenticated: false,
        isLoading: false
    }

    loadCurrentUser = () => {
    this.setState({
      isLoading: true
        });
        // imported method
        getCurrentUser()
            .then(response => {
                this.setState({
                    currentUser: response,
                    isAuthenticated: true,
                    isLoading: false
                });
            })
            .catch(error => {
                console.log(error)
                this.setState({
                    isLoading: false
                });  
            });
  }

  componentDidMount() {
        this.loadCurrentUser();
  }

  handleLogin = () => {
        this.loadCurrentUser();
        this.props.history.push("/");
    }

    render () {
        return (
            <React.Fragment>
                <Navigation
                    currentUser={this.state.currentUser}
                    isAuthenticated={this.state.isAuthenticated}
                    handleLogout={this.handleLogout} />
                <Switch>
                    <PrivateRoute
                        authenticated={this.state.isAuthenticated}
                        exact
                        path='/postulante'
                        component={Panel}
                        currentUser={this.state.currentUser} />
                    <Route
                        exact
                        path='/'
                        render={
                            (props) => <Landing {...props} />
                        } />

                    <Route
                        path="/login"
                        exact
                        render={
                            (props) =>  <Login onLogin={this.handleLogin} {...props} />
                        } />
                </Switch>
            </React.Fragment>
        );
    }
}

export default withRouter(App);

请注意,<Navigation /> 组件确实获得了正确的状态变量。

PrivateRoute.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//imports...
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
    <Route
      {...rest}
      render={props =>
        authenticated ? (
          <Component {...rest} {...props} />
        ) : (
          <Redirect
            to={{
              pathname: '/login',
              state: { from: props.location }
            }}
          />
        )
      }
    />
);
export default PrivateRoute


问题与 PrivateRoute 组件在第一次渲染时没有任何来自主 App 组件的更新道具有关。

如果您直接导航到 PrivateRoute 路径而不先进入任何其他路由,您将被重定向回 /login。您的 PrivateRoute 尝试在父 App\\ 的 componentDidMount() 逻辑完成之前呈现。所以 isAuthenticated 被传递为假。

如果您从 HomeLogin 开始,然后使用 Link 转到 PrivateRoute,则会发生相反的情况。

最终,这就是为什么人们使用像 redux 这样的状态管理工具来让经过身份验证的状态在全球范围内共享,而不是通过父组件传递。

虽然有一个解决方法!

参考沙箱:https://codesandbox.io/s/intelligent-dan-uskcy

  • 我们可以通过使用额外的状态值来解决这个问题
    App 组件是否曾被初始化。我们称之为
    wasInitialized
  • PrivateRoute 将接收它作为称为 wasInitialized 的道具,如果
    我们直接进入它的组件路径,在 App 有机会完成它的 componentDidMount() 逻辑之前,wasInitialized 将为 false。
  • 如果 wasInitialized 是假的,我们不会重定向到 /login,
    相反,我们将只显示一个空字符串,给父 App\\'s
    componentDidMount() 时间来执行和更新
    isAuthenticated 值。
  • 现在让我们看一下这一行:

    <Route {...rest} render={props => auth === true ? <Component
    {...props} /> : !wasInitialized ? "" : <Redirect to="/login" />
    }

    在下一次重新渲染中,isAuthenticated 将是 true 或
    错误的。如果用户是Authenticated,我们渲染预期的组件。如果用户未通过身份验证,我们进行下一个检查。现在 wasInitialized 的值为 true,因此 check 的计算结果为 false。因此,由于两个检查都没有通过,我们重定向到 /login.

  • 应用程序.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    class App extends Component {

    state = {
        currentUser: null,
        isAuthenticated: false,
        isLoading: false,
        wasInitialized: false
    }

    loadCurrentUser = () => {
    this.setState({
      isLoading: true
        });
        // imported method
        getCurrentUser()
            .then(response => {
                this.setState({
                    currentUser: response,
                    isAuthenticated: true,
                    wasInitialized: true,
                    isLoading: false
                });
            })
            .catch(error => {
                console.log(error)
                this.setState({
                    isLoading: false,
                    wasInitialized: true
                });  
            });
      }

      componentDidMount() {
            this.loadCurrentUser();
      }

      handleLogin = () => {
            this.loadCurrentUser();
            this.props.history.push("/");
        }
    render () {
        return (
            <React.Fragment>
                <Navigation
                    currentUser={this.state.currentUser}
                    isAuthenticated={this.state.isAuthenticated}
                    handleLogout={this.handleLogout} />
                <Switch>
                    <PrivateRoute
                        authenticated={this.state.isAuthenticated}
                        path='/postulante'
                        component={Panel}
                        wasInitialized={this.state.wasInitialized}
                        currentUser={this.state.currentUser} />
                    <Route
                        exact
                        path='/'
                        render={
                            (props) => <Landing {...props} />
                        } />

                    <Route
                        path="/login"
                        exact
                        render={
                            (props) =>  <Login onLogin={this.handleLogin} {...props} />
                        } />
                </Switch>
            </React.Fragment>
        );
    }
    }

    export default withRouter(App);

    私人的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import React from"react";
    import { Route, Redirect } from"react-router-dom";

    const PrivateRoute = ({
      component: Component,
      auth,
      wasInitialized,
      ...rest
    }) => {
      return (
        <Route
          {...rest}
          render={props =>
            auth === true ? (
              <Component {...props} />
            ) : !wasInitialized ? (
             ""
            ) : (
              <Redirect to="/login" />
            )
          }
        />
      );
    };

    export default PrivateRoute;