资讯专栏INFORMATION COLUMN

Router入门0x201: 从 URL 到 SPA

honmaple / 2719人阅读

摘要:的全称是统一资源定位符英文,可以这么说,是一种标准,而网址则是符合标准的一种实现而已。渲染器,将组件渲染到页面上。

0x000 概述

从这一章开始就进入路由章节了,并不直接从如何使用react-route来讲,而是从路由的概念和实现来讲,达到知道路由的本质,而不是只知道如何使用react-route库的目的,毕竟react-route只是一个库,是路由的一个实现而已,而不是路由本身。

0x001 URL的概念

很多人对url的理解就是网址,我们在浏览器地址栏输入网址,便可以访问到特定网页,但其实url的含义远远不止是网址。url的全称是统一资源定位符(英文:Uniform Resource Locator),可以这么说,url是一种标准,而网址则是符合url标准的一种实现而已。

让我们做几个实验:

打开浏览器,访问segmentfault的主页,此时地址栏显示的是:

https://segmentfault.com

桌面新建from-url-to-spa.txt文件,输入内容from url to spa,并拖拽到浏览器,此时浏览器显示的是

file:///Users/FollowWinter/Desktop/from-url-to-spa.txt

打开一个github项目,并选择ssh访问,我们可以得到以下地址:

git@github.com:followWinter/flex-layout.git

说明:其中,1访问了一个网页,2访问了一个本地文件,3访问了一个开源项目,从以上可以看出,url有多种用途各异的实现,但是我们可以这么归纳,网络上(包括本地和远程)所有的的东西都看作资源,我们可以通过一种符合某种标准的格式来访问这种资源,从而忽略设备类型(服务器、路由器、硬盘......)、网络类型(远程、本地......)、资源类型(文本、图片、音乐、电影......),而这种标准就是url,也就是我对统一资源定位符的理解。

统一资源定位符的标准格式如下:

协议类型:[//服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]

统一资源定位符的完整格式如下:

协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]

0x002 spa是什么

SPA全称是single page web application,也就是只有一个页面的web应用程序,我们访问一个网页,能够在这个网页上完成所有的业务操作,我们就可以称之为SPA,是和框架无关、技术无关的一个概念。并不是说用angularvuereact实现的web应用才叫SPA,因为这些框架也可以在多页应用中使用。

0x003 如何实现spa

只要在一个页面完成所有业务操作,就可以称之为SPA了,所以实现所谓的SPA也很简单,就是将原本多页的步骤转化为一个页面就行了。

0x004 SPA和路由有啥关系啊

回答:没有关系。SPA不一定要使用路由,不使用也没有关系,但是随着单页应用了扩大,将所有的逻辑都卸载一个页面上,会导致逻辑爆炸,维护痛苦,所以在逻辑上又分为多个页面,达到好维护的效果。

0x005 路由出现

一开始是没有路由的,但是做的应用多了,便有了路由。对于路由的需求有两个:

维护上的需求,过多的逻辑写在一个页面上,容易混乱,所以用路由分离多带带逻辑和页面。

状态保存的需求,比如一个SPA,我们有文章和文章详情页,有一天我们需要分享一个文章,希望可以通过一个链接直接访问到这篇文章。但是单页应用是无状态的,而网址又是唯一的,比如a.com/index.html,无法做到直接访问详情页,所以就出现了一些方案:

hash:a.com/index.html#detail/1,访问 id 为1的文章详情页

url:a.com/index/detail/1,访问 id 为1的文章详情页

这样我们就可以分享一篇文章给其他用户了,方案1实现比较简单,但是路由丑陋并且占用了 hash 符,页面中就不能乱用 hash 符了。方案2好但是需要后端配合,实现也很简单,不管这个 url 是什么,都返回单页应用的 html 就好了。

0x006 实现简单的SPA

架构:

组件,每个组件都是独立的,可以渲染出自己的dom,并且可以绑定事件,拥有生命周期。

渲染器,将组件渲染到页面上。

服务,做数据管理等一些逻辑服务。

项目初始化:

整个项目起始没有啥特别的,只是支持了es6而已,而整个项目我们也将会用es6来实现

初始化项目及其目录

+ 0x021-spa
    + src 
        + core
        + page
        + services
        - index.html
        - index.js
    - .babelrc
    - package.json
    - webpack.config.js

