【问题标题】:react-router: Not found (404) for dynamic content?react-router:动态内容未找到(404)?
【发布时间】:2017-06-06 00:26:18
【问题描述】:

react-router 如何正确处理 Universal 应用中动态内容的 404 页面?

假设我想显示一个带有类似'/user/:userId' 的路由的用户页面。我会有这样的配置:

<Route path="/">
    <Route path="user/:userId" component={UserPage} />
    <Route path="*" component={NotFound}  status={404} />
</Route>

如果我请求/user/valid-user-id,我会得到用户页面。

如果我请求 /foo,我会得到正确的 404。

但是如果我请求/user/invalid-user-id 怎么办。当为用户获取数据时,我会意识到这个用户不存在。所以,接缝的正确做法是:

  • 显示 404 页面
  • 返回 404 http 代码(用于服务器端 渲染)
  • 保持网址不变(我不想要重定向)

我该怎么做?这似乎是一种非常标准的行为。我很惊讶没有找到任何例子......

编辑:
像我这样的接缝并不是唯一一个与之抗争的人。这样的事情会有很大帮助:https://github.com/ReactTraining/react-router/pull/3098
由于我的应用不会很快上线,所以我决定等着看下一个 react-router 版本会提供什么......

【问题讨论】:

  • 如何获取用户数据?我认为那里的代码将是您正在寻找的答案的重要组成部分。
  • @KeesvanLierop 我正在使用 redux。所以我会有一个 thunk 动作返回一个 Promise。 promise 返回的错误表明给定的 userId 不存在。默认情况下,此操作将由我的 UserPage 组件触发。但也许在实际渲染组件之前这样做更有意义。我不确定......但我愿意接受建议以使其与 react-router 一起使用......

标签: reactjs react-router universal isomorphic-javascript


【解决方案1】:

首先为onEnter 回调创建一个中间件函数,这样这对于redux promises 是可行的:

import { Router, Route, browserHistory, createRoutes } from "react-router";

function mixStoreToRoutes(routes) {
    return routes && routes.map(route => ({
        ...route,
        childRoutes: mixStoreToRoutes(route.childRoutes),
        onEnter: route.onEnter && function (props, replaceState, cb) {
            route.onEnter(store.dispatch, props, replaceState)
                .then(() => {
                    cb(null)
                })
                .catch(cb)
        }
    }));
}

const rawRoutes = <Route path="/">
    <Route path="user/:userId" component={UserPage} onEnter={userResolve.fetchUser} />
    <Route path="*" component={NotFound}  status={404} />
</Route>

现在在这个onEnter 函数中,您可以直接使用redux 存储。因此,您可以调度成功或失败的操作。示例:

function fetch(options) {
    return (dispatch) => {
        return new Promise((resolve, reject) => {
            axios.get('<backend-url>')
                .then(res => {
                    resolve(dispatch({type: `CLIENT_GET_SUCCESS`, payload: res.data}))
                })
                .catch(error => {
                    reject(dispatch({type: `CLIENT_GET_FAILED`, payload: error}));
                })
            }
        })
    }
}

let userResolve = {

    fetchUser: (dispatch, props, replace) => {
        return new Promise((next, reject) => {
            dispatch(fetch({
                user: props.params.user
            }))
                .then((data) => {
                    next()
                })
                .catch((error) => {
                    next()
                })
        })
    }

}

当 resolve promise 现在失败时,react-router 会自动寻找它可以为这个端点渲染的下一个组件,在本例中是 404 组件。

这样您就不必使用replaceWith 并且您的网址会继续保留。

