这是今天自己随便鼓捣 React-Router
的官方 demo 所遇到的问题,具体表现是:'/'
根节点 Home 显示无误,不过其他任何路由 例如 /detail
,均显示 Cannot GET /detail
。
在我反复确认 js 代码与官方给出的代码一致后,我开始从 webpack 寻找答案。
果然很快,我就找到类似的问题:
React-router v4 - cannot GET url
React-router urls don’t work when refreshing or writting manually
虽然他们的问题普遍表现在:第一次访问页面是好的,但是在刷新或者手动输入路由地址时,会报 Cannot GET /detail
。
所幸我照着他们的解决方案也改好了我的 bug
将 demo 中用的 BrowserRouter
改为 HashRouter
即可
你需要注意的是:
比如你的路由设置为 /detail
,原来访问 localhost:8080/detail
,在改成 HashRouter
之后,可能需要访问 localhost:8080/#/detail
。
修改 webpack.config.js
配置文件
module.exports = {
// 省略其他的配置
devServer: {
historyApiFallback: true
}
}
在 devServer
中必须设置 historyApiFallback: true
至于为何会出这个问题呢,这里有一篇文章讲的非常好,我这边做个大概的总结加翻译吧(可能不是很贴切,毕竟英语捉鸡,有兴趣的同学可以直接移步 传送门1 传送门2)
首先我们需要知道 React-Router
这类前端路由被称为 client-side routers
(后面简称 CSR
吧)。此外,在引入 CSR
后,目前会有两个地方(浏览器、服务端)会进行 URL 解析的工作,在以前浏览器会直接发送一个 GET request
给服务端,服务端做 URL 解析的工作。
了解完上面的知识点后,我们先来分析一次正确的 CSR 解析过程。
浏览器在访问页面(比如首页 "/"
)之前,客户端还没有加载任何的 js 代码,意味着也没有 React、React Router,所以首次的 GET request
都会被发送到服务端;
服务端在解析 URL 之后,会返回包含需要的 js 代码的页面,其中就有 React、React Router 等等,首页加载完成
打个比方,用户点击了首页导航栏中的 About us
按钮,URL 修改成了 http://example.com/about
(CSR
会调用 history.pushState
这个浏览器 API 仅在浏览器本地修改了 URL) —— 没有请求发送到服务端!没有页面刷新!(因为在第2步后,CSR
已经加载完成,承担了解析 URL 的工作)
所以基本上当你点击了一个跳转按钮,一些 JavaScript 代码运行控制地址栏中的 URL 发生变化(没有页面刷新)。
那么如果手动进行了刷新或是手动输入地址,此时程序会再次绕过 React、React Router 的代码,发送 GET request
到服务端,如果你的服务端没有设置对应的路由可以解析这个 URL,那它就会返回 Cannot GET
。尽管相同的 URL 会在客户端是可以正常运行的,那是 React Router 在解析 URL。
下面我们来看看上面给的两种解决方案为何可以生效,以及各自的缺陷
把 Browser Router
改为 Hash Router
,页面的 URL 会变为 http://example.com/#/about
。在 hash 标志符(#
)之后的部分不会被发送到服务端,服务端只能接收到 http://example.com
,返回首页以及包含 React、React Router 的 JS 代码。然后 CSR
代码生效,会解析 #/about
,并呈现正确的页面。
缺点
#
,不是很好看webpack 手册
进行这项配置是告诉 Webpack Dev Server,当使用 HTML History API
(history.pushState
) 时,任意的 404
响应都会被重定向到 /index.html
。
缺点
注
有时候配置了 historyApiFallback
可能会出现下面这样的问题
这个问题需要配置 webpack.config.js
中的
module.exports = {
output: {
publicPath: '/'
}
}
publicPath
配置的值需要与你的 index.html 引入的 src 对应:
<script type="text/javascript" src="/app.bundle.js">script>
下面附一份我的 webpack 配置文件,大家可以根据自己的项目情况进行修改
var path = require('path')
module.exports = {
entry: './src/app.js',
output: {
path: path.join(__dirname, './build'),
filename: 'app.bundle.js',
publicPath: '/'
},
mode: 'development',
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader'},
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
]
},
devServer: {
historyApiFallback: true
}
}