一、vue-router
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。
安装:
vue add router
核心步骤:
步骤一:使用vue-router插件,router.js
import Router from 'vue-router'
Vue.use(Router)
步骤二:创建Router实例,router.js
export default new Router({...})
步骤三:在根组件上添加该实例,main.js
import router from './router'
new Vue({
router,
}).$mount("#app");
步骤四:添加路由视图,App.vue
<router-view></router-view>
导航
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
二、vue-router源码实现
路由是什么
路由其实就是一个指向,把路径指向相应的组件地址
路由的原理
根据hash值的不同展示 不同的组件
需求分析
- 作为一个插件存在:实现 VueRouter 类和 install 方法
- 实现两个全局组件:router-view用于显示匹配组件的内容,router-link用于跳转
- 监控url的变化:监听 hashChange 或 propstate 事件
- 响应最新url:创建一个响应式的属性current,当它改变时获取相对应组件并显示
2.1实现一个插件:创建VueRouter类和install方法
2.1.1创建krouter/kvue-router.js
let Vue; //引用构造函数,KVueRouter中要使用
//保存选项
class KVueRouter{
constructor(options){
this.$options = options;
}
}
//插件:实现install方法,注册$router
KVueRouter.install = function(_Vue){
//保存构造函数,在 KVueRouter 里面使用
Vue = _Vue;
// 挂载$router
//怎么获取根实例中的router选项
//全局混入
Vue.mixin({//混入到所有的组件实例
beforeCreate(){
// 输入this,页面中有多少个组件,就有多少个this
// 根组件、app组件、Home组件 //见图片
// 混入也可以进行全局注册。使用时格外小心!
// 一旦使用全局混入,它将影响每一个之后创建的 Vue 实例
// console.log(this);//this指的是组件实例
// 确保根实例的时候才执行
/**
new Vue({
router,
render: h => h(App)
}).$mount('#app');
*/
// 只有main.js中有router,其他组件都没有这个选项
// 只有根组件拥有router选项
if(this.$options.router){ //判断是不是根实例
vm.$route
Vue.prototype.$router = this.$options.router;
}
}
});
}
export default KVueRouter;
为什么要用混入方式写?
主要原因是use代码在前,Router实例创建在后,而install逻辑又需要用 到该实例
根组件、app组件、Home组件
2.1.2创建krouter/index.js
import Vue from 'vue'
import VueRouter from './kvue-router'//用的是自己写的kvue-router
import Home from '../views/Home.vue'
//1.安装
// 2.应用插件
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
// 3.创建实例
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
2.1.3 main.js
import Vue from 'vue'
import App from './App.vue'
// import router from './router'
//使用自定义
import router from './Krouter/index'
//为什么router实例挂载在这里?
/**
//Vue.prototype.$router = router
*/
new Vue({
router,
render: h => h(App)
}).$mount('#app');
2.2创建router-link和router-view
krouter/kvue-router.js
//与Vue.mixin同级
//任务2:实现两个全局组件 router-link 和 router-view
//全局注册对象 Vue.component
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
/**
* template: '<a>aaa</a>'
* 不能使用,为什么呢?
* 报错:You are using the runtime-only build of Vue where the template compiler is not available.
* Either pre-compile the templates into render functions, or use the compiler-included build.
* 在模板编译器不可用的情况下,您使用的是仅运行时版本的Vue。
* 可以将模板预编译为呈现函数,也可以使用编译器包含的内部版本。
* 解决方式:
* 可以使用 render 函数
* 在项目配置的时候,默认 npm 包导出的是运行时构建,即 runtime 版本,不支持编译 template 模板。
* vue 在初始化项目配置的时候,有两个运行环境配置的版本:Compiler 版本、Runtime 版本。
*/
render(h) { //在Runtime 版本 ,只能使用render函数来描述组件
//<a href="#/about">111</a>
// <router-link to="/about">xxx</router-link>
// h(tag,data,children)
console.log(this.$slots);
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
}
});
Vue.component('router-view', {});
使用
template: '<a>aaa</a>'
报错
You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
在模板编译器不可用的情况下,您使用的是仅运行时版本的Vue。可以将模板预编译为呈现函数,也可以使用编译器包含的内部版本。
原因分析:
在项目配置的时候,默认 npm 包导出的是运行时构建,即 runtime 版本(运行时版本),不支持编译 template 模板。
vue 在初始化项目配置的时候,有两个运行环境配置的版本:Compiler 版本、Runtime 版本(运行时版本)。
解决方法:
不需要编译器
new Vue({
render (h) {
return h('div', this.hi)
}
})
2.3 监控url变化
定义响应式路由的current属性,监听hashchange事件
class KVueRouter {
constructor(options) {
this.$options = options;
console.log(this.$options);
//需要创建响应式的current属性 Vue监听current变量重要执行者
//利用Vue提供的defineReactive做响应化
//这样将来current变化的时候,依赖的组件会重新render
Vue.util.defineReactive(this,'current','/');
// this.current = '/';
//监控url变化
//给window注册一个hash值改变事件, 也就是说只要hash值发生了改变, 就会触发这个事件.
window.addEventListener('hashchange',this.onHashChange.bind(this));
//页面刷新
window.addEventListener('load',this.onHashChange.bind(this));
}
onHashChange(){
console.log(window.location.hash);
this.current = window.location.hash.slice(1); //去掉 #
}
}
//Vue.js 的插件应该暴露一个 install 方法。
//这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
KVueRouter.install = function (_Vue) {
//保存构造函数,在 KVueRouter 里面使用
Vue = _Vue;
Vue.mixin({//混入到所有的组件实例
beforeCreate() {
//只有main.js中有router,其他组件都没有这个选项
if (this.$options.router) { //判断是不是根实例
Vue.prototype.$router = this.$options.router;
}
}
});
//任务2:实现两个全局组件 router-link 和 router-view
//全局注册对象 Vue.component
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
render(h) {
console.log(this.$slots);
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
}
});
Vue.component('router-view', {
render(h){
// 动态获取path对应的component
let component = null;
this.$router.$options.routes.forEach(route => {
if (route.path === this.$router.current) {
component = route.component
}
});
return h(component);
}
});
}
2.4 提前处理路由表
提前处理路由表避免每次都循环
class KVueRouter {
constructor(options) {
......
//创建一个路由的映射表
//缓存path和route映射关系
this.routeMap = {}
options.routes.forEach(route => {
this.routeMap[route.path] = route
})
}
......
}
使用
Vue.component('router-view', {
render(h){
//获取path对应的component
// let component = null;
// this.$router.$options.routes.forEach(route=>{
// if(route.path == this.$router.current){
// component = route.component;
// }
// })
//
const {routeMap,current} = this.$router;
const component = routeMap[current].component || null;
return h(component);
}
});
2.5整理
krouter/index.js
import Vue from 'vue'
import VueRouter from './kvue-router' 用的是自己写的kvue-router
import Home from '../views/Home.vue'
//1.安装
// 2.应用插件
Vue.use(VueRouter)
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')
}
]
// 3.创建实例
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
// import router from './router'
//使用自定义
import router from './Krouter/index'
new Vue({
router,
render: h => h(App)
}).$mount('#app');
krouter/kvue-router.js
import Link from './Krouter-link'
import View from './Krouter-view'
let Vue;
// 路由是什么:
// 路由其实就是一个指向,把路径指向相应的组件地址
// 路由的原理:
// 根据hash值的不同展示 不同的组件
/**
* vue-router源码实现
* 需求分析
* 1.作为一个插件存在:实现 VueRouter 类和 install 方法
* 2.实现两个全局组件:router-view用于显示匹配组件的内容,router-link用于跳转
* 3.监控url的变化:监听 hashChange 或 propstate 事件
* 4.响应最新url:创建一个响应式的属性current,当它改变时获取相对应组件并显示
*/
//1.实现一个插件:挂载 $router
class KVueRouter {
constructor(options) {
this.$options = options;
console.log(this.$options);
//需要创建响应式的current属性 Vue监听current变量重要执行者
//利用Vue提供的defineReactive做响应化
//这样将来current变化的时候,依赖的组件会重新render
Vue.util.defineReactive(this,'current','/');
// this.current = '/';
//监控url变化
//给window注册一个hash值改变事件, 也就是说只要hash值发生了改变, 就会触发这个事件.
window.addEventListener('hashchange',this.onHashChange.bind(this));
//页面刷新
window.addEventListener('load',this.onHashChange.bind(this));
//创建一个路由的映射表
this.routeMap = {}
options.routes.forEach(route => {
this.routeMap[route.path] = route
})
}
onHashChange(){
console.log(window.location.hash);
this.current = window.location.hash.slice(1); //去掉 #
}
}
//Vue.js 的插件应该暴露一个 install 方法。
//这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
KVueRouter.install = function (_Vue) {
//保存构造函数,在 KVueRouter 里面使用
Vue = _Vue;
//挂载$router
//怎么获取根实例中的router选项
//全局混入
Vue.mixin({//混入到所有的组件实例
beforeCreate() {
/**
* 输入this,页面中有多少个组件,就有多少个this
* 根组件、app组件、Home组件
* 混入也可以进行全局注册。使用时格外小心!
* 一旦使用全局混入,它将影响每一个之后创建的 Vue 实例
* console.log(this);//this指的是组件实例
*/
//确保根实例的时候才执行
/**
new Vue({
router,
render: h => h(App)
}).$mount('#app');
*/
//只有main.js中有router,其他组件都没有这个选项
if (this.$options.router) { //判断是不是根实例
Vue.prototype.$router = this.$options.router;
}
}
});
//任务2:实现两个全局组件 router-link 和 router-view
//全局注册对象 Vue.component
Vue.component('router-link', Link);
Vue.component('router-view', View);
}
export default KVueRouter;
krouter/krouter-link.js
export default {
props: {
to: {
type: String,
required: true
}
},
/**
* template: '<a>aaa</a>'
* 不能使用,为什么呢?
* 报错:You are using the runtime-only build of Vue where the template compiler is not available.
* Either pre-compile the templates into render functions, or use the compiler-included build.
* 在模板编译器不可用的情况下,您使用的是仅运行时版本的Vue。
* 可以将模板预编译为呈现函数,也可以使用编译器包含的内部版本。
* 解决方式:
* 可以使用 render 函数
* 在项目配置的时候,默认 npm 包导出的是运行时构建,即 runtime 版本,不支持编译 template 模板。
* vue 在初始化项目配置的时候,有两个运行环境配置的版本:Compiler 版本、Runtime 版本。
*/
render(h) { //在Runtime 版本 ,只能使用render函数来描述组件
//<a href="#/about">111</a>
// <router-link to="/about">xxx</router-link>
// h(tag,data,children)
console.log(this.$slots);
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
}
}
krouter/krouter-view.js
export default {
render(h){
//获取path对应的component
const {routeMap,current} = this.$router;
const component = routeMap[current].component || null;
return h(component);
}
}
三、Vuex
Vuex 集中式 存储管理应用的所有组件的状态,并以相应的规则保证状态以 可预测 的方式发生变化。
安装vuex
vue add vuex
核心概念
状态 - state
state保存应用状态
export default new Vuex.Store({
state:{counter:0}
})
状态变更 - mutations
mutations用于修改状态,store.js
export default new Vuex.Store({
mutations: {
add(state){
state.counter++;
}
}
})
派生状态 - getters
从state派生出新状态,类似计算属性
export default new Vuex.Store({
getters:{
doubleCounter(state){
return state.counter *2
}
},
})
动作 - actions
添加业务逻辑,类似于controller
export default new Vuex.Store({
actions: {
add({commit}){
setTimeout(()=>{
commit('add');
},1000);
}
},
})
测试代码
<p @click="$store.commit('add')">counter:{{$store.state.counter}}</p>
<p @click="$store.dispatch('add')">async counter:{{$store.state.counter}}</p>
<p>double counter:{{$store.getters.doubleCounter}}</p>
四、vuex原理解析
任务分析
-
实现一个插件:声明store类,挂载$store
-
Store具体实现:
- 创建响应式的state,保存mutations、actions和getters
- 实现commit根据用户传入type执行对相应的mutation
- 实现dispatch根据用户传入type执行对应action,同时传递上下文
4.1 初始化:Store声明、install实现,kvuex.js
//保存构造函数的引用,避免import
let Vue;
class Store {
constructor(options) {
//响应化处理state
this.state = new Vue({
data:options.state
});
}
}
//install插件
//install方法会被vue调用,vue调用的时候,就把自己直接作为参数传进入了
function install(_Vue) {
Vue = _Vue;
//混入
Vue.mixin({
beforeCreate() {
if (this.$options.store) { //this指向的是当前vuex这个对象
Vue.prototype.$store = this.$options.store
}
},
})
}
//Vuex { }
export default {
Store,//类
install
}
4.2 实现commit:根据用户传入的type获取并执行对应mutation,kvuex.js
//保存构造函数的引用,避免import
let Vue;
class Store {
constructor(options) {
//1.保存用户配置的mutations选项
this._mutations = options.mutations || {};
}
//store.commit('add',1)
//type:mutation类型
//payload :载荷,是参数
commit(type, payload) {
/**
* 调用dispath的时候,报这个错误,
* Uncaught TypeError: Cannot read property '_mutations' of undefined
* this不对,
* 绑定commit、dispatch的上下文为store实例
* this.commit = this.commit.bind(this);
* this.dispatch = this.dispatch.bind(this);
*/
const entry = this._mutations[type];
if (!entry) {
console.error(`unknown mutation type:${type}`);
return;
}
//指定上下文为Store实例
//传递state给mutation
entry(this.state, payload);
}
}
4.3 实现actions:根据用户传入的type获取并执行mutation
//保存构造函数的引用,避免import
let Vue;
class Store {
constructor(options) {
//保存用户编写的actions选项
this._actions = options.actions || {};
绑定commit上下文否则action中调用commit时可能出问题!!
//同时也把action绑了,因为action可以互调
const store = this;
const {commit,action} = store;
this.commit = function boundCommit(type,payload){
commit.call(store,type,payload);
}
this.action= function boundAction(type,payload){
action.call(store,type,payload);
}
}
/**
* 调用dispath的时候,commit函数中报这个错误,
* Uncaught TypeError: Cannot read property '_mutations' of undefined
* this不对,
* 绑定commit、dispatch的上下文为store实例
* this.commit = this.commit.bind(this);
* this.dispatch = this.dispatch.bind(this);
*/
//store.dispatch
dispatch(type, payload) {
const entry = this._actions[type];
if (!entry) {
console.error(`unknown action type: ${type}`); return
}
// 异步结果处理常常需要返回Promise
return entry(this, payload);
}
}
4.4 防止用户直接修改state
//保存构造函数的引用,避免import
let Vue;
class Store {
constructor(options) {
// //响应化处理state
// this.state = new Vue({
// data:options.state
// });
// 保护state ,防止用户直接修改state
// 1.加 $$
this._vm = new Vue({
data: {
//加两个$,Vue不做代理,对外部是隐藏的
$$state: options.state
}
});
}
//2.start存取器
get state(){
console.log(this._vm);
return this._vm._data.$$state
}
//3.只读
set state(v){
console.error('你造吗?你这样不好')
}
//4.参考源码的严格模式
}
4.5 整理
kstore/index.js
import Vue from 'vue'
import Vuex from './kvuex' 引入自定义
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter:0
},
getters:{
doubleCounter(state){
return state.counter *2
}
},
mutations: {
add(state){
state.counter++;
}
},
actions: {
//解构上下文
add({commit}){
setTimeout(()=>{
commit('add');
},1000);
}
},
modules: {
}
})
main.js
import Vue from 'vue'
import App from './App.vue'
// import store from './store'
//使用自定义
import store from './kstore/index'
new Vue({
store,
render: h => h(App)
}).$mount('#app');
kstore/kvuex.js
//保存构造函数的引用,避免import
let Vue;
class Store {
constructor(options) {
//1.保存mutations
this._mutations = options.mutations || {};
this._actions = options.actions || {};
// //响应化处理state
// this.state = new Vue({
// data:options.state
// });
// 保护state ,防止用户直接修改state
// 1.加 $$
this._vm = new Vue({
data: {
//加两个$,Vue不做代理,对外部是隐藏的
$$state: options.state
}
});
//-------------------------
//绑定commit、dispatch的上下文为store实例
//this.commit = this.commit.bind(this);
//this.dispatch = this.dispatch.bind(this);
//和下面作用一样
// 绑定commit上下文否则action中调用commit时可能出问题!!
// 同时也把action绑了,因为action可以互调
const store = this
const {commit, action} = store
this.commit = function boundCommit(type, payload) {
commit.call(store, type, payload)
}
this.action = function boundAction(type, payload) {
return action.call(store, type, payload)
}
}
//2.start存取器
get state(){
console.log(this._vm);
return this._vm._data.$$state
}
//3.只读
set state(v){
console.error('你造吗?你这样不好')
}
//store.commit('add',1)
//type:mutation类型
//payload :载荷,是参数
commit(type, payload) {
/**
* 调用dispathc的时候,报这个错误,
* Uncaught TypeError: Cannot read property '_mutations' of undefined
* this不对,
* 绑定commit、dispatch的上下文为store实例
* this.commit = this.commit.bind(this);
* this.dispatch = this.dispatch.bind(this);
*/
const entry = this._mutations[type];
if (!entry) {
console.error(`unknown mutation type: ${type}`);
return
}
//指定上下文为Store实例
//传递state给mutation
entry(this.state, payload);
}
//store.dispatch
dispatch(type, payload) {
const entry = this._actions[type];
if (!entry) {
console.error(`unknown action type: ${type}`);
return
}
// 异步结果处理常常需要返回Promise
return entry(this, payload);
}
}
//install插件
//install方法会被vue调用,vue调用的时候,就把自己直接作为参数传进入了
function install(_Vue) {
Vue = _Vue;
//混入
Vue.mixin({
beforeCreate() {
if (this.$options.store) { //this指向的是当前vuex这个对象
Vue.prototype.$store = this.$options.store
}
},
})
}
//Vuex { }
export default {
Store,//类
install
}
五、作业
- 尝试去看看vue-router的源码,并解答:嵌套路由的解决方式
- 尝试去看看vuex的源码,并实现getters的实现
- 了解vue数据响应原理为下节课做准备