【讨论】:

    【解决方案2】:

    如果您不使用服务器端渲染,则无法在页面渲染之前返回 404。您将需要以任何一种方式(在服务器上或通过客户端上的 AJAX)检查用户是否存在。如果没有服务器端渲染,第一个是不可能的。

    一种可行的方法是在 Promise 错误时显示 404 页面。

    【讨论】:

    • 实际上我可以为我的 404 渲染页面。它只是与不应渲染的路由关联的组件。我可以使用路由的onEnter 回调,在此处执行请求,然后在用户不存在时调用replaceWith。但它并不真正喜欢它,因为它会将用户重定向到另一个 url。顺便说一句,我实际上是在做服务器端渲染,所以我正在寻找一个通用的解决方案。
    【解决方案3】:

    我在我正在制作的使用服务器端渲染和 react-router 的项目中尝试了我的解决方案,它在那里工作,所以我会告诉你我做了什么。

    创建一个用于验证 ID 的函数。如果 ID 有效,则返回带有正确组件的 with User 页面,如果 ID 无效,则返回带有 404 页面。

    看例子:

    // Routes.jsx

    function ValidateID(ID) {
        if(ID === GOOD_ID) {
            return (
                <Route path="/">
                    <Route path="user/:userId" component={UserPage} />
                    <Route path="*" component={NotFound}  status={404} />
                </Route>
            );
        } else {
           return (
               <Route path="/">
                   <Route path="user/:userId" status={404} component={Page404} />
                  <Route path="*" component={NotFound}  status={404} />
               </Route>
           );
        }
    

    // Router.jsx

    <Router route={ValidateID(ID)} history={browserHistory}></Router>
    

    这应该与我的project 中的服务器端渲染一起使用。它不使用 Redux。

    【讨论】:

    • 谢谢,但是这个策略可以处理 ID 的异步检查吗?
    • @TomEsterez 我不知道,我没试过,但是route 正在获取从 ValidateID 返回的值,所以 ID 是同步还是异步可能并不重要。
    【解决方案4】:

    在动态路径的情况下,你可以这样做,你不必改变当前的路径。

    只需导入 error404 组件并在状态中定义一个属性(未找到)用于调节。

    import React, { Component } from 'react';
    import axios from 'axios';
    import Error404 from './Error404';
    
    export default class Details extends Component {
        constructor(props) {
            super(props)
            this.state = {
                project: {}, notfound: false
            }
        }
    
        componentDidMount() {
            this.fetchDetails()
        }
    
        fetchDetails = () => {
            let component = this;
            let apiurl = `/restapi/projects/${this.props.match.params.id}`;
            axios.get(apiurl).then(function (response) {
                component.setState({ project: response.data })
            }).catch(function (error) {
                component.setState({ notfound: true })
            })
        }
    
        render() {
            let project = this.state.project;
            return (
                this.state.notfound ? <Error404 /> : (
                    <div>
                        {project.title}
                    </div>
                )
            )
        }
    }
    

    【讨论】:

      【解决方案5】:

      我在制作博客网站时遇到了类似的问题。我一直在寻找解决方案一段时间。我正在基于动态链接映射(使用地图功能)我的博客组件。 初始代码sn-p如下:

      import Blog from '../../Components/Blog/Blog.component';
      
      import './BlogPage.styles.scss';
      
      const BlogPage = ({ BlogData, match }) => {
      
          return (
              <div className='blog-page'>
                  {
                      BlogData.map((item, idx)=>
                          item.link === match.params.postId?
                          <Blog 
                              key={idx} 
                              title={item.title} 
                              date={item.date} 
                              image={item.image} 
                              content={item.content}
                              match={match}
                           />
                          :''
                      )
                  }
              </div>
          )
      };
      
      export default BlogPage;
      

      我使用了一个 hack,我将使用 filter 函数而不是 map 并存储它,然后检查它是否存在(在这种情况下,检查结果是否长度大于零),如果存在,则使用 props 呈现博客组件对于其他页面,我呈现 Not Found 组件 (My404Component)。 sn-p如下:

      import Blog from '../../Components/Blog/Blog.component';
      import My404Component from '../../Components/My404C0mponent/My404Component.component';
      
      import './BlogPage.styles.scss';
      
      const BlogPage = ({ BlogData, match }) => {
          const result = BlogData.filter(item => item.link === match.params.postId);
      
          console.log(result);
      
          return (
              <div className={result.length>0? 'blog-page': ''}>
                  {
                      result.length>0?
                      <Blog 
                          title={result[0].title} 
                          date={result[0].date} 
                          image={result[0].image} 
                          content={result[0].content} 
                          match={match} 
                       />
                      :<My404Component />
                  }
              </div>
          )
      };
      
      export default BlogPage;
      

      这样,只要输入的链接的值无效,Blog 组件就不会呈现,因为结果将是一个空数组并且它的长度将为 0,而是呈现 My404Component。 代码有点原始,我还没有重构它。 希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-11-21
        • 1970-01-01
        • 1970-01-01
        • 2016-01-18
        • 2017-04-20
        • 1970-01-01
        • 2019-08-07
        相关资源
        最近更新 更多