50 líneas de código lo llevan a una comprensión preliminar del principio de Vue-Router

texto

Uso básico de Vue-Router.

    // router/index.js
    import Vue from 'vue'l
    import Router from 'vue-router';

    Vue.use(Router);

    export default new Router({...});
复制代码
// main.js
import Vue from 'vue';
import router from './router';

new Vue({
	router,
	render: (h) => h(App),
}).$mount('#app');
复制代码
<!-- App.vue -->
<div id="nav">
	<router-link to="/">Home</router-link> |
	<router-link to="/about">About</router-link>
</div>
<router-view />
复制代码

El código anterior es familiar para todos, no sé si tiene algunas preguntas.

  • Vue.use()¿qué es? ¿Qué hizo él?
  • main.js¿Por qué quieres routermontarlo ?
  • router-linky ¿de dónde vienen router-viewestos dos componentes?

Lea el siguiente contenido con las preguntas anteriores.

Conocimiento básico

Vue.use()

Echemos un vistazo a la explicación en el sitio web oficial de Vue .

Instale el complemento Vue.js. Si el complemento es un objeto, se debe proporcionar un método de instalación. Si el complemento es una función, se utilizará como método de instalación. Cuando se llama al método de instalación, Vue se pasa como un parámetro.

Este método debe llamarse antes de llamar a new Vue().

Cuando el mismo complemento llama varias veces al método de instalación, el complemento solo se instalará una vez.

Es decir, si desea implementar un complemento, debe implementar el método de instalación del complemento.

Vue.mixin

A continuación, también debemos comprender un concepto de mezcla, que es la API global de Vue , Vue.mixin .

El registro de un mixin afecta globalmente a todas las instancias de Vue creadas después del registro. Los autores de complementos pueden usar mixins para inyectar un comportamiento personalizado en los componentes. No se recomienda su uso en el código de la aplicación.

análisis de la demanda

Antes de comenzar, primero realizamos una ola de análisis de demanda, para que podamos hacer mejor lo siguiente.

  • Implementar complemento vue
  • Analizar la opción de rutas
  • controlar los cambios de URL
    • html5 history api /login
    • hash xx.html#/login
  • Implementar componentes globales
    • <router-link>
    • <router-view>

Código

Implementar el complemento Vue

保存 Vue 的局部变量,便于 VueRouter 使用。

至于为什么要用混入在 beforeCreate 声明周期中执行 VueRouter 的初始化是因为:

Vue.use 会执行插件的 install 方法,这时候 Vue 的实例还不存在,所以把操作放在根组件的 beforeCreate 里执行。

并且要保证 router 只在根组件挂载一次。

let Vue;

class VueRouter {}

Vue.install = function (_Vue) {
	Vue = _Vue;

	Vue.mixin({
		beforeCreate() {
			let router = this.$options.router;
			if (router) {
				Vue.prototype.$router = router;
				router.init();
			}
		},
	});
};
复制代码

解析 routes 选项

我们希望得到这样的数据,便于根据 path 来找到对应的 Component

    {
        '/': {
            path: '',
            component: Index,
            ...
        },
        '/about': {
            path: '/about',
            component: About,
            ...
        }
    }
复制代码

在做一些初始化的操作后,我们对 routes 做个简单的处理。

    class VueRouter {
        constructor(options) {
            this.$options = options;
            this.routeMap = {};
        }

        init() {
            this.createRouteMap();
            ...
        }

        createRouteMap() {
            this.$options.routes.forEach(route => {
                this.routeMap[route.path] = route;
            })
        }
    }
复制代码

监控 url 变化

这里有几点需要注意。

  • this 的指向问题。
  • 要保证 current 是响应式的。
  • 要拿到 # 之后的部分便于和组件对应。

利用了 vue 做数据响应式,至于为什么 current 要是响应式的,那就继续往下看喽。

    class VueRouter {
        constructor(options) {
            ...
            this.app = new Vue({
                data: { current: '/' }
            });
        }

        init() {
            ...
            this.bindEvents();
        }

        bindEvents() {
            window.addEventListener('hashchange', this.onHashChange.bind(this));
        }

        onHashChange() {
            this.app.current = window.location.hash.slice(1) || '/';
        }
    }
复制代码

