vue2必知必会源码篇

44 阅读3分钟

响应式原理

import { initMixin } from "./init";
import { lifecycleMixin } from "./lifecycle";
import { renderMixin } from "./render";
import {stateMixin} from './state'

function Vue(options){
    // options 为用户传入的选项
    this._init(options); // 初始化操作, 组件
}

// 扩展原型的
initMixin(Vue);
renderMixin(Vue); // _render
lifecycleMixin(Vue); // _update
stateMixin(Vue);
export default Vue;

import { compileToFunction } from "./compiler/index";
import { mountComponent } from "./lifecycle";
import { initState } from "./state";
export function initMixin(Vue) { // 表示在vue的基础上做一次混合操作
    Vue.prototype._init = function(options) {
        // el,data
        const vm = this; // var that = this;
       
        vm.$options = options; // 后面会对options进行扩展操作

        // 对数据进行初始化 watch computed props data ...
        initState(vm); // vm.$options.data  数据劫持

        if(vm.$options.el){
            // 将数据挂载到这个模板上
            vm.$mount(vm.$options.el);
        }
    }
    Vue.prototype.$mount = function (el) {
        const vm = this;
        const options = vm.$options
        el = document.querySelector(el);
        vm.$el = el;
        // 把模板转化成 对应的渲染函数 =》 虚拟dom概念 vnode =》 diff算法 更新虚拟dom =》 产生真实节点,更新
        if(!options.render){ // 没有render用template,目前没render
            let template = options.template;
            if(!template && el){ // 用户也没有传递template 就取el的内容作为模板
                template = el.outerHTML;
                let render = compileToFunction(template);
                options.render = render;
            }
        }
        // options.render 就是渲染函数
        // 调用render方法 渲染成真实dom 替换掉页面的内容

        mountComponent(vm,el); // 组件的挂载流程
    }
}
import { observe } from "./observer/index"; // node_resolve_plugin
import { isFunction } from "./utils";

export function initState(vm) { // 状态的初始化
    const opts = vm.$options;
    if (opts.data) {
        initData(vm);
    }
    // if(opts.computed){
    //     initComputed();
    // }
    // if(opts.watch){
    //     initWatch();
    // }
}

function proxy(vm,source,key){
    Object.defineProperty(vm,key,{
        get(){
            return vm[source][key];
        },
        set(newValue){
            vm[source][key] = newValue
        }
    })
}
function initData(vm) { //
    let data = vm.$options.data; // vm.$el  vue 内部会对属性检测如果是以$开头 不会进行代理
    // vue2中会将data中的所有数据 进行数据劫持 Object.defineProperty

    // 这个时候 vm 和 data没有任何关系, 通过_data 进行关联


    data = vm._data = isFunction(data) ? data.call(vm) : data;

    // 用户去vm.xxx => vm._data.xxx
    for(let key in data){ // vm.name = 'xxx'  vm._data.name = 'xxx'
        proxy(vm,'_data',key); 
    }

    observe(data);
}
import { isObject } from "../utils";
import { arrayMethods } from "./array";
import Dep from './dep'
// 1.如果数据是对象 会将对象不停的递归 进行劫持
// 2.如果是数组,会劫持数组的方法,并对数组中不是基本数据类型的进行检测

// 检测数据变化 类有类型 , 对象无类型

// 如果给对象新增一个属性不会触发视图更新  (给对象本身也增加一个dep,dep中存watcher,如果增加一个属性后,我就手动的触发watcher的更新)
class Observer { 
    constructor(data) { // 对对象中的所有属性 进行劫持

        this.dep = new Dep(); // 数据可能是数组或者对象

        Object.defineProperty(data,'__ob__',{
            value:this,
            enumerable:false // 不可枚举的
        })
        // data.__ob__ = this; // 所有被劫持过的属性都有__ob__ 
        if(Array.isArray(data)){  // 我希望数组的变化可以触发视图更新?
            // 数组劫持的逻辑
            // 对数组原来的方法进行改写, 切片编程  高阶函数
            data.__proto__ = arrayMethods;
            // 如果数组中的数据是对象类型,需要监控对象的变化
            this.observeArray(data);
        }else{
            this.walk(data); //对象劫持的逻辑 
        }
    }
    observeArray(data){ // 对我们数组的数组 和 数组中的对象再次劫持 递归了
        // [{a:1},{b:2}]


        // 如果数组里放的是对象类型,也做了观测,JSON.stringify() 也做了收集一来了
        data.forEach(item=>observe(item))
    }
    walk(data) { // 对象
        Object.keys(data).forEach(key => {
            defineReactive(data, key, data[key]);
        })
    }
}
// vue2 会对对象进行遍历 将每个属性 用defineProperty 重新定义 性能差

