Vue-Router 前端路由实现的思路
一.前端路由是什么?
1. 路由
只要满足一对多的关系就叫分发, 分别发送到各个地方。路由就是分发请求,通过网络把信息从原地址传输到目的地址,路由器就是分发请求的东西。
前端路由:
在一个HTML页面中实现与用户交互时不刷新和跳转页面的同时,为SPA中的每个视图展示匹配一个特殊的url,改变这个url且不会让浏览器像服务器发送请求,并且可以监听到url的变化。为实现这一目标,我们需要做到以下二点:
- 改变 url 且不让浏览器像服务器发送请求。
- 可以监听到 url 的变化
什么是 SPA
SPA 是 single page web application 的简称,译为单页Web应用。
简单的说 SPA 就是一个WEB项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。 取而代之的是利用 JS 动态的变换 HTML 的内容,从而来模拟多个视图间跳转。
2. 分发
3. 路由表
自定义hash与div的关系
const div1 = document.createElement("div"); //JS创建div div1.innerHTML = "1"; const div2 = document.createElement("div"); div2.innerHTML = "2"; const div3 = document.createElement("div"); div3.innerHTML = "3"; const div4 = document.createElement("div"); div4.innerHTML = "4"; const routeTable = { "1": div1, "2": div2, "3": div3, "4": div4 }; function route() { let number = window.location.hash.substr(1); let app = document.querySelector("#app"); number = number || 1; let div = routeTable[number.toString()]; //得到'1' if (!div) { div = document.querySelector("#div404"); } div.style.display = "block"; app.innerHTML = ""; //置空 app.appendChild(div); } route(); window.addEventListener("hashchange", () => { console.log("hash变了"); route(); });
4. 默认路由
number = number || 1 //如果number不存在就给个保底址1
5. 404 路由 / 保底路由
if(div){ div.style.display="block" }else{ div=document.querySelector("#div404") div.style.display="block" } ```J ### 6. 嵌套路由 # 二.hash模式? history模式? memory模式? ## 1.hash ### Js实现 [代码片段](https://code.juejin.cn/pen/7144916568755929118) **任何情况下你都可以用hash做前端路由,但是SEO不友好。** **缺点:SEO不友好。** 服务器收不到hash,因为浏览器是不会把#之后的内容发送给服务器。所以不管你是访问`https://www.baidu.com/#1`还是`https://www.baidu.com/#frank`对百度来说都是同一个内容,永远只展示默认路由`https://www.baidu.com`。根本没法收录你的内容,这就叫SEO不友好。 但也不是绝对的,google的**hashbang**可以让google能识别#,需要你的服务器做些配置:加`!`,这样google就会认为是不同的页面。例如`https://www.baidu.com/#!1` ```js const app = document.querySelector("#app"); const div1 = document.createElement("div"); div1.innerHTML = "1"; const div2 = document.createElement("div"); div2.innerHTML = "2"; const div3 = document.createElement("div"); div3.innerHTML = "3"; const div4 = document.createElement("div"); div4.innerHTML = "4"; const routeTable = { "1": div1, "2": div2, "3": div3, "4": div4 }; function route(container) { let number = window.location.hash.substr(1); number = number || 1; // 获取界面 let div = routeTable[number.toString()]; if (!div) { div = document.querySelector("#div404"); } div.style.display = "block"; // 展示界面 container.innerHTML = ""; container.appendChild(div); } route(app); window.addEventListener("hashchange", () => { console.log("hash 变了"); route(app); });
2.history
Js实现
<nav class="hi"> <a class="link" href="/">home</a> <a class="link" href="/a">a</a> <a class="link" href="/c">c</a> </nav> <section id="home"> <h1>home</h1> <p>This is home page</p> </section> <section id="a"> <h1>a</h1> <p>This is a page</p> </section> <section id="default"> <h1>404</h1> <p>404</p> </section> <script> </script>
.hi{ display: flex; justify-content: center; } a{ width: 100px; border: 1px solid black; padding: 5px; left:50%; }
class Router { constructor({ mode, routes }) { this.routes = routes window.onpopstate = () => this.setPage() document.querySelectorAll('.link').forEach(node => { node.addEventListener('click', (e) => { this.setPage(node.pathname) e.preventDefault() }) }) this.setPage() } setPage(path = '/') { history.pushState({}, "", path) this.routes.forEach(route => route.component.style.display = "none") let route = this.routes.find(route => route.path === path) || this.routes[this.routes.length - 1] route.component.style.display = "block" } } new Router({ mode: 'history', routes: [ { path: '/', component: document.querySelector('#home') }, { path: '/a', component: document.querySelector('#a') }, { component: document.querySelector('#default') }, ] })
条件1.后端将所有前端路由都渲染同一个页面(不能是404)
条件2.不需要兼容IE8以下。
只要后端能实现需求:不管你请求https://www.baidu.com/#1
还是https://www.baidu.com/#frank
都得到同一个页面https://www.baidu.com
,而且不需要兼容IE8以下,那你就可以使用history模式。
const app = document.querySelector("#app"); const div1 = document.createElement("div"); div1.innerHTML = "1"; const div2 = document.createElement("div"); div2.innerHTML = "2"; const div3 = document.createElement("div"); div3.innerHTML = "3"; const div4 = document.createElement("div"); div4.innerHTML = "4"; const routeTable = { "/1": div1, "/2": div2, "/3": div3, "/4": div4 }; function route(container) { let number = window.location.pathname; console.log("number: " + number); if (number === "/") { number = "/1"; } // 获取界面 let div = routeTable[number.toString()]; if (!div) { div = document.querySelector("#div404"); } div.style.display = "block"; // 展示界面 container.innerHTML = ""; container.appendChild(div); } const allA = document.querySelectorAll("a.link"); for (let a of allA) { a.addEventListener("click", e => { e.preventDefault(); const href = a.getAttribute("href"); window.history.pushState(null, `page ${href}`, href); // 通知 onStateChange(href); }); } route(app); function onStateChange() { console.log("state 变了"); route(app); }
细节
1.每次点都要重新渲染,体验感很差,于是浏览器出了一个新的APIhistory
2.如果是通过document.querySelectorAll
得到的allA是不能直接map的,可以用这种方法for(let a of allA){}
遍历。
3.在不刷新页面的情况下,改URL
用法 var stateObj = { foo: "bar" }; history.pushState(stateObj, "", "bar.html"); 项目实例 const href = a.getAttribute("href");//每次点击时获取href值 window.history.pushState(null, `page ${href}`, href);//改URL
4.监听state变化
点击链接时跳转,怎么监听变化了呢?
在push时进行通知:就是调用一个函数onStateChange(href)
知道它变了后就可以render了。
5.history模式为什么需要所有的页面指向同一个页面呢?
因为用户喜欢刷新。比如说当前页面是/4
,点击刷新,如果4没有定位到该页面就会到404页面,那这可不行。
memory模式(一般不用)
memory模式既不用hash也不用history,用一个对象来存储你的东西。
hash、history模式都是把数据存在URL,URL一变,页面就要变。memory是存到localStorage里。
const app = document.querySelector("#app"); const div1 = document.createElement("div"); div1.innerHTML = "1"; const div2 = document.createElement("div"); div2.innerHTML = "2"; const div3 = document.createElement("div"); div3.innerHTML = "3"; const div4 = document.createElement("div"); div4.innerHTML = "4"; const routeTable = { "/1": div1, "/2": div2, "/3": div3, "/4": div4 }; function route(container) { let number = window.localStorage.getItem("xxx"); if (!number) { number = "/1"; } // 获取界面 let div = routeTable[number.toString()]; if (!div) { div = document.querySelector("#div404"); } div.style.display = "block"; // 展示界面 container.innerHTML = ""; container.appendChild(div); } const allA = document.querySelectorAll("a.link"); for (let a of allA) { a.addEventListener("click", e => { e.preventDefault(); const href = a.getAttribute("href"); window.localStorage.setItem("xxx", href); // 通知 onStateChange(href); }); } route(app); function onStateChange() { console.log("state 变了"); route(app); }
把路径存在用户看不见的地方,没有路径
\
memory优点: 这种模式适合非浏览器,比如你想在APP里想做路由,APP是没有路径的,不是网页就没有路径。
memory适用于:react native和weex(Vue手机端的方案),有市场但前端一般不用。
hash、history、memory三者区别
hash、history
都是把路径存在url上,只不过一个是URL的哈希,一个是URL的路径。memory不用URL,前端一般放在localStorage,移动端APP放在本地数据库里面。
memory缺点是没有URL,只对单机有效,是单机版的路由,分享无效。hash、history
可以记录你的信息,所以你可以分享你的URL。你的页面显示的是几,用户看到的就是几,所以是可分享的路由。