摘要:概述上一章講了如何實(shí)現(xiàn)組件頁(yè)面切換,這一章講如何解決上一章出現(xiàn)的問(wèn)題以及如何優(yōu)雅的實(shí)現(xiàn)頁(yè)面切換。在中監(jiān)聽(tīng)了事件,這樣就可以在變化的時(shí)候,需要路由配置并調(diào)用。
0x000 概述
上一章講了SPA如何實(shí)現(xiàn)組件/頁(yè)面切換,這一章講如何解決上一章出現(xiàn)的問(wèn)題以及如何優(yōu)雅的實(shí)現(xiàn)頁(yè)面切換。
0x001 問(wèn)題分析回顧一下上一章講的頁(yè)面切換,我們通過(guò)LeactDom.render(new ArticlePage(),document.getElementById("app"))來(lái)切換頁(yè)面,的確做到了列表頁(yè)和詳情頁(yè)的切換,但是我們可以看到,瀏覽器的網(wǎng)址始終沒(méi)有變化,是http://localhost:8080,那如果我們希望直接訪問(wèn)某個(gè)頁(yè)面,比如訪問(wèn)某篇文章呢?也就是我們希望我們?cè)L問(wèn)的地址是http://localhost:8080/article/1,進(jìn)入這個(gè)地址之后,可以直接訪問(wèn)id 為1的文章。
問(wèn)題1:無(wú)法做到訪問(wèn)特定頁(yè)面并傳遞參數(shù)
問(wèn)題2:通過(guò)LeactDom.render(new ArticlePage(),document.getElementById("app"))太冗余了
基本上也是基于發(fā)布-訂閱模式,
register: 注冊(cè)路由
push: 路由跳轉(zhuǎn)
源碼
class Router { static routes = {} /** * 如果是數(shù)組 * 就遍歷數(shù)組并轉(zhuǎn)化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并執(zhí)行 init 方法 * 如果是對(duì)象 * 就轉(zhuǎn)化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并和原來(lái)的 this.route 合并 * 注意: 如果用對(duì)象形式必須手動(dòng)執(zhí)行 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 } } } } /** * 跳轉(zhuǎn)到某個(gè)路由 * 本質(zhì)是遍歷所有路由并執(zhí)行 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: "主頁(yè)", component: (props) => { return document.createTextNode(`這是${props.route.name}`) } }, { path: "/detail", name: "詳情頁(yè)", 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)
說(shuō)明:
當(dāng)push方法調(diào)用的時(shí)候,會(huì)觸發(fā)register的時(shí)候傳入的callback,并找到push傳入的path匹配的路由信息,然后將該路由信息作為callback的參數(shù),并執(zhí)行callback。
在上面的流程中,我們注冊(cè)了兩個(gè)路由,每個(gè)路由的配置信息大概包含了path、name、component三個(gè)鍵值對(duì),但其實(shí)只有path是必須的,其他的都是非必須的,可以結(jié)合框架、業(yè)務(wù)來(lái)傳需要的參數(shù);在注冊(cè)路由的同時(shí)傳入了路由觸發(fā)時(shí)的動(dòng)作。這里設(shè)定為將父節(jié)點(diǎn)的子節(jié)點(diǎn)全部移除后替換為新的子節(jié)點(diǎn),也就達(dá)到了組件切換的功能,通過(guò)callback的props參數(shù),我們可以獲取到當(dāng)前觸發(fā)的路由配置和觸發(fā)該路由配置的時(shí)候的數(shù)據(jù),比如后面調(diào)用Route.push("/index",{name:1})的時(shí)候,callback的props為
{ data:{ name:1 }, route:{ path: "/index", name: "主頁(yè)", component: (props) => { return document.createTextNode(`這是${props.route.name}`) } } }0x003 和上一章的SPA結(jié)合
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: "主頁(yè)", component: ArticlePage }, { path: "/detail", name: "詳情頁(yè)", component: DetailPage } ], (data, route,match) => { if (route.path !== match) return LeactDom.render(new route.component(data), document.getElementById("app")) })
然后在頁(yè)面跳轉(zhuǎn)的地方,修改為Route跳轉(zhuǎn)
// 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 指定跳轉(zhuǎn)頁(yè)面-hash
先看結(jié)果,我們希望我們?cè)谠L問(wèn)http://localhost:8080/#detail?articleId=2的時(shí)候跳轉(zhuǎn)到id=2的文章的詳情頁(yè)面,所以我們需要添加幾個(gè)方法:
import Url from "url-parse" class Router { static routes = {} /** * 初始化路徑 * 添加 hashchange 事件, 在 hash 發(fā)生變化的時(shí)候, 跳轉(zhuǎn)到相應(yīng)的頁(yè)面 * 同時(shí)根據(jù)訪問(wèn)的地址初始化第一次訪問(wèn)的頁(yè)面 * */ 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()) }) }) } /** * 如果是數(shù)組 * 就遍歷數(shù)組并轉(zhuǎn)化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并執(zhí)行 init 方法 * 如果是對(duì)象 * 就轉(zhuǎn)化成 {"/index":{route:{...},callback:()=>{....}}} 形式 * 并和原來(lái)的 this.route 合并 * 注意: 如果用對(duì)象形式必須手動(dòng)執(zhí)行 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 } } } } /** * 跳轉(zhuǎn)到某個(gè)路由 * 其實(shí)只是簡(jiǎn)單的改變 hash * 觸發(fā) hashonchange 函數(shù) * * @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 轉(zhuǎn)化成 參數(shù)對(duì)象 * 比如 ?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] } /** * 將參數(shù)變成 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 } /** * 組合地址和數(shù)據(jù) * 比如 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
說(shuō)明:這里修改了push方法,原本callback在這里調(diào)用的,但是現(xiàn)在換成在init調(diào)用。在init中監(jiān)聽(tīng)了hashchange事件,這樣就可以在hash變化的時(shí)候,需要路由配置并調(diào)用callback。而在監(jiān)聽(tīng)變化之前,我們先調(diào)用了一次,是因?yàn)槿绻覀兊谝淮芜M(jìn)入就有hash,那么就不會(huì)觸發(fā)hanshchange,所以我們需要手動(dòng)調(diào)用一遍,為了初始化第一次訪問(wèn)的頁(yè)面,這樣我們就可以通過(guò)不同的地址訪問(wèn)不同的頁(yè)面了,而整個(gè)站點(diǎn)只初始化了一次(在不使用按需加載的情況下),體驗(yàn)非常好,還要另外一種實(shí)行這里先不講,日后有空獨(dú)立出來(lái)講關(guān)于路由的東西。
0x005 將自己實(shí)現(xiàn)的路由和React集成重構(gòu)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}
重構(gòu)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}
重構(gòu)路由配置和渲染
const routes = [ { path: "/index", name: "主頁(yè)", component: ArticlePage }, { path: "/detail", name: "詳情頁(yè)", component: DetailPage } ]; Router.register(routes, (data, route) => { let Component = route.component ReactDom.render(0x006 為React定制Router組件, document.getElementById("app") ) })
在上面每調(diào)用一次Router.push,就會(huì)執(zhí)行一次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") )
說(shuō)明
在RouterApp組件中調(diào)用了Route.init來(lái)初始化調(diào)用,然后在每個(gè)Route中注冊(cè)路由,每次路由變化的時(shí)候都會(huì)導(dǎo)致Route組件更新,從而使組件切換。
0x007 總結(jié)路由本身是不帶有任何特殊的屬性的,在與框架集成的時(shí)候,應(yīng)該考慮框架的特點(diǎn),比如react的時(shí)候,我們可以使用react和react-route直接結(jié)合,也可以通過(guò)使用react-route-dom來(lái)結(jié)合。
0x008 資源源碼
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/98045.html
摘要:插拔式應(yīng)用架構(gòu)方案和傳統(tǒng)前端架構(gòu)相比有以下幾個(gè)優(yōu)勢(shì)業(yè)務(wù)模塊分布式開(kāi)發(fā),代碼倉(cāng)庫(kù)更易管理。 showImg(https://segmentfault.com/img/remote/1460000016053325?w=2250&h=1500); 背景 隨著互聯(lián)網(wǎng)云的興起,一種將多個(gè)不同的服務(wù)集中在一個(gè)大平臺(tái)上統(tǒng)一對(duì)外開(kāi)放的概念逐漸為人熟知,越來(lái)越多與云相關(guān)或不相關(guān)的中后臺(tái)管理系統(tǒng)或企業(yè)級(jí)...
摘要:的全稱是統(tǒng)一資源定位符英文,可以這么說(shuō),是一種標(biāo)準(zhǔn),而網(wǎng)址則是符合標(biāo)準(zhǔn)的一種實(shí)現(xiàn)而已。渲染器,將組件渲染到頁(yè)面上。 0x000 概述 從這一章開(kāi)始就進(jìn)入路由章節(jié)了,并不直接從如何使用react-route來(lái)講,而是從路由的概念和實(shí)現(xiàn)來(lái)講,達(dá)到知道路由的本質(zhì),而不是只知道如何使用react-route庫(kù)的目的,畢竟react-route只是一個(gè)庫(kù),是路由的一個(gè)實(shí)現(xiàn)而已,而不是路由本身。 ...
摘要:我們將創(chuàng)建一個(gè)簡(jiǎn)單的,它將從到返回一個(gè)隨機(jī)數(shù)。我們來(lái)改變組件顯示隨機(jī)數(shù)在這個(gè)階段,我們只是模仿客戶端的隨機(jī)數(shù)生成過(guò)程。 在這個(gè)教程中,我們將講解如何將vue.js單頁(yè)應(yīng)用與Flask后端進(jìn)行連接。 一般來(lái)說(shuō),如果你只是想通過(guò)Flask模板使用vue.js庫(kù)也是沒(méi)有問(wèn)題的。但是,實(shí)際上是一個(gè)很明顯的問(wèn)題那就是,Jinja(模板引擎)也和Vue.js一樣采用雙大括號(hào)用于渲染,但只是一個(gè)還算...
摘要:說(shuō)起,其實(shí)早在出現(xiàn)之前,網(wǎng)頁(yè)就是在服務(wù)端渲染的。沒(méi)有涉及流式渲染組件緩存對(duì)的服務(wù)端渲染有更深一步的認(rèn)識(shí),實(shí)際在生產(chǎn)環(huán)境中的應(yīng)用可能還需要考慮很多因素。選擇的服務(wù)端渲染方案,是情理之中的選擇,不是對(duì)新技術(shù)的盲目追捧,而是一切為了需要。 作者:威威(滬江前端開(kāi)發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處。 背景 最近, 產(chǎn)品同學(xué)一如往常笑嘻嘻的遞來(lái)需求文檔, 縱使內(nèi)心萬(wàn)般拒絕, 身體倒是很誠(chéng)實(shí)...
摘要:路由模塊的本質(zhì)就是建立起和頁(yè)面之間的映射關(guān)系。這時(shí)候我們可以直接利用傳值了使用來(lái)匹配路由,然后通過(guò)來(lái)傳遞參數(shù)跳轉(zhuǎn)對(duì)應(yīng)路由配置于是我們可以獲取參數(shù)六配置子路由二級(jí)路由實(shí)際生活中的應(yīng)用界面,通常由多層嵌套的組件組合而成。 一、前言 要學(xué)習(xí)vue-router就要先知道這里的路由是什么?為什么我們不能像原來(lái)一樣直接用標(biāo)簽編寫鏈接哪?vue-router如何使用?常見(jiàn)路由操作有哪些?等等這些問(wèn)...
閱讀 1448·2021-11-17 09:33
閱讀 3038·2021-10-13 09:39
閱讀 2712·2021-10-09 10:01
閱讀 2456·2021-09-29 09:35
閱讀 3908·2021-09-26 10:01
閱讀 3530·2019-08-26 18:37
閱讀 3157·2019-08-26 13:46
閱讀 1920·2019-08-26 13:39