react-router 踩坑记
react-router踩坑分享
背景
有一个Web项目,采用的是前后端分离的方式,前端使用的是react技术栈,后端使用的是Django框架。
有这么需求: 在一个新闻列表里,点击某一条新闻,要求新闻详情页里的title keywords description
是动态的。
辛苦历程
JavaScript动态修改
当从后端取回数据时,用JavaScript动态改变title标签和meta标签就ok了。但是后来才发现,这个需求的目的是为了SEO,而爬虫可以爬到的数据是后端返回的html页面上的数据。如果是通过JavaScript来动态处理title keywords description
的话,是不利于SEO的,因此,动态修改title keywords description
只能放到后端做。后端将html拼接好,返回前端来,前端再进行相应的逻辑操作。
第一次尝试
当思路理清楚之后,其实想着也就很简单, 三步就可以了。
- 前端向后端发送请求。
- 后端获取前端发送请求的URL地址,得到参数,然后从数据库里读出相应的数据。
- 返回一个新的html页面。
然后开始进行测试:
首先,打开主页面,点击任意一条新闻,此时的url还是hash模式的。
当跳到新闻详情页表的时候,打开控制台,进入Network, 查看所发送的请求。
可以看见,请求的路径是localhost:3000
, 也就是根目录,而后台配置的根路由的代码如下:
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
]
因此,可以知道,在请求后台的时候,后台返回了index.html
模板回来。但是,问题出现了,我们想要返回的index.html
里的keywords description title
是在后端拼接好然后返回给前端,而目前我们所能实现的仅仅是返回一个index.html
, 我们没办法改变那3个值,因为我们没有办法获取到前端的URL,或者说是前端没有办法传参数到后端去, 导致后端不能从数据库里查出来那三个值。
那么,现在需要解决的问题就是如何将参数传到后端去。
第二次尝试
由于,点击某条新闻的时候,跳到新闻详情页的时候,这是浏览器主动去向后端发送的请求,因此是很难去控制这个过程的。
所以,有了下面这个解决方案: 既然很难在浏览器主动发请求的时候去控制参数的传递,那么就不用去控制。而是在浏览器返回了index.html
后,这个时候,已经有了新闻的id
, 因此就可以人为的控制再发一次请求,这个时候后端配置一个路由,根据id
去数据库里查找keywords description title
, 并在后端拼接好返回给前端。只不过有个缺点,会刷新一次页面。
但是,在实践的过程中,我理论上推导了一次整个过程,当人为控制发请求后,后端返回一个index.html
, 然后又会执行js文件,然后又会发请求,然后又会返回index.html
, 然后又会执行js文件,然后又会发请求。。。 就这样,一直循环下去,显然是不可能的。
第三次尝试
这探索新的解决的方案的时候,了解了Http Header里的referer
, 当浏览器向web服务器发送请求的时候,一般都会带上referer
, 告诉服务器,我是从哪个连接来的。因为想着后端只要获取到了前端的URL,一切就搞定了,但是显而易见,referer
依然不行。拿网易云音乐测试如下:
第四次尝试
在不断尝试的过程中,珩哥提到,后端获取不到前端的URL地址,是因为react-router路由是使用的hash路由(http://localhost:3001/#/news/index/4141?_k=mv8udy
),如果将hashHistory改为browserHistory(http://localhost:3001/news/index/4141
)的话,那么后端一定就可以获取到前端的URL。
解决方案:
cd projectName/app/src
vim app.js
// hash路由
import { Router, hashHistory as historyProvider, match } from 'react-router';
// 修改为browserHistory
import { Router, browserHistory as historyProvider, match } from 'react-router';
进行查看:
主页面:
新闻详情页:
总结步骤
- 设置react-router路由方式为:
import { browserHistory } from 'react-router';
。 - 对服务器进行改造,否则用户直接请求某个某个路由,就会报404。
- 进行测试。
那么如何进行测试呢?
- 看前端的请求地址。
- 多刷新页面试试。(因为之前的是单页面的,就算改变了路由,还是不会向后台发送请求。)
- 多看
title
的变化, 会遗留title
不变化或者说是变化出错的bug。 - 只要有处理路由相关的地方,都测一下。(很重要)
其他方案
不知道大家是否还有其他解决方案?
原理
History
它是一个库,react-router基于它来管理历史会话记录。
简单的说,一个history知道如何去监听浏览器地址栏的变化,并解析这个URL转化为location对象,然后router使用它匹配到路由,最后正确地渲染对应的组件。
常见的3种History
BrowserHistory
它其实就是HTML5推出的历史记录API,可以把浏览器记录当作一个栈,通过History提供的API来对浏览器记录进行相应的操作。
常见的方法有下面这几种:
- history.push(path, [state])
- history.replace(path, [state])
- history.go(n)
- history.goBack()
- history.goForword()
BrowserHistory是使用react-router应用推荐的history。它使用浏览器中的HistoryAPI用于处理URL,会创建一个像example.com/some/path
这样的真实的URL。
要使用这个History的话,必须在服务器端做好处理URL的准备。一般从下面几个方面来考虑:
- 处理
/
的请求,Django
简单配置如下:
// urls.py
from apps import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
- 处理打开新页面的URL跳转,比如点击某条新闻,跳转到新闻详情页这样的URL跳转(即本文开头的介绍)。
- 处理每个URL的路由, 这也是最容易被忽视的一部分。当你在导航栏里点来点去,显示是没有问题的,就像下面这样。
但是当你刷新页面的时候,你会得到这样一个结果:
这是由于,你的应用是单页的,当你点击导航栏的时候,页面并没有刷新,只是JavaScript去请求了后台,然后更新了前端路由和组件状态,但是没有刷新页面,导致没有去请求后台的地址。所以,必须为每个URL在后端配置路由。一般来说,只需要配置简单的根路由就可以了,除非你需要动态更改模板index.html
上的数据。
HashHistory
Hash history使用URL中的hash(#)部分去创建形如: example.com/#/path的路由。
像?_k=ckuvip没用的URL是什么东西?
每个URL对应都会对应一个state对象,你可以在对象里存储数据(比如当前页面滚动到哪个位置了),但是这个数据不会出现在URL中。实际上,数据被存到了sessionStorage中。
当一个history通过应用程序的push或者replace作跳转时,它可以在新的location里存储
location state
, 而不用显示在URL中,它类似于一个HTML中的post的表单数据。在DOM API中,这些hash history通过
window.location.hash = newHash
很简单地用于被跳转,且不用存储它们的location state。但我们想要每个history都能使用location.state, 因此要为每个location创建一个唯一的key, 并把它们的状态存储在sessionStorage中。当点击'后退'和'前进'时,我们就会有一个机制去恢复这些location state。
MemoryHistory
Memory history不会在地址栏里被操作或读取,这就解释了是如何实现服务端渲染的。
BrowserHistory与HashHistory的对比
实际上,两种History都只是人为控制路由的一种手段。
相同点:
它们都只是简单的改变了地址栏里的URL,如果没有刷新页面的话,都不会向后端主动发送请求。
不同点:
当页面刷新的时候,对于BrowserHistory, 浏览器会向后台发送整个URL的请求, 而对于HashHistory, 它只会请求后台的根目录。
使用场景
- 不能完全是单页应用。
- 必要要刷新页面,因为这样,才会有向后端发送请求。
- 需要动态改变模板里的内容,比如
head
里的内容。 - 不是特别复杂的系统。
其他
推荐阅读: 关于如何爬取ajax应用
感觉单页的seo,除了用SSR,就没有其他的办法了。
参考
react-router 踩坑记的更多相关文章
- Hook踩坑记:React Hook react-unity-webgl
自公司前后分离上手React以来,一个坑一个坑的踩,Class的全生命周期云里雾里,还么屡明白,就抱上了Hook的大腿不松手,确实爽到飞起.修改到Hook的过程基本比较顺畅,直接少了三分之一的代码,组 ...
- Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记
前言 本文讲解如何在 Vue 项目中使用 TypeScript 来搭建并开发项目,并在此过程中踩过的坑 . TypeScript 具有类型系统,且是 JavaScript 的超集,TypeScript ...
- 【踩坑记】从HybridApp到ReactNative
前言 随着移动互联网的兴起,Webapp开始大行其道.大概在15年下半年的时候我接触到了HybridApp.因为当时还没毕业嘛,所以并不清楚自己未来的方向,所以就投入了HybridApp的怀抱. Hy ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- djangorestframework+vue-cli+axios,为axios添加token作为headers踩坑记
情况是这样的,项目用的restful规范,后端用的django+djangorestframework,前端用的vue-cli框架+webpack,前端与后端交互用的axios,然后再用户登录之后,a ...
- HttpWebRequest 改为 HttpClient 踩坑记-请求头设置
HttpWebRequest 改为 HttpClient 踩坑记-请求头设置 Intro 这两天改了一个项目,原来的项目是.net framework 项目,里面处理 HTTP 请求使用的是 WebR ...
- vue踩坑记
vue踩坑记 易错点 语法好难啊qwq 不要把'data'写成'date' 在v-html/v-bind中使用vue变量时不需要加变量名 在非vue事件中使用vue中变量时需要加变量名 正确 < ...
随机推荐
- iOS 关于UITableView的黑科技
UITableView是我们最常用的控件了,今天我就来介绍一些关于UITableView的黑科技和一些注意的地方. 1.修改左滑删除按钮的高度 左滑删除这是iOS最先发明的,之后安卓开始模仿. ...
- 【JAVAWEB学习笔记】03_JavaScript
javascript简单介绍 ECMAScript 1.语法 2.变量:只能使用var定义,如果在函数的内容使用var定义,那么它是一个局部变量,如果没有使用var它是一个全局的.弱类型! 3.数据类 ...
- JVM类加载续
上一篇理解了JVM类加载过程的第一个阶段,这篇来说说剩下的阶段:验证.准备.解析.初始化.需要注意的是,这些阶段(解析除外)只是按照这个顺序开始,但是执行的过程中可能存在交叉. 验证:就是要对加载的二 ...
- SQLServer 延迟事务持久性
SQL Server 2014新功能 -- 延迟事务持久性(Delayed Transaction Durability) SQL Server事务提交默认是完全持久性的(Full Durable), ...
- Codeforces Round #392 (Div. 2)
D题,给出n,k,k是n进制数,但是大于十进制时,它的表示方法仍为十进制那种,比如16进制下的15,我们可以看成就是15,或者1|5,也就是1×16+5 = 21,让你求出能表达的最小十进制数 从后面 ...
- java.util.Properties工具类
import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import ...
- 【面向对象设计原则】之里氏替换原则(LSP)
里氏代换原则由2008年图灵奖得主.美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing 教授于1994年提出,所以使用的是这位女博士的性命名的一个 ...
- Vue 非父子组件通信
组件是Vue核心的一块内容,组件之间的通信也是很基本的开发需求.组件通信又包括父组件向子组件传数据,子组件向父组件传数据,非父子组件间的通信.前两种通信Vue的文档都说的很清楚,但是第三种文档上确只有 ...
- [BZOJ4008]亚瑟王
Description 小 K 不慎被 LL 邪教洗脑了,洗脑程度深到他甚至想要从亚瑟王邪教中脱坑. 他决定,在脱坑之前,最后再来打一盘亚瑟王.既然是最后一战,就一定要打得漂 亮.众所周知,亚瑟王是一 ...
- js判断是否是ie浏览器且给出ie版本
之前懒得写判断ie版本js,因为网上关于这方面的代码太多了,所以从网上拷贝了一个,放到项目上才发现由于时效性的问题,代码不生效.就自己写一个吧. 怎么去看浏览器的内核等信息 ---- js的全局对象w ...