📁 Vuex/Vue 您所在的位置:网站首页 vueutil 📁 Vuex/Vue

📁 Vuex/Vue

#📁 Vuex/Vue| 来源: 网络整理| 查看: 265

实现 install 方法生成路由表路由模式hashh5transitionTorouter-viewrouter-link钩子

2020vue-b阶段课程(架构)\第2章 手写Vue-Router

使用 vue/cli 初始化一个带 vue-router 的项目

import Vue from "vue";import VueRouter from "vue-router";import Home from "../views/Home.vue";Vue.use(VueRouter); // 使用Vue-Router插件const routes = [ { path: "/", name: "Home", component: Home, }, { path: "/about", name: "About", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ "../views/About.vue"), },];const router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes,});export default router; // 创建Vue-router实例,将实例注入到main.js中 import Vue from "vue";import App from "./App.vue";import router from "./router";new Vue({ router, render: (h) => h(App),}).$mount("#app");

实现 install 方法Vue.use = funciton(plugin, options){ plugin.install(this, options)}

实现 install 方法

export default function install(Vue) { _Vue = Vue; Vue.mixin({ // 给每个组件添加_routerRoot属性 beforeCreate() { if (this.$options.router) { // 如果有 router 属性说明是根实例 this._routerRoot = this; this._router = this.$options.router; this._router.init(this); // 初始化路由,这里的 this 指向的是根实例 } else { // 儿子找爸爸 this._routerRoot = this.$parent && this.$parent._routerRoot; } } })}

为了让所有子组件都有_routerRoot(根实例),所有组件都可以通过this._routerRoot._router获取用户的实例化路由对象。

生成路由表class VueRouter { constructor(options) { // 生成路由映射表 // match 匹配方法 // addRoutes 动态添加路由 this.matcher = createMatcher(options.routes || []); }}VueRouter.install = install; import { createRouteMap } from "./create-route-map";export function createMatcher(routes) { // 路径和记录匹配 / record let { pathMap } = createRouteMap(routes); // 创建映射表 v function match(path) { return pathMap[path]; }; function addRoutes(routes) { createRouteMap(routes, pathMap); } return { addRoutes, match }} export function createRouteMap(routes, oldPathMap) { // 如果有oldPathMap 我需要将 routes格式化后放到 oldPathMap 中 // 如果没有传递 需要生成一个映射表 let pathMap = oldPathMap || {} routes.forEach(route => { addRouteRecord(route, pathMap); }) return { pathMap }}function addRouteRecord(route, pathMap, parent) { let path = parent ? `${parent.path}/${route.path}` : route.path; // 将记录 和 路径关联起来 let record = { // 最终路径 会匹配到这个记录,里面可以自定义属性等 path, component: route.component, // 组件 props: route.props || {}, parent } pathMap[path] = record; route.children && route.children.forEach(childRoute => { addRouteRecord(childRoute, pathMap, record); // 在循环儿子的时候将父路径也同时传入,目的是为了在子路由添加的时候可以拿到父路径 })}

路由模式 this.mode = options.mode || 'hash'; switch (this.mode) { case 'hash': this.history = new Hash(this) break case 'history': this.history = new HTML5History(this); break }// ... init(app) { const history = this.history; // 初始化时,应该先拿到当前路径,进行匹配逻辑 // 让路由系统过度到某个路径 const setupHashListener = () => { history.setupListener(); // 监听路径变化 } history.transitionTo( // 父类提供方法负责跳转 history.getCurrentLocation(), // 子类获取对应的路径 // 跳转成功后注册路径监听,为视图更新做准备 setupHashListener ) }

hashexport default class Hash extends History { constructor(router) { super(router); // hash路由初始化的时候 需要增加一个默认hash值 #/ ensureHash(); } getCurrentLocation() { return getHash(); } setUpListener() { window.addEventListener('hashchange', () => { // hash 值变化 再去切换组件 渲染页面 this.transitionTo(getHash()); }) }}function ensureHash() { if (!window.location.hash) { window.location.hash = '/'; }}function getHash() { return window.location.hash.slice(1);}

高版本浏览器可以用 popstate 代替 hashchange 事件,性能更好

h5import History from './base'export default class HTML5History extends History { constructor(router) { super(router); } getCurrentLocation() { return window.location.pathname;// 获取路径 } setUpListener() { window.addEventListener('popstate', () => { // 监听前进和后退 this.transitionTo(window.location.pathname); }) } pushState(location) { history.pushState({}, null, location); }} // 路由公共的方法都放在这 大家共用function createRoute(record, location) { // 创建路由 const matched = []; // 不停的去父级查找 if (record) { while (record) { matched.unshift(record); record = record.parent; } // /about/a => [about,aboutA] } return { ...location, matched }}export default class History { constructor(router) { this.router = router; // 有一个数据来保存路径的变化 // 当前没有匹配到记录 this.current = createRoute(null, { path: '/' }); // => {path:'/',matched:[]} } transitionTo(path, cb) { // 前端路由的实现原理 离不开hash h5 let record = this.router.match(path); // 匹配到后 this.current = createRoute(record, { path }); // 路径变化 需要渲染组件 响应式原理 // 我们需要将currrent属性变成响应式的,这样后续更改current 就可以渲染组件了 // Vue.util.defineReactive() === defineReactive // 我可以在router-view组件中使用current属性,如果路径变化就可以更新router-view了 cb && cb(); // 默认第一次cb是hashchange }}

init( )时 先跳转路径,然后开始监听路径变化

transitionTo

根据路径进行组件的渲染

transitionTo(path, cb) { // {path:'/',matched:[record]} // 前端路由的实现原理 离不开hash h5 let record = this.router.match(path); // 匹配到后 let route = createRoute(record, { path }); // 1.保证跳转的路径 和 当前路径一致 // 2.匹配的记录个数 应该和 当前的匹配个数一致 说明是相同路由 if (path === this.current.path && route.matched.length === this.current.matched.length) { return } // 在跳转前 我需要先走对应的钩子 // 修改current _route 实现跳转的 let queue = this.router.beforeHooks; const iterator = (hook,next) =>{ // 此迭代函数可以拿到对应的hook hook(route,this.current,next); } runQueue(queue,iterator,()=>{ this.updateRoute(route); cb && cb(); // 默认第一次cb是hashchange // 后置的钩子 }) // 更新current 需要重新渲染视图 // Vue.util.defineReactive(); // 如果 两次路由一致 不要跳转了 }

createRoute 返回的结果 {path:'about/a',matched:[{...'about' },{...'about/a' }]}

嵌套路由时 要写两层 router-view才可以,先渲染about再渲染about/a

需要将 current 属性变化成响应式的,后续 current 变化会更新视图

// vuex中的 state 在哪里使用就会收集对应的 watcher// current 里面的属性在哪使用,就会收集对应的 watcherVue.util.defineReactive(this,'_route',this._router.history.current);

要改变_route需要传回调函数进去,对_route重新赋值

history.listen((route)=>{ // 监听 监听如果current变化了 就重新的给 _route赋值 app._route = route; })

组件

Object.defineProperty(Vue.prototype,'$router',{ // 方法 get(){ return this._routerRoot._router } }) Object.defineProperty(Vue.prototype,'$route',{ // 属性 get(){ return this._routerRoot._route } }); Vue.component('router-link',RouterLink) Vue.component('router-view',RouterView)

router-viewexport default { functional:true, render(h,{parent,data}){ // current = {matched:[]} .$route // data里面我可以增加点标识 // 内部current变成了响应式的 // 真正用的是$route this.$route = current; current = xxx let route = parent.$route; // 获取current对象 // 依次的将matched 的结果赋予给每个router-view // 父 * 父 * -> 父 * -> 子 * let depth = 0; while (parent) { // 1.得是组件 if(parent.$vnode && parent.$vnode.data.routerView ){ depth++; } parent = parent.$parent; // 不停的找父亲 } // 两个router-view [ /about /about/a] /about/a let record = route.matched[depth]; // 默认肯定先渲染第一层 if(!record){ return h() // 空 } // 渲染匹配到的组件,这里一直渲染的是第一个 data.routerView = true; return h(record.component, data); // }}

嵌套路由,record 是数组,通过找有几级父亲及渲染标致,判断该渲染 record 第几条记录

router-linkexport default { functional: true, // 函数式组件, 会导致render函数中没有this了 // 正常组件是一个类 this._init() 如果是函数式组件就是一个普通函数 props: { // 属性校验 to: { type: String, required: true } }, // render的第二个函数 是内部自己声明一个对象 render(h, { props, slots, data, parent }) { // render 方法和 template 等价的 -> template语法需要被编译成render函数 const click = () => { // 组件中的$router parent.$router.push(props.to) } // jsx 和 react语法一样 < 开头的表示的是html {} js属性 return { slots().default } }}

钩子function runQueue(queue,iterator,cb){ function step(index){ if(index >= queue.length) return cb(); let hook = queue[index]; iterator(hook,()=>step(index+1)); // 第二个参数什么时候调用就走下一次的 } step(0);}


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有