实现全局组件

Vue.component

  • 参数

    • {string} id
    • {Function | Object} [definition]
  • 用法

    注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称

    // 注册组件,传入一个扩展过的构造器
    Vue.component(
    	'my-component',
    	Vue.extend({
    		/* ... */
    	})
    );
    
    // 注册组件,传入一个选项对象 (自动调用 Vue.extend)
    Vue.component('my-component', {
    	/* ... */
    });
    
    // 获取注册的组件 (始终返回构造器)
    var MyComponent = Vue.component('my-component');
    复制代码

router-link

router-link 这个组件在这里的实现很简单,只是封装了 a 标签。

我们希望得到的东西应该是这样的:

    <router-link to="/">首页</router-link>
    // 转换成
    <a href="/">首页</a>
复制代码

下面的代码里有这样几个知识点

  • render 里的 this 指向的是当前组件的实例。
  • render 里面实际上是可以写 JSX 的,不过最后还是会被 loader 转换成 VNode
  • href 是属性,所以要在 attrs里。
  • this.$slots 是插槽相关的知识,default可以拿到标签里的内容。
    class VueRouter {
        ...

        init() {
            ...
            this.initComponent();
        }

        initComponent() {
            Vue.component('router-link', {
                props: {
                    to: String
                },
                render(h) {
                    return h(
                        'a',
                        attrs: {
                            href: `#${ this.to }`
                        },
                        [this.$slots.default]
                    )
                }
            })
        }
    }
复制代码

补充知识 createElement

render 里的 h 函数实际上就是 createElement,它返回的就是大名鼎鼎的 VNode

// @returns {VNode}
createElement(
	// {String | Object | Function}
	// 一个 HTML 标签名、组件选项对象,或者
	// resolve 了上述任何一种的一个 async 函数。必填项。
	'div',

	// {Object}
	// 一个与模板中 attribute 对应的数据对象。可选。
	{},

	// {String | Array}
	// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
	// 也可以使用字符串来生成“文本虚拟节点”。可选。
	[
		'先写一些文字',
		createElement('h1', '一则头条'),
		createElement(MyComponent, {
			props: {
				someProp: 'foobar',
			},
		}),
	]
);
复制代码

router-view

现在来解释为什么数据要是响应式的:

只要 render 组件里面,用到了某个响应式的数据,那么这个响应式的数据发生了变化就会重新执行 render,这样页面才会刷新。

因为这里的 this 指向的是当前组件的实例,所以这里使用了 函数式组件 相关的知识请移步官方文档 函数式组件

通过第二个参数可以拿到 parent.$router 这样我们就拿到了 router 的实例。

    initComponent() {
        ...

        Vue.component('router-view', {
            functional: true,
            render(h, { parent }) {
                const router = parent.$router;
                return h(router.routeMap[router.app.current].component)
            }
        })
    }
复制代码

完整代码

let Vue;

class VueRouter {
	constructor(options) {
		this.$options = options;
		this.routeMap = {};
		this.app = new Vue({ data: { current: '/' } });
	}

	init() {
		this.bindEvents();
		this.createRouteMap();
		this.initComponent();
	}

	bindEvents() {
		window.addEventListener('hashchange', this.onHashChange.bind(this));
	}

	onHashChange() {
		this.app.current = window.location.hash.slice(1) || '/';
	}

	createRouteMap() {
		this.$options.routes.forEach((route) => {
			this.routeMap[route.path] = route;
		});
	}

	initComponent() {
		Vue.component('router-link', {
			props: {
				to: String,
			},
			render(h) {
				return h('a', { attrs: { href: `#${this.to}` } }, [
					this.$slots.default,
				]);
			},
		});

		Vue.component('router-view', {
			functional: true,
			render(h, { parent }) {
				const router = parent.$router;
				return h(router.routeMap[router.app.current].component);
			},
		});
	}
}

VueRouter.install = function (_Vue) {
	Vue = _Vue;
	Vue.mixin({
		beforeCreate() {
			let router = this.$options.router;
			if (router) {
				Vue.prototype.$router = router;
				router.init();
			}
		},
	});
};

export default VueRouter;
复制代码

Supongo que te gusta

Origin juejin.im/post/7085674827603771423
Recomendado
Clasificación