// {arr:[1,2,3]}  

function dependArray(value){
    for(let i = 0; i < value.length;i++){
        let current = value[i]; // current是数组里面的数组 [[[[[]]]]]
        current.__ob__ &&  current.__ob__.dep.depend();
        if(Array.isArray(current)){
            dependArray(current);
        }
    }
}

function defineReactive(data,key,value){ // value有可能是对象
    let childOb = observe(value); // 本身用户默认值是对象套对象 需要递归处理 (性能差)
    let dep = new Dep(); // 每个属性都有一个dep属性

   // 获取到了数组对应ob

    Object.defineProperty(data,key,{
        get(){
            // 取值时我希望将watcher和dep 对应起来
            if(Dep.target){ // 此值是在模板中取值的
                dep.depend() // 让dep记住watcher
                if(childOb){ // 可能是数组 可能是对象,对象也要收集依赖,后续写$set方法时需要触发他自己的更新操作
                    childOb.dep.depend(); // 就是让数组和对象也记录watcher

                    if(Array.isArray(value)){ //取外层数组要将数组里面的也进行依赖收集
                        dependArray(value);
                    }

                }
            }
            return value
        },
        set(newV){ 
            // todo... 更新视图

            if(newV !== value){
                observe(newV); // 如果用户赋值一个新对象 ,需要将这个对象进行劫持
                value = newV;
                dep.notify(); // 告诉当前的属性存放的watcher执行
            }
           
        }
    })
}

export function observe(data) {
    // 如果是对象才观测
    if (!isObject(data)) {
        return;
    }
    if(data.__ob__){
        return data.__ob__;
    }
    // 默认最外层的data必须是一个对象
    return new Observer(data)
}
import Watcher from "./observer/watcher";
import { nextTick } from "./utils";
import { patch } from "./vdom/patch";
export function lifecycleMixin(Vue) {
    Vue.prototype._update = function(vnode) {
        // 既有初始化 又又更新 
        const vm = this;

        vm.$el = patch(vm.$el, vnode);

    }
    Vue.prototype.$nextTick = nextTick
}
// 后续每个组件渲染的时候都会有一个watcher
export function mountComponent(vm, el) {

    // 更新函数 数据变化后 会再次调用此函数
    let updateComponent = () => {
        // 调用render函数,生成虚拟dom
        vm._update(vm._render()); // 后续更新可以调用updateComponent方法
        // 用虚拟dom 生成真实dom
    }

    // 观察者模式: 属性是“被观察者”  刷新页面:“观察者”
    // updateComponent();
    new Watcher(vm,updateComponent,()=>{
        console.log('更新视图了')
    },true); // 他是一个渲染watcher  后续有其他的watcher
}

watch与computed原理

import Dep from "./observer/dep";
import { observe } from "./observer/index"; // node_resolve_plugin
import Watcher from "./observer/watcher";
import { isFunction } from "./utils";


export function stateMixin(Vue) {
    Vue.prototype.$watch = function(key, handler, options = {}) {
        options.user = true; // 是一个用户自己写的watcher

        // vm,name,用户回调,options.user
        new Watcher(this, key, handler, options);
    }
}
export function initState(vm) { // 状态的初始化
    const opts = vm.$options;
    if (opts.data) {
        initData(vm);
    }
    if (opts.computed) {
        initComputed(vm, opts.computed);
    }
    if (opts.watch) { // 初始化watch
        initWatch(vm, opts.watch);
    }
}

function proxy(vm, source, key) {
    Object.defineProperty(vm, key, {
        get() {
            return vm[source][key];
        },
        set(newValue) {
            vm[source][key] = newValue
        }
    })
}

