【问题标题】:Authenticate async with react-router-v4使用 react-router-v4 验证异步
【发布时间】:2018-02-20 02:00:26
【问题描述】:

我有这个PrivateRoute 组件(来自文档):

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    isAuthenticated ? (
      <Component {...props}/>
    ) : (
      <Redirect to={{
        pathname: '/login',
        state: { from: props.location }
      }}/>
    )
  )}/>
)

我想将 isAuthenticated 更改为 aysnc 请求 isAuthenticated()。但是,在响应返回之前页面重定向。

为了澄清,isAuthenticated 函数已经设置好了。

如何在决定显示什么之前等待异步调用完成?

【问题讨论】:

    标签: reactjs react-router react-router-v4


    【解决方案1】:

    如果有人对使用钩子而不是类组件的@CraigMyles 实现感兴趣:

    export const PrivateRoute = (props) => {
        const [loading, setLoading] = useState(true);
        const [isAuthenticated, setIsAuthenticated] = useState(false);
    
        const { component: Component, ...rest } = props;
    
        useEffect(() => {
            const fetchData = async () => {
                const result = await asynCall();
    
                setIsAuthenticated(result);
                setLoading(false);
            };
            fetchData();
        }, []);
    
        return (
            <Route
                {...rest}
                render={() =>
                    isAuthenticated ? (
                        <Component {...props} />
                    ) : loading ? (
                        <div>LOADING...</div>
                    ) : (
                        <Redirect
                            to={{
                                pathname: "/login",
                                state: { from: props.location },
                            }}
                        />
                    )
                }
            />
        );
    };
    
    
    

    调用时效果很好:

    <PrivateRoute path="/routeA" component={ComponentA} />
    <PrivateRoute path="/routeB" component={ComponentB} />
    

    【讨论】:

    • 我相信您可以通过将[setIsAuthenticated] 作为useEffect() 中的第二个参数传递来防止不必要的异步调用。
    • 为什么需要一个状态来跟踪加载?是否可以修改三元语句以仅考虑身份验证状态变量?
    • 完全忘记了第二个参数,这是必须的!由于需要单独的“加载”,因为“isAuthenticated”,我想你可以开始最后一个 undefined 并从那里开始,这是一种有效的方法!感谢您的提示
    • 这个方法只是渲染一个加载界面,但是在异步调用结束后它并没有更新,它只是停留在加载界面,我该怎么办?
    【解决方案2】:

    @pizza-r0b 的解决方案非常适合我。但是,我必须稍微修改解决方案,以防止加载 div 被多次显示(对于应用程序中定义的每个 PrivateRoute 一次),方法是将加载 div 呈现在内部 - 而不是外部 - Route(类似于React Router's auth example):

    class PrivateRoute extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          loading: true,
          isAuthenticated: false
        }
      }
    
      componentDidMount() {
        asyncCall().then((isAuthenticated) => {
          this.setState({
            loading: false,
            isAuthenticated
          })
        })
      }
    
      render() {
        const { component: Component, ...rest } = this.props
        return (
          <Route
            {...rest}
            render={props =>
              this.state.isAuthenticated ? (
                <Component {...props} />
              ) : (
                  this.state.loading ? (
                    <div>LOADING</div>
                  ) : (
                      <Redirect to={{ pathname: '/login', state: { from: this.props.location } }} />
                    )
                )
            }
          />
        )
      }
    }
    

    为了完整起见,从我的 App.js 中摘录:

    <DashboardLayout>
      <PrivateRoute exact path="/status" component={Status} />
      <PrivateRoute exact path="/account" component={Account} />
    </DashboardLayout>
    

    【讨论】:

    • 嘿克雷格,这确实有效,但你能告诉我/login 将被路由到哪里吗?我的意思是哪个组件?它应该是某个地方的“登录”组件,但如果我在 App.js 中定义 /login 就像 &lt;PrivateRoute exact path="/login" component={Login} /&gt; 一样,那将是一个无限循环。
    • @Annjawn 您不会将/login 路由定义为PrivateRoute,而是普通的Route。登录页面不是私密的。
    【解决方案3】:

    如果您不使用 Redux 或任何其他类型的状态管理模式,您可以使用 Redirect 组件和组件状态来确定页面是否应该呈现。这将包括将状态设置为加载状态,进行异步调用,在请求完成后保存用户,或者缺少用户来声明和呈现 Redirect 组件,如果不满足条件,组件将重定向。

    class PrivateRoute extends React.Component {
      state = {
        loading: true,
        isAuthenticated: false,
      }
      componentDidMount() {
        asyncCall().then((isAuthenticated) => {
          this.setState({
            loading: false,
            isAuthenticated,
          });
        });
      }
      render() {
        const { component: Component, ...rest } = this.props;
        if (this.state.loading) {
          return <div>LOADING</div>;
        } else {
          return (
            <Route {...rest} render={props => (
              <div>
                {!this.state.isAuthenticated && <Redirect to={{ pathname: '/login', state: { from: this.props.location } }} />}
                <Component {...this.props} />
              </div>
              )}
            />
          )
        }
      }
    }
    

    【讨论】:

    • state = … 在类声明中对我不起作用。我不得不使用构造函数来设置初始状态。
    • 您需要配置 babel 以使用类属性转换,或使用 babel 预设阶段 0 - 2 以使其工作@raddevon
    • @pizza-r0b 你能告诉我 index.js 路由器设置对你的代码有什么要求吗?
    • @J.Pedro 本质上客户端 JS 是不安全的。要将 isAuthenticated 变量更改为 true 需要的不仅仅是打开开发控制台——您可能必须筛选已编译、缩小的代码,然后代理一个新的 JS 文件。如果有人决心这样做,他们将能够看到一些isAuthenticated UI,但是后端负责显示所有经过身份验证的数据并验证用户是否经过身份验证并允许查看数据。因此,即使有人看到经过身份验证的 UI,也需要由后端来保护敏感数据。
    • @pizza-r0b componentDidMount() 方法只调用一次。它仅在用户单击第一个链接时调用。它不满足条件,即当用户访问私有路由时,componentDidMount 或包含 promise 的方法应该调用。我怎样才能实现它?
    猜你喜欢
    • 1970-01-01
    • 2018-04-10
    • 2018-05-17
    • 2017-09-11
    • 1970-01-01
    • 2017-08-02
    • 1970-01-01
    • 2020-02-11
    相关资源
    最近更新 更多