index.html:




    React Study
    
    


.babelrc

{
  "presets": [
    "env",
    "stage-3"

  ]
}

package.json

{
  "name": "0x021-spa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "webpack-dev-server --color --process "
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-3": "^6.24.1",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.1.5"
  }
 
}

webpack.config.js:

const path = require("path")
   var HtmlWebpackPlugin = require("html-webpack-plugin");
   
   module.exports = {
   entry: path.resolve(__dirname, "src/index.js"),
   mode: "development",
   output: {
       path: path.resolve(__dirname, "dist"),
       filename: "bundle.js"
   },
   devServer: {
       open: true
   },
   module: {
       rules: [
           {
               test: /.js$/,
               loader: "babel-loader"
           },
   
       ]
   },
   plugins: [
       new HtmlWebpackPlugin({
           template: path.resolve(__dirname, "src/index.html")
       })
   ]
   }

渲染器实现

渲染器的作用起始就是渲染组件而已,而每个组件都有一个render方法,该方法返回一个dom字符串,也就是说,渲染器的本质就是将dom字符串挂载和卸载。

core/LeactDom.js

class LeactDom {
    static render(child, parent) {
        parent.innerHTML=child
    }
}

export default LeactDom

测试index.js

import LeactDom from "./core/LeactDom";
import LeactDom from "./core/LeactDom";

LeactDom.render(`

这是一个p

`, document.getElementById("app")) document.getElementById("p").addEventListener("click", () => { LeactDom.render("这是一个span", document.getElementById("app")) })

查看浏览器

 如图,我们已经实现了切换了,只需要将之封装为组件就行了
![图片描述][1]    

组件

core/Component.js

// 这是组件根类, 所有的组件都继承这个根
class Component {
    // 返回 dom 字符串
   render() {
        return ""
    }

    // dom 挂载上去以后 执行该方法, 可以在这个方法上执行 dom 查询和事件绑定
    componentDidMount() {

    }

}

export default Component

自定义组件page/Hello.js

import Component from "../core/Component";

class Hello extends Component {

    render() {
        return `

hello

` } componentDidMount() { document.getElementById("hello").addEventListener("click", () => { alert("hello") }) } } export default Hello

引入Hello组件

import LeactDom from "./core/LeactDom";
import Hello from "./page/Hello";

LeactDom.render(Hello,document.getElementById("app"))

修改LeactDom

class LeactDom {

    static render(child, parent, props={}) {
        if (typeof child === "function") {
            let comp = new child()
            comp.props = props
            parent.innerHTML = comp.render()
            comp.componentDidMount()
        } else {
            parent.innerHTML = child
        }
    }
}

export default LeactDom

查看效果

框架完成开始编写服务

文章获取服务service/AticleService.js

const articles = [
    {
        id: 1,
        title: "Redux入门0x101: 简介及`redux`简单实现",
        summary: "简介及`redux`简单实现",
        detail: "详情1"
    },
    {
        id: 2,
        title: "Redux入门0x102: redux 栗子之 counter",
        summary: "redux 栗子之 counter",
        detail: "详情2"
    },
    {
        id: 3,
        title: "Redux入门0x103: 拆分多个 reducer",
        summary: "拆分多个 reducer",
        detail: "详情3"
    },
    {
        id: 4,
        title: "Redux入门0x104: Action Creators",
        summary: "Action Creators",
        detail: "详情4"
    },
    {
        id: 5,
        title: "Redux入门0x105: redux 中间件",
        summary: "redux 中间件",
        detail: "详情5"
    },

]

class ArticleService {

    static getAll() {
        return articles
    }

    static getById(id) {
        return articles.find((article) => {
            return id == article.id
        })
    }
}

export default ArticleService

开始编写自定义组件

文章列表组件

import ArticleService from "../services/ArticleService";
import DetailPage from "./DetailPage";
import LeactDom from "../core/LeactDom";

class ArticlePage {
    render() {
        let articlesListString = ArticleService.getAll()
            .map(article => {
                return `
${article.title}

${article.summary}


` }) .reduce((article1, article2) => { return article1 + article2 }) let articleListContrinerString = `

文章列表


${articlesListString}
` return articleListContrinerString } 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")) }) } ) } } export default ArticlePage

文章详情组件

import ArticleService from "../services/ArticleService";
import Component from "../core/Component";
import LeactDom from "../core/LeactDom";
import ArticlePage from "./ArticlePage";

class DetailPage extends Component {
    constructor(props) {
        super()
        this.article = ArticleService.getById(props.articleId)
    }

    render() {
        const {title, summary, detail} = this.article
        return `

${title}

${summary}


${detail}

` } componentDidMount() { document.getElementById("back").addEventListener("click", () => { LeactDom.render(new ArticlePage(), document.getElementById("app")) }) } } export default DetailPage

加载组件index.js

import LeactDom from "./core/LeactDom";
import ArticlePage from "./page/ArticlePage";

LeactDom.render(new ArticlePage(),document.getElementById("app"))

8 查看最终效果

0x007 总结

这里要做的只是一个案例,而不是写一个完整的框架,所以在很多地方并没有完善,只是为了验证实现SPA的方式,而结果也确实验证了。也将一些问题暴露出来了,其他的问题我们不关心,我们只关心我们之前提出的问题,只有一个网址,如何将某个页面分享出去,很明显,做成SPA之后,无法将文章详情页面分享给他人。解决 方法也已经给出来了:

hash

url

将在下一张讲述如何解决

0x008 资源

源码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108512.html

相关文章

  • Vue.js 服务端渲染业务入门实践

    摘要:说起,其实早在出现之前,网页就是在服务端渲染的。没有涉及流式渲染组件缓存对的服务端渲染有更深一步的认识,实际在生产环境中的应用可能还需要考虑很多因素。选择的服务端渲染方案,是情理之中的选择,不是对新技术的盲目追捧,而是一切为了需要。 作者:威威(沪江前端开发工程师)本文原创,转载请注明作者及出处。 背景 最近, 产品同学一如往常笑嘻嘻的递来需求文档, 纵使内心万般拒绝, 身体倒是很诚实...

    miya 评论0 收藏0
  • Router入门0x202: 自己实现 Router 页面调度和特定页面访问

    摘要:概述上一章讲了如何实现组件页面切换,这一章讲如何解决上一章出现的问题以及如何优雅的实现页面切换。在中监听了事件,这样就可以在变化的时候,需要路由配置并调用。 0x000 概述 上一章讲了SPA如何实现组件/页面切换,这一章讲如何解决上一章出现的问题以及如何优雅的实现页面切换。 0x001 问题分析 回顾一下上一章讲的页面切换,我们通过LeactDom.render(new Articl...

    dance 评论0 收藏0
  • 头开始学习vue-router

    摘要:路由模块的本质就是建立起和页面之间的映射关系。这时候我们可以直接利用传值了使用来匹配路由,然后通过来传递参数跳转对应路由配置于是我们可以获取参数六配置子路由二级路由实际生活中的应用界面,通常由多层嵌套的组件组合而成。 一、前言 要学习vue-router就要先知道这里的路由是什么?为什么我们不能像原来一样直接用标签编写链接哪?vue-router如何使用?常见路由操作有哪些?等等这些问...

    tommego 评论0 收藏0
  • 头开始学习vue-router

    摘要:路由模块的本质就是建立起和页面之间的映射关系。这时候我们可以直接利用传值了使用来匹配路由,然后通过来传递参数跳转对应路由配置于是我们可以获取参数六配置子路由二级路由实际生活中的应用界面,通常由多层嵌套的组件组合而成。 一、前言 要学习vue-router就要先知道这里的路由是什么?为什么我们不能像原来一样直接用标签编写链接哪?vue-router如何使用?常见路由操作有哪些?等等这些问...

    jhhfft 评论0 收藏0
  • 头开始学习vue-router

    摘要:路由模块的本质就是建立起和页面之间的映射关系。这时候我们可以直接利用传值了使用来匹配路由,然后通过来传递参数跳转对应路由配置于是我们可以获取参数六配置子路由二级路由实际生活中的应用界面,通常由多层嵌套的组件组合而成。 一、前言 要学习vue-router就要先知道这里的路由是什么?为什么我们不能像原来一样直接用标签编写链接哪?vue-router如何使用?常见路由操作有哪些?等等这些问...

    frontoldman 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<