function initData(vm) { //
    let data = vm.$options.data; // vm.$el  vue 内部会对属性检测如果是以$开头 不会进行代理
    // vue2中会将data中的所有数据 进行数据劫持 Object.defineProperty

    // 这个时候 vm 和 data没有任何关系, 通过_data 进行关联


    data = vm._data = isFunction(data) ? data.call(vm) : data;

    // 用户去vm.xxx => vm._data.xxx
    for (let key in data) { // vm.name = 'xxx'  vm._data.name = 'xxx'
        proxy(vm, '_data', key);
    }

    observe(data);
}

function initWatch(vm, watch) { // Object.keys
    for (let key in watch) {
        let handler = watch[key];

        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i])
            }
        } else {
            createWatcher(vm, key, handler)
        }

    }
}

function createWatcher(vm, key, handler) {
    return vm.$watch(key, handler)
}

function initComputed(vm, computed) {

    const watchers = vm._computedWatchers = {}
    for (let key in computed) {
        // 校验 
        const userDef = computed[key];
        // 依赖的属性变化就重新取值 get
        let getter = typeof userDef == 'function' ? userDef : userDef.get;

        // 每个就算属性本质就是watcher   
        // 将watcher和 属性 做一个映射
        watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true }); // 默认不执行

        // 将key 定义在vm上
        defineComputed(vm, key, userDef);
    }
}

function createComputedGetter(key) {
    
    return function computedGetter() { // 取计算属性的值 走的是这个函数
        // this._computedWatchers 包含着所有的计算属性
        // 通过key 可以拿到对应watcher,这个watcher中包含了getter
        let watcher = this._computedWatchers[key]
        // 脏就是 要调用用户的getter  不脏就是不要调用getter

        if(watcher.dirty){ // 根据dirty属性 来判断是否需要重新求职
            watcher.evaluate();// this.get()
        }

        // 如果当前取完值后 Dep.target还有值  需要继续向上收集
        if(Dep.target){
            // 计算属性watcher 内部 有两个dep  firstName,lastName
            watcher.depend(); // watcher 里 对应了 多个dep
        }
        return watcher.value
    }
}

function defineComputed(vm, key, userDef) {
    let sharedProperty = {};
    if (typeof userDef == 'function') {
        sharedProperty.get = userDef;
    } else {
        sharedProperty.get = createComputedGetter(key);
        sharedProperty.set = userDef.set ;
    }
    Object.defineProperty(vm, key, sharedProperty); // computed就是一个defineProperty
}

vue上全局方法原理 vue.componment、vue.extends、vue.mixin

import { mergeOptions } from "../utils";

export function initGlobalApi(Vue) {
    Vue.options = {}; // 用来存放全局的配置 , 每个组件初始化的时候都会和options选项进行合并
    // Vue.component
    // Vue.filter
    // Vue.directive
    
    Vue.mixin = function(options) {
        this.options = mergeOptions(this.options, options); 
        return this;
    }


    Vue.options._base = Vue; // 无论后续创建多少个子类 都可以通过_base找到Vue
    Vue.options.components = {};
    Vue.component = function (id,definition){
        // 保证组件的隔离, 每个组件都会产生一个新的类,去继承父类
        definition = this.options._base.extend(definition);
        this.options.components[id] = definition;
        
    }

    // 给个对象返回类
    Vue.extend = function (opts) { // extend方法就是产生一个继承于Vue的类
        // 并且身上应该有父类的所有功能 
        const Super = this
        const Sub = function VueComponent(options){
            this._init(options);
        }
        // 原型继承
        Sub.prototype = Object.create(Super.prototype);
        Sub.prototype.constructor = Sub;
        Sub.options = mergeOptions(Super.options,opts);// 只和Vue.options合并
        return Sub;
    }
}
import { compileToFunction } from "./compiler/index";
import { callHook, mountComponent } from "./lifecycle";
import { initState } from "./state";
import { mergeOptions } from "./utils";
export function initMixin(Vue) { // 表示在vue的基础上做一次混合操作
    Vue.prototype._init = function(options) {
        // el,data
        const vm = this; // var that = this;

        vm.$options = mergeOptions(vm.constructor.options, options); // 后面会对options进行扩展操作
       
        callHook(vm, 'beforeCreate');
        // 对数据进行初始化 watch computed props data ...
        initState(vm); // vm.$options.data  数据劫持
        callHook(vm, 'created');

        if (vm.$options.el) {
            // 将数据挂载到这个模板上
            vm.$mount(vm.$options.el);
        }
    }
    Vue.prototype.$mount = function(el) {
        const vm = this;
        const options = vm.$options
        el = document.querySelector(el);
        vm.$el = el;
        // 把模板转化成 对应的渲染函数 =》 虚拟dom概念 vnode =》 diff算法 更新虚拟dom =》 产生真实节点,更新
        if (!options.render) { // 没有render用template,目前没render
            let template = options.template;
            if (!template && el) { // 用户也没有传递template 就取el的内容作为模板
                template = el.outerHTML;
            }
            let render = compileToFunction(template);
            options.render = render;
        }
        // options.render 就是渲染函数
        // 调用render方法 渲染成真实dom 替换掉页面的内容

        mountComponent(vm, el); // 组件的挂载流程
    }
}

