50 linhas de código levam você a uma compreensão preliminar do princípio do Vue-Router

texto

Uso básico do 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 />
复制代码

O código acima é familiar para todos, não sei se você tem algumas perguntas.

  • Vue.use()O que é isso? O que ele fez?
  • main.jsPor que você quer montá router-lo ?
  • router-linke de onde vieram router-viewesses dois componentes?

Por favor, leia o conteúdo a seguir com as perguntas acima.

Conhecimento básico

Vue.use()

Vamos dar uma olhada na explicação no site oficial do Vue .

Instale o plug-in Vue.js. Se o plugin for um objeto, um método de instalação deve ser fornecido. Se o plugin for uma função, ele será usado como método de instalação. Quando o método install é chamado, o Vue é passado como parâmetro.

Este método precisa ser chamado antes de chamar new Vue().

Quando o método de instalação é chamado várias vezes pelo mesmo plugin, o plugin será instalado apenas uma vez.

Ou seja, se você deseja implementar um plugin, deve implementar o método de instalação do plugin.

Vue.mixin

Em seguida, também precisamos entender um conceito de mixagem, que é a API global do Vue Vue.mixin .

Registrar um mixin globalmente afeta cada instância Vue criada após o registro. Os autores de plugins podem usar mixins para injetar comportamento personalizado em componentes. Não recomendado para uso no código do aplicativo.

análise de demanda

Antes de começarmos, primeiro realizamos uma onda de análise de demanda, para que possamos fazer melhor a próxima coisa.

  • Implementar plugin vue
  • Analisar a opção de rotas
  • monitorar alterações de URL
    • html5 history api /login
    • hash xx.html#/login
  • Implementar componentes globais
    • <router-link>
    • <router-view>

Código

Implemente o plugin 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;
复制代码

Acho que você gosta

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