【 web高级 01vue 】 vue直播课02 vue全家桶原理剖析

一、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值的不同展示 不同的组件

需求分析

  1. 作为一个插件存在:实现 VueRouter 类和 install 方法
  2. 实现两个全局组件:router-view用于显示匹配组件的内容,router-link用于跳转
  3. 监控url的变化:监听 hashChange 或 propstate 事件
  4. 响应最新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 状态、数据
  • mutations 更改状态的函数
  • actions 异步操作
  • store 包含以上概念的容器

状态 - 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
}

五、作业

  1. 尝试去看看vue-router的源码,并解答:嵌套路由的解决方式
  2. 尝试去看看vuex的源码,并实现getters的实现
  3. 了解vue数据响应原理为下节课做准备

六、vue-router的使用及实现原理

猜你喜欢

转载自blog.csdn.net/weixin_42580704/article/details/113943244