vuex实现原理

export let Vue;
function install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
        beforeCreate(){ //this代表的是每个组件实例
            // 获取根组件上的store 将他共享给每个组件
            // 每个组件中都应该有$store
            let options= this.$options;
            if(options.store){
                // 根
                // console.log('根',options.name)
                this.$store = options.store
            }else{
                // 先保证他是一个子组件,并且父亲上有$store
                if(this.$parent && this.$parent.$store){
                    this.$store = this.$parent.$store
                }
            }
        }
    })
} 
// 父  this.$store -》 子 this.$store -》孙子 this.$store
export default install
import Vue from 'vue'
import Vuex from '@/vuex'
// import Vuex from 'vuex'
// import logger from 'vuex/dist/logger.js'
Vue.use(Vuex)
// // new Vue

// function logger() {
//     return function(store) {
//         let prevState = JSON.stringify(store.state);
//         store.subscribe((mutation, state) => { // 所有的更新操作都基于mutation (状态变化都是通过mutation的)
//             // 如果直接手动的更改状态 此scbscribe是不会执行  commit()
//             console.log('prevState:' + prevState);
//             console.log('mutation:' + JSON.stringify(mutation));
//             console.log('currentState:' + JSON.stringify(state));
//             prevState = JSON.stringify(state);

//         })
//     }
// }
function persists() {
    return function(store) { // vuex-persists
        let localState = JSON.parse(localStorage.getItem('VUEX:STATE'))
        if (localState) {
            store.replaceState(localState);
        }

        // 和 mutation挂钩的
        store.subscribe((mutation, rootState) => { // 状态变化了 想做一些其他事 
            // 状态发生变化就存localStorage中
            // 防抖
            localStorage.setItem('VUEX:STATE', JSON.stringify(rootState));
        });
    }
}
let store = new Vuex.Store({ // vuex持久化插件?
    plugins: [
        // logger()
        persists() // 每次状态变化都可以存入到localStorage中
    ],
    state: { // state = > data
        name: 'zhufeng',
        age: 12
    },
    mutations: { // method  commit 同步更改状态
        changeAge(state, payload) {
            state.age += payload
        }
    },
    actions: { // 异步操作 调用api接口 dispatch, 多次commit mutation  
        changeAge({ commit }, payload) {
            setTimeout(() => {
                commit('changeAge', payload);
            }, 1000);
        }
    },
    getters: { // 计算属性
        myAge(state) {
            return state.age + 10
        }
    },
    strict: true, // 如果不是在mutation中操作的状态会发生警告
    modules: { // 进行模块分割
        // namespaced 能解决子模块和父模块的命名冲突文件 ,相当于增加了一个命名空间
        // 如果没有namespaced 默认getters都会被定义到父模块上,
        // mutations 会被合并在一起, 最终一起调用,有了命名空间就没有这个问题了
        // 子模块的名字不能和父模块中的状态重名
        a: {
            namespaced: true,
            state: {
                name: 't1',
                age: 10
            },
            // 所有的getters 都会被合并到跟上
            getters: { // 首页一个模块 home 订单页一个模块 order  用户一个模块 user
                myAge(state) {
                    return state.age + 20;
                }
            },
            mutations: {
                changeAge(state, payload) {
                    state.age += payload
                }
            },
            modules: {
                c: {
                    namespaced: true,
                    state: {
                        age: 100
                    },
                    mutations: {
                        changeAge(state, payload) {
                            state.age += payload
                        }
                    },
                    modules: {
                        d: {
                            namespaced: true,
                            state: {
                                age: 100
                            },
                        }
                    }
                }
            }
        },

    }
})

