摘要:概述上一章講了如何實現組件頁面切換,這一章講如何解決上一章出現的問題以及如何優雅的實現頁面切換。在中監聽了事件,這樣就可以在變化的時候,需要路由配置并調用。
0x000 概述
上一章講了SPA如何實現組件/頁面切換,這一章講如何解決上一章出現的問題以及如何優雅的實現頁面切換。
0x001 問題分析回顧一下上一章講的頁面切換,我們通過LeactDom.render(new ArticlePage(),document.getElementById("app"))來切換頁面,的確做到了列表頁和詳情頁的切換,但是我們可以看到,瀏覽器的網址始終沒有變化,是http://localhost:8080,那如果我們希望直接訪問某個頁面,比如訪問某篇文章呢?也就是我們希望我們訪問的地址是http://localhost:8080/article/1,進入這個地址之后,可以直接訪問id 為1的文章。
問題1:無法做到訪問特定頁面并傳遞參數
問題2:通過LeactDom.render(new ArticlePage(),document.getElementById("app"))太冗余了
基本上也是基于發布-訂閱模式,
register: 注冊路由
push: 路由跳轉
源碼
class Router { static routes = {} /** * 如果是數組 * 就遍歷數組并轉化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并執行 init 方法 * 如果是對象 * 就轉化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并和原來的 this.route 合并 * 注意: 如果用對象形式必須手動執行 init 方法 * 最終 this.route 形式為 * [ * {"/index":{route:{...},callback:()=>{....}}} * {"/detail":{route:{...},callback:()=>{....}}} * ] * @param routes * @param callback */ static register(routes, callback) { if (Array.isArray(routes)) { this.routes = routes.map(route => { return { [route.path]: { route: route, callback: callback } } }).reduce((r1, r2) => { return {...r1, ...r2} }) } this.routes = { ...this.routes, ...{ [routes.path]: { route: routes, callback: callback } } } } /** * 跳轉到某個路由 * 本質是遍歷所有路由并執行 callback * * @param path * @param data */ static push(path, data) { Object.values(this.routes).forEach(route => { route.callback(data, this.routes[ path].route, path) }) } } export default Router
使用
import Router from "./core/Router"; Router.register([ { path: "/index", name: "主頁", component: (props) => { return document.createTextNode(`這是${props.route.name}`) } }, { path: "/detail", name: "詳情頁", component: (props) => { return document.createTextNode(`這是${props.route.name}`) } } ], (data, route, match) => { if (route.path === match) { let app = document.getElementById("app") app.childNodes.forEach(c => c.remove()) app.appendChild(new route.component({data,route,match})) } }) Router.push("/index") setTimeout(()=>{ Router.push("/detail") },3000)
說明:
當push方法調用的時候,會觸發register的時候傳入的callback,并找到push傳入的path匹配的路由信息,然后將該路由信息作為callback的參數,并執行callback。
在上面的流程中,我們注冊了兩個路由,每個路由的配置信息大概包含了path、name、component三個鍵值對,但其實只有path是必須的,其他的都是非必須的,可以結合框架、業務來傳需要的參數;在注冊路由的同時傳入了路由觸發時的動作。這里設定為將父節點的子節點全部移除后替換為新的子節點,也就達到了組件切換的功能,通過callback的props參數,我們可以獲取到當前觸發的路由配置和觸發該路由配置的時候的數據,比如后面調用Route.push("/index",{name:1})的時候,callback的props為
{ data:{ name:1 }, route:{ path: "/index", name: "主頁", component: (props) => { return document.createTextNode(`這是${props.route.name}`) } } }0x003 和上一章的SPA結合
import Router from "./core/Router"; import DetailPage from "./page/DetailPage"; import ArticlePage from "./page/ArticlePage"; import LeactDom from "./core/LeactDom"; Router.register([ { path: "/index", name: "主頁", component: ArticlePage }, { path: "/detail", name: "詳情頁", component: DetailPage } ], (data, route,match) => { if (route.path !== match) return LeactDom.render(new route.component(data), document.getElementById("app")) })
然后在頁面跳轉的地方,修改為Route跳轉
// ArticlePage#componentDidMount componentDidMount() { let articles = document.getElementsByClassName("article") ;[].forEach.call(articles, article => { article.addEventListener("click", () => { // LeactDom.render(new DetailPage({articleId: article.getAttribute("data-id")}), document.getElementById("app")) Router.push("/detail",{articleId:article.getAttribute("data-id")}) }) } ) } // DetailPage#componentDidMount componentDidMount() { document.getElementById("back").addEventListener("click", () => { LeactDom.render(new ArticlePage(), document.getElementById("app")) Router.push("/index") }) }0x004 指定跳轉頁面-hash
先看結果,我們希望我們在訪問http://localhost:8080/#detail?articleId=2的時候跳轉到id=2的文章的詳情頁面,所以我們需要添加幾個方法:
import Url from "url-parse" class Router { static routes = {} /** * 初始化路徑 * 添加 hashchange 事件, 在 hash 發生變化的時候, 跳轉到相應的頁面 * 同時根據訪問的地址初始化第一次訪問的頁面 * */ static init() { Object.values(this.routes).forEach(route => { route.callback(this.queryStringToParam(), this.routes["/" + this.getPath()].route,"/"+this.getPath()) }) window.addEventListener("hashchange", () => { Object.values(this.routes).forEach(route => { route.callback(this.queryStringToParam(), this.routes["/" + this.getPath()].route,"/"+this.getPath()) }) }) } /** * 如果是數組 * 就遍歷數組并轉化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并執行 init 方法 * 如果是對象 * 就轉化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并和原來的 this.route 合并 * 注意: 如果用對象形式必須手動執行 init 方法 * 最終 this.route 形式為 * [ * {"/index":{route:{...},callback:()=>{....}}} * {"/detail":{route:{...},callback:()=>{....}}} * ] * @param routes * @param callback */ static register(routes, callback) { if (Array.isArray(routes)) { this.routes = routes.map(route => { return { [route.path]: { route: route, callback: callback } } }).reduce((r1, r2) => { return {...r1, ...r2} }) this.init() } this.routes = { ...this.routes, ...{ [routes.path]: { route: routes, callback: callback } } } } /** * 跳轉到某個路由 * 其實只是簡單的改變 hash * 觸發 hashonchange 函數 * * @param path * @param data */ static push(path, data) { window.location.hash = this.combineHash(path, data) } /** * 獲取路徑 * 比如 #detail => /detail * @returns {string|string} */ static getPath() { let url = new Url(window.location.href) return url.hash.replace("#", "").split("?")[0] || "/" } /** * 將 queryString 轉化成 參數對象 * 比如 ?articleId=1 => {articleId: 1} * @returns {*} */ static queryStringToParam() { let url = new Url(window.location.href) let hashAndParam = url.hash.replace("#", "") let arr = hashAndParam.split("?") if (arr.length === 1) return {} return arr[1].split("&").map(p => { return p.split("=").reduce((a, b) => ({[a]: b})) })[0] } /** * 將參數變成 queryString * 比如 {articleId:1} => ?articleId=1 * @param params * @returns {string} */ static paramToQueryString(params = {}) { let result = "" Object.keys(params).length && Object.keys(params).forEach(key => { if (result.length !== 0) { result += "&" } result += key + "=" + params[key] }) return result } /** * 組合地址和數據 * 比如 detail,{articleId:1} => detail?articleId=1 * @param path * @param data * @returns {*} */ static combineHash(path, data = {}) { if (!Object.keys(data).length) return path.replace("/", "") return (path + "?" + this.paramToQueryString(data)).replace("/", "") } } export default Router
說明:這里修改了push方法,原本callback在這里調用的,但是現在換成在init調用。在init中監聽了hashchange事件,這樣就可以在hash變化的時候,需要路由配置并調用callback。而在監聽變化之前,我們先調用了一次,是因為如果我們第一次進入就有hash,那么就不會觸發hanshchange,所以我們需要手動調用一遍,為了初始化第一次訪問的頁面,這樣我們就可以通過不同的地址訪問不同的頁面了,而整個站點只初始化了一次(在不使用按需加載的情況下),體驗非常好,還要另外一種實行這里先不講,日后有空獨立出來講關于路由的東西。
0x005 將自己實現的路由和React集成重構ArticlePage
class ArticlePage extends React.Component { render() { return} handleClick(article) { Router.push("/detail", {articleId: article.id}) } }文章列表
{ ArticleService.getAll().map((article, index) => { returnthis.handleClick(article)}>}) }{article.title}
{article.summary}
重構DetailPage
class DetailPage extends React.Component { render() { const {title, summary, detail} = ArticleService.getById(this.props.data.articleId) return} handleClick() { Router.push("/index") } }{title}
{summary}
{detail}
重構路由配置和渲染
const routes = [ { path: "/index", name: "主頁", component: ArticlePage }, { path: "/detail", name: "詳情頁", component: DetailPage } ]; Router.register(routes, (data, route) => { let Component = route.component ReactDom.render(0x006 為React定制Router組件, document.getElementById("app") ) })
在上面每調用一次Router.push,就會執行一次ReactDom.render,并不符合React的思想,所以,需要為React定義一些組件
RouteApp組件
class RouterApp extends React.Component { componentDidMount(){ Router.init() } render() { return {...this.props.children} } }
Route組件
class Route extends React.Component { constructor(props) { super() this.state={ path:props.path, match:"", data:{} } } componentDidMount() { Router.register({ path: this.props.path }, (data, route) => { this.setState({ match:route.path, data:data }) }) } render() { let Component = this.props.component if (this.state.path===this.state.match){ return} return null } }
使用
class App extends React.Component { render() { return () } } ReactDom.render(, document.getElementById("app") )
說明
在RouterApp組件中調用了Route.init來初始化調用,然后在每個Route中注冊路由,每次路由變化的時候都會導致Route組件更新,從而使組件切換。
0x007 總結路由本身是不帶有任何特殊的屬性的,在與框架集成的時候,應該考慮框架的特點,比如react的時候,我們可以使用react和react-route直接結合,也可以通過使用react-route-dom來結合。
0x008 資源源碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98045.html
摘要:插拔式應用架構方案和傳統前端架構相比有以下幾個優勢業務模塊分布式開發,代碼倉庫更易管理。 showImg(https://segmentfault.com/img/remote/1460000016053325?w=2250&h=1500); 背景 隨著互聯網云的興起,一種將多個不同的服務集中在一個大平臺上統一對外開放的概念逐漸為人熟知,越來越多與云相關或不相關的中后臺管理系統或企業級...
摘要:的全稱是統一資源定位符英文,可以這么說,是一種標準,而網址則是符合標準的一種實現而已。渲染器,將組件渲染到頁面上。 0x000 概述 從這一章開始就進入路由章節了,并不直接從如何使用react-route來講,而是從路由的概念和實現來講,達到知道路由的本質,而不是只知道如何使用react-route庫的目的,畢竟react-route只是一個庫,是路由的一個實現而已,而不是路由本身。 ...
摘要:我們將創建一個簡單的,它將從到返回一個隨機數。我們來改變組件顯示隨機數在這個階段,我們只是模仿客戶端的隨機數生成過程。 在這個教程中,我們將講解如何將vue.js單頁應用與Flask后端進行連接。 一般來說,如果你只是想通過Flask模板使用vue.js庫也是沒有問題的。但是,實際上是一個很明顯的問題那就是,Jinja(模板引擎)也和Vue.js一樣采用雙大括號用于渲染,但只是一個還算...
摘要:說起,其實早在出現之前,網頁就是在服務端渲染的。沒有涉及流式渲染組件緩存對的服務端渲染有更深一步的認識,實際在生產環境中的應用可能還需要考慮很多因素。選擇的服務端渲染方案,是情理之中的選擇,不是對新技術的盲目追捧,而是一切為了需要。 作者:威威(滬江前端開發工程師)本文原創,轉載請注明作者及出處。 背景 最近, 產品同學一如往常笑嘻嘻的遞來需求文檔, 縱使內心萬般拒絕, 身體倒是很誠實...
摘要:路由模塊的本質就是建立起和頁面之間的映射關系。這時候我們可以直接利用傳值了使用來匹配路由,然后通過來傳遞參數跳轉對應路由配置于是我們可以獲取參數六配置子路由二級路由實際生活中的應用界面,通常由多層嵌套的組件組合而成。 一、前言 要學習vue-router就要先知道這里的路由是什么?為什么我們不能像原來一樣直接用標簽編寫鏈接哪?vue-router如何使用?常見路由操作有哪些?等等這些問...
閱讀 1410·2021-11-17 09:33
閱讀 3018·2021-10-13 09:39
閱讀 2686·2021-10-09 10:01
閱讀 2447·2021-09-29 09:35
閱讀 3891·2021-09-26 10:01
閱讀 3518·2019-08-26 18:37
閱讀 3149·2019-08-26 13:46
閱讀 1910·2019-08-26 13:39