模拟 VueRouter 3

initComponents

initComponents 是用于创建 router-linkrouter-view 组件的方法,她接收 Vue 参数,虽然 Vue 已经被我们记录到了全局变量中的 _Vue 变量上,但是这里当作参数传入,就是为了减少这个方法和外部的依赖。

js 复制代码
initComponent(Vue) {
    Vue.component("router-link", { ... })
    Vue.component("router-view", { ... })
}    

再回顾一下 router-linkrouter-view 组件的使用:

js 复制代码
<div id="nav"> 
   <router-link to="/">Index</router-link> | 
   <router-link to="/blog">Blog</router-link> | 
   ... 
</div> 
<router-view/>
  1. 最终要渲染成一个超链接 -- <a>
  2. 接收一个字符串类型的参数 to 作为超链接的地址 -- props
  3. 将标签之间的内容作为超链接的内容渲染出来 -- <slot>
js 复制代码
Vue.component("router-link", {
   props: {
     to: String
   },
   template:"<a :href='to'><slot></slot></a>"
})

试一试

导出一下我们写到现在的 VueRouter(export default class VueRouter),然后在我们的 VueCli 项目中修改导入 router 的地址,将 import VueRouter from 'vue-router' 中的 vue-router 改为我们 VueRouter 文件的地址,试着看看我们的 router-link 组件是否能正确显示。

当我们启动项目后,可以在控制台看到以下报错:

这里提示我们使用的是 runtime-only 版本的 vue,其中是不包含编译 template 的模块的,那我们想要避免这个错误有两种方法:

  1. 使用完整版本的 vue

VueCli 默认使用的是运行时版本的 Vue ,因为这样效率更高,如果我们需要使用完整版本的 Vue,就需要在项目根目录下添加 vue.config.js 文件,然后添加配置 runtimeCompiler

js 复制代码
// vue.config.js
module.exports = {
  runtimeCompiler: true
}

再次运行项目就会发现报错消失,页面正常显示了 router-link 组件。

  1. template 改为 render 函数的写法 render 函数就是渲染函数,它接收一个参数 createElement 方法(通常简写成 h),通过调用 createElement 可以创建虚拟 DOM
js 复制代码
    Vue.component("router-link", {
      props: {
        to: String
      },
      render(h) {
        // `createElement` 方法可以接收3个参数,分别是要创建元素的`选择器`、`属性`、`子元素`。
        return h("a", {
          attrs: {
            href: this.to
          }
        }, [this.$slots.default])   // 默认插槽
      },
   })

创建 router-view

router-view 是根据当前路由地址(this.data.current)在 routeMap 中找到对应显示组件的。需要注意的有2点:

  1. 写在 render 函数中的 this 不是 VueRouter 的实例而是 routerView 组件,所以需要在 initComponent 方法内先获取 this,此时的 thisVueRouter 的实例。
  2. h 可以接收一个组件,然后将组件转换为虚拟 DOM
js 复制代码
const self = this
Vue.component("router-view", {
  render(h) {
    const cm = self.routeMap[self.data.current]
    return h(cm)
  }
})

我们已经完成了 router-linkrouter-view 两个组件,可以在浏览器上看下效果,没有报错,但是当我们点击 router-link 切换路由时,我们可以看到页面的 url 改变了,并且向服务端发起请求重新刷新了页面。

但在单页面应用中,我们其实不希望这样,那我们就需要做出以下修改:

1. 注册 click 事件覆盖超链接默认向服务器发起请求的行为

这里如果不加 e.preventDefault(),则点击事件会让整个页面向服务器发起请求并刷新页面,也不要删除 href 属性,删掉这个属性会导致超链接的样式变得和普通文本一样。

2. 使用 history.pushState 方法改变地址栏的路径,但是不会向服务器发起请求

pushState 接收三个参数,分别是 data, title(网页的标题), url(要转跳的路径),暂时只设置 url 即可。

3. 修改 current 路由地址,触发 router-view 的更新

VueRouter 的属性 data 是响应式的,在 router-view 中我们根据当前地址 data.current 来决定显示的组件。那么我们只需要改变 data.current 的值,就可以同步更新 router-view 的组件了。

那么怎么在 click 方法里,修改 data.current 的值呢?首先明确 click 方法中的 this 就是 router-view 组件,它是一个 vue 实例,对于所有的 vue 实例都具有 $router 属性,其中的包含 current 表示当前路由地址。

js 复制代码
render(h){
    return h("a",{
        attrs:{ href:'' },
        on:{
            // 不要加小括号,否则会变成立即调用
            click:this.clickhander
        } 
    },[this.$slots.default])
},
methods:{
    clickhander(e){
        history.pushState({},"",this.to)
        this.$router.data.current=this.to
        e.preventDefault()
    }
}

完整代码

js 复制代码
initComponent(Vue){
    Vue.component("router-link",{
        props:{
            to:String
        },
        render(h){
            return h("a",{
                attrs:{
                    href:''
                },
                on:{
                    click:this.clickhander
                }
            },[this.$slots.default])
        },
        methods:{
            clickhander(e){
                history.pushState({},"",this.to)
                this.$router.data.current=this.to
                e.preventDefault()
            }
        }
    })
    const self = this
    Vue.component("router-view",{
        render(h){
            const cm=self.routeMap[self.data.current]
            return h(cm)
        }
    })

}
相关推荐
前端老宋Running2 分钟前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔3 分钟前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654265 分钟前
Android的自定义View
前端
WILLF5 分钟前
HTML iframe 标签
前端·javascript
枫,为落叶23 分钟前
Axios使用教程(一)
前端
小章鱼学前端28 分钟前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah29 分钟前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
流星稍逝31 分钟前
手搓一个简简单单进度条
前端
倚栏听风雨1 小时前
详解 TypeScript 中,async 和 await
前端
小皮虾1 小时前
告别服务器!小程序纯前端“图片转 PDF”工具,隐私安全又高效
前端·javascript·微信小程序