export default store;
import { forEach } from '../util'
import Module from './module';
class ModuleCollection {
    constructor(options) {
        // 对数据进行格式化操作
        this.root = null;
        this.register([],options); // 为了记录父子关系 
    }
    getNamespace(path){ // [a,b,c]
        // 返回一个字符串 a/b/c   ''
        let root = this.root
        let ns =  path.reduce((ns,key)=>{ // this.root.c.namespace
           let module =  root.getChild(key); 
           root = module;
           return module.namespaced ? ns + key + '/'   :ns
        },'');
        return ns;
    }
    register(path,rawModule){ 
        let newModule = new Module(rawModule)
        rawModule.newModule = newModule;// 自定义属性
        if(path.length == 0){
            this.root = newModule
        }else{
            // [a] // [a,c]  => [a]
            // 找父亲
            let parent = path.slice(0,-1).reduce((memo,current)=>{
                return memo.getChild(current)
            }, this.root);
            parent.addChild(path[path.length-1],newModule);
            // 根据当前注册的key ,将他注册到对应的模块的儿子处
        }
        // 注册完毕当前模块,在进行注册根模块 
        if(rawModule.modules){
            forEach(rawModule.modules,(module,key)=>{
               this.register(path.concat(key),module);
            })
        }
    }
}

export default ModuleCollection


// this.root = {
//     _raw: 用户定义的模块,
//     state: 当前模块自己的状态,
//     _children: { // 孩子列表
//         a: {
//             _raw: 用户定义的模块,
//             state: 当前模块自己的状态,
//             _children: { // 孩子列表
//                 e: {}
//             }
//         },
//         c: {

//         }
//     }

// }
import { Vue } from './install'
import ModuleCollection from './module/module-collection';
import { forEach } from './util';

function getNewState(store, path) {
    return path.reduce((memo, current) => {
        return memo[current];
    }, store.state)
}

function installModule(store, rootState, path, module) { //  a/b/c/d
    // 需要循环当前模块的

    // 获取moduleCollection类的实例
    let ns = store._modules.getNamespace(path);
    // module.state => 放到rootState对应的儿子里
    if (path.length > 0) { // 儿子模块 
        // 需要找到对应父模块,将状态声明上去
        // {name:'zf',age:'12',a:aState}
        let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo[current];
        }, rootState);
        // 对象新增属性不能导致重新更新视图
        store._withCommittting(() => {
            Vue.set(parent, path[path.length - 1], module.state);
        })
    }
    module.forEachGetter((fn, key) => {
        store.wrapperGetters[ns + key] = function() {
            return fn.call(store, getNewState(store, path));
        }
    });
    module.forEachMutation((fn, key) => { // {myAge:[fn,fn]}
        store.mutations[ns + key] = store.mutations[ns + key] || [];
        store.mutations[ns + key].push((payload) => {
            store._withCommittting(() => {
                fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
            })

            store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
        })
    });
    module.forEachAction((fn, key) => {
        store.actions[ns + key] = store.actions[ns + key] || [];
        store.actions[ns + key].push((payload) => {
            return fn.call(store, store, payload)
        })
    });
    module.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
    });
}

function resetVM(store, state) {
    let oldVm = store._vm;
    store.getters = {};
    const computed = {};
    forEach(store.wrapperGetters, (getter, key) => {
        computed[key] = getter;
        Object.defineProperty(store.getters, key, {
            get: () => store._vm[key]
        })
    });
    store._vm = new Vue({
        data: {
            $$state: state
        },
        computed
    });
    if (store.strict) { // 说明是严格模式我要监控状态
        store._vm.$watch(() => store._vm._data.$$state, () => {
            // 我希望状态变化后 直接就能监控到,watcher都是异步的? 状态变化会立即执行,不是异步watcher
            console.assert(store._committing, 'no mutate in mutation handler outside')
        }, { deep: true, sync: true }); // 内部会遍历所有的属性
    }
    if (oldVm) { // 重新创建实例后,需要将老的实例卸载掉
        Vue.nextTick(() => oldVm.$destroy())
    }
}
class Store {
    constructor(options) {
        // 对用户的模块进行整合 
        // 当前格式化完毕的数据 放到了this._modules里
        this._modules = new ModuleCollection(options); // 对用户的参数进行格式化操作
        this.wrapperGetters = {}
        // 我需要将模块中的所有的getters,mutations,actions进行收集
        this.mutations = {};
        this.actions = {};
        this._subscribes = [];
        this._committing = false; // 默认不是在mutation中更改的

        this.strict = options.strict;


        // 没有namespace的时候 getters都放在根上 ,actions,mutations 会被合并数组
        let state = options.state;

        installModule(this, state, [], this._modules.root);

        resetVM(this, state);
        if (options.plugins) { // 说明用户使用了插件
            options.plugins.forEach(plugin => plugin(this))
        }
    }
    _withCommittting(fn) {
        this._committing = true; // 如果true
        fn(); // 函数是同步的 获取_commiting 就是true,如果是异步的那么就会变成false 就会打印日志
        this._committing = false;
    }
    subscribe(fn) {
        this._subscribes.push(fn);
    }
    replaceState(newState) { // 需要替换的状态

        this._withCommittting(() => {
            this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
        })

        // 虽然替换了状态,但是mutation getter中的state在初始化的时候 已经被绑定死了老的状态
    }

    get state() {
        return this._vm._data.$$state
    }
    commit = (mutationName, payload) => { // 发布
        this.mutations[mutationName] && this.mutations[mutationName].forEach(fn => fn(payload))
    }
    dispatch = (actionName, payload) => {
        this.actions[actionName] && this.actions[actionName].forEach(fn => fn(payload))
    }

    registerModule(path, module) { // 最终都转换成数组  register(['a','c'])
        if (typeof path == 'string') path = [path];

        // module 是用户直接写的
        this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
        // 注册完毕后 ,在进行安装

        // 将用户的module 重新安装
        installModule(this, this.state, path, module.newModule);

        // vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
        resetVM(this, this.state); // 销毁重来
    }
}
// state getters action mutation  (modules 分层)
export default Store;

mapState、mapgetters、mapMutations、mapActions

import install from './install';
import Store from './store'
function mapState(stateList) {
    let obj = {};
    for (let i = 0; i < stateList.length; i++) {
      let stateName = stateList[i];
      obj[stateName] = function () {
        return this.$store.state[stateName];
      };
    }
    return obj;
  }
  function mapGetters(gettersList) {
    let obj = {};
    for (let i = 0; i < gettersList.length; i++) {
      let getterName = gettersList[i];
      obj[getterName] = function () {
        return this.$store.getters[getterName];
      };
    }
    return obj;
  }
  function mapMutations(mutationList) {
    let obj = {};
    for (let i = 0; i < mutationList.length; i++) {
      obj[mutationList[i]] = function (payload) {
        this.$store.commit(mutationList[i], payload);
      };
    }
    return obj;
  }
  function mapActions(actionList) {
    let obj = {};
    for (let i = 0; i < actionList.length; i++) {
      obj[actionList[i]] = function (payload) {
        this.$store.dispatch(actionList[i], payload);
      };
    }
    return obj;
  }
export default {
    install,
    Store,
    mapState,
    mapGetters,
    mapMutations,
    mapActions
}

路由实现原理

import Vue from 'vue'
import VueRouter from '@/vue-router'
import Home from '../views/Home.vue';
import About from '../views/About.vue';

Vue.use(VueRouter); // 注册两个全局组件  install(Vue)


// 核心实现就是根据路径变化 找到对应的组件, 显示到router-view中
// hash模式 history模式


// about=> About   /about/a => AComponent
// 父 router-view
// 子 router-view



const routes = [ // 映射表  用户配置
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About,
    children:[
      {path:'a',component:{
        render:(h)=> <h1>about a</h1>
      }},
      {path:'b',component:{
        render:(h)=> <h1>about b</h1>
      }}
    ]
  }
]
// 最终生成了一个vue-router实例
// const store = Vuex.Store()
const router = new VueRouter({
  mode:'history', // # 丑 兼容性好  / 好看 但是需要服务端支持,在开发环境内部提供了historyFallback插件
  routes
})


router.beforeEach((to,from,next)=>{ //[]
  console.log(to,from,1);
  setTimeout(() => {
   next();
  }, 1000);
})
router.beforeEach((to,from,next)=>{ // 全局钩子 路由钩子 组件钩子
  console.log(to,from,2);
  next();
})
export default router

export let Vue;
import RouterLink from './components/link';
import RouterView from './components/view'
 export default function install(_Vue){ // 谁用我这个插件 版本就是谁
    Vue = _Vue;
    // 给所有的组件统一增加$route 和 $route 属性
    Vue.mixin({ //  _router 共享给每个人的
        beforeCreate(){
            // 父 store  子 拿父store  孙子 拿子的store
            // 我给根实例增加一个_router属性
            // 所有人都拿到根上的_router
            if(this.$options.router){
                // 根组件
                this._router = this.$options.router;
                this._routerRoot = this; // 表示根组件上有一个唯一的标识叫_routerRoot 指向了自己

                // 初始化路由的逻辑 只初始化一次
                this._router.init(this); // 整个应用的根
            }else{
                // 子 孙子
                this._routerRoot = this.$parent && this.$parent._routerRoot
            }
            // 所有组件都有_routerRoot._router获取路由的实例
        }
    })
    // _routerRoot 是根实例  根实例上有_router属性
    // 所有的组件都可以获取根 _routerRoot, 获取根的属性 _routerRoot._router
    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)
    Vue.component('router-link',RouterLink)
    Vue.component('router-view',RouterView)
}

image.png

import install, { Vue } from './install';
import {createMatcher} from './create-matcher'
import Hash from './history/hash';
import HTML5History from './history/h5';
class VueRouter {
    constructor(options = {}) {
        const routes = options.routes;
        this.mode = options.mode || 'hash';

        //  给我个路径 我就返回给你对应的记录 
        //  match匹配方法
        // addRoutes 动态添加路由 
        this.matcher = createMatcher(options.routes || []);

        // 根据模式需要初始化不同的路由系统  hash/history 底层实现不一样 ,但是使用的方式是一样的
        // hash => hash.js  => push
        // history => history.js => push
        // base

        // 每次跳转 我需要获取当前的路径 this.$router.pathname
        switch(this.mode) {
            case 'hash': // location.hash  => push
                this.history = new Hash(this)
                break
            case 'history': // pushState => push
                this.history = new HTML5History(this); 
                break

        }
        this.beforeHooks = []
    }
    match(location){
        return this.matcher.match(location);
    }
    push(location){
        // 跳转页面
        this.history.transitionTo(location,()=>{
            // pushState
            this.history.pushState(location);
        })
    }
    init(app) {
        const history = this.history; // 当前管理路由的
        // hash  -》 hashchange  但是浏览器支持popstate 就优先采用popstate
        // history -> popState  性能高于hashchange 但是有兼容性问题

        // 页面初始化完毕后 需要先进行一次跳转

        // 跳转到某个路径
        const setUpListener = () =>{
            // 此事件的实现方式也不一致
            // ....
            history.setUpListener();
        }
        history.transitionTo(
            history.getCurrentLocation(), // 各自的获取路径方法
            setUpListener
        );

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

// node vue2 部署 linux + docker + nginx  vue3  node
// 咱们 年前上到1.31日  年后初10继续

// vue3 讲一个半月 源码 + 用法 + 组件库 + 生态 + ts
// 之前听过node的同学 年后回来就听运维课  , vue3有录播 -> webpack -> react 
// 没学过node课的听vue3  -> node -> react -> webpack

export default VueRouter

//  路由公共的方法都放在这 大家共用

function createRoute(record, location) { // 创建路由
    const matched = [];
    // 不停的去父级查找
    if (record) {
        while (record) {
            matched.unshift(record);
            record = record.parent;
        } // /about/a => [about,aboutA]
    }
    return {
        ...location,
        matched
    }
}

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);
}
export default class History {
    constructor(router) {
        this.router = router;
        // 有一个数据来保存路径的变化
        // 当前没有匹配到记录
        this.current = createRoute(null, {
            path: '/'
        }); // => {path:'/',matched:[]}    
    }
    listen(cb){
        this.cb = cb; // 保存当前的cb函数
    }
    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();
        // 如果 两次路由一致  不要跳转了
    }
    updateRoute(route){
        this.current = route; // 不光要改变current , 还有改变_route
        this.cb && this.cb(route); 
    }
}
export 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.得是组件  <router-view></router-view>  <app></app>
            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); // <router-view routeView=true></router-view>
    }
}