vuejs - 全家桶原理和实现

110 阅读2分钟

vue-router源码实现

vue-router

安装:vue add router 核心步骤:

  • 步骤一: 使用vue-router插件, router.js
import Router form 'vue-router'
Vue.use(Router)
  • 步骤二: 创建Router实例 并导出, router.js
import Router from 'vue-router
export default new Router({....})
  • 步骤三: 在根组件上添加该实例
import router from './router'
new Vue({
	router
}).$mount("#app")
  • 步骤四: 添加路由视图, App.vue <router-view></router-view>
  • 添加导航
<router-link to="/">Home</router-link>
this.$router.push('/')

源码实现

需求分析

  • spa 页面不能刷新
    • hash #/about
    • history api /about
  • 根据url显示对应的内容
    • router-view
    • 数据响应式:current变量持有url地址, 一旦变化, 动态重新执行render

任务

  • 实现一个插件, kvue-router.js
    • 实现VueRouter类
      • 处理路由选项
      • 监控url变化, hashchange
      • 响应这个变化
    • 实现install方法
      • $router 注册
      • 两个全局组件
//实现一个插件
//返回一个函数
//或者返回一个对象, 有一个install放法
let _Vue
class VueRouter {
	// 选项: routers - 路由表
	constructor(options) {
    	this.$options = options
        //定义一个响应式的current 属性
        //利用Vue提供的defineReactive做出响应化
        //const initial = window.locath.hash.slice(1) || '/'
        //_Vue.util.defineReactive(this, 'current', initial)
        this.current = window.location.hash.slice(1)
        _Vue.util.defineReactive(this, 'matched', [])
        this.match()
        
        //this.routeMap = {}
        //options.routes.forEach(
        	//route => {
            	//this.routeMap[route.path] == route
            //}
        //)
        // 监控url变化
   		 window.addEventListner('hashchange', this.onHashChange.bind(this))
         window.addEventListner('load', this.onHashChange.bind(this))
    }
    
    onHashChange(){
    	this.current = window.location.hash.slice(1)
        this.matched = []
        this.match()
    }
    
    match(routes) {
    	routes = routes || this.$options.routes
        
        //递归遍历
        //Object.keys(routes).forEach(route => {
        
        //})this.
        for(const route of routes) {
        	if(route.path === '/' && this.current === '/') {
            	this.mathed.push(route)
                return
            }
            
            if(route.path !== '/' && cthis.current.indexof(route.path) !== -1) {
            	this.mathed.push(route)
                if(route.children) {
                	this.match(route.children)
                }
                return
            }
        }
    }
}
VueRouter.install = function(Vue) {
	//引用Vue构造函数,在上面VueRouter中使用
    _Vue = Vue
    // 1.挂载$router
    Vue.mixin({
      beforeCreate(){
      	if(this.$options.router) {
        	Vue.prototype.$router = this.$options.router
        }
      }
    })
    
    // 2.定义两个全局组件router-link, router-view
    Vue.component('router-link', {
    	props: {
        	to: {
            	type: String,
                require: true
            }
        },
    	render(h) {
        	//<a href=“#/abaout”>xxxxx</a>
        	return h('a', {
            	attrs: {
                	href: '#' + this.to
                }
            }, this.$slots.default)
        }
    })
    
    Vue.component('router-view', {
    	// 1.标记router-view 深度
        // 2.路由匹配
    	render(h) {
        	//标记当前router-view深度
            this.$vnode.data.routerView = true
            //深度
            let deep = 0
            let parent = this.$parent
            while(parent) {
            	const vnodeData = parent.$vnode && parent.$vnode.data
                if(vnodeData) {
                	if(vnodeData.routerView) {
                    	//当前paret是一个router-view
                    	deep++
                    }
                }
            	parent = this.$parent
            }
            
            //const {routeMap, current} = this.$router
            //const component = routeMap[current] ? routeMap[current].component : null
            
            //获取path对应的component
            let comonent = null
            const route = this.$router.matched[depth]
            
        	return h(component)
        }
    })
}

export default VueRouter

Vuex

  • state
  • mutations -- $commit
  • getters
  • actions --$dispach

要解决问题

任务

  • 实现插件
    • 实现Store类
      • 维持一个响应式状态
      • 实现 commit()
      • 实现 dispatch()
      • getters
    • 挂载$store实例
	//1. 实现一个插件
    let _Vue
    class Store {
    	constructor(options) {
        	this._mutations = options.mutations
        	this._actions = options.actions
            this._wrapedGetters = options.getters
            
            //定义computed选项
            const computed = {}
            this.getters = {}
            
            const store = this
            Object.keys(this._wrapedGetters).forEach(key => {
            	//获取用户定义的getter
                const fn = store._wrappedGetters[key]
                //转换为computed可以使用的无参数形式
                computed[key] = function() {
                	return fn(store.state) 
                }
                //为getters定义只读属性
                Object.defineProperty(store.getters, key, {
                	get: () => store._vm[key]
                })
            })
            
        	//创建响应式的state
            this._vm = new _Vue({
            	data() {
                	return {
                    	//不希望被代理, 就加上$
                    	$$state: options.state
                    }
                },
                computed
            })
            
            this.commit = this.commit.bind(this)
            this.dispatch = this.dispatch.bind(this)
            
            //getters
            this.getters = {}
            for(let prop in options.getters) {
            	Object.defineProperty(this.getters, prop, {
                	get() {
                    	 return options.getters.prop(this.state)
                    }
                    set(v) {
                    	console.error('please use replaceState to reset state')
                    }
                })
            }
            
        }
        
        get state() {
        	return this._vm._data.$$state
        }
        //只读
        set state(v) {
        	console.error('please use replaceState to reset state')
        }
        
        //修改state
        // this.$store.commit('add', 1)
        commite(type, payload) {
        	//获取type对应的mutation
            const entry = this._mutations[type]
            if(!entry) {
            	console.error('unknow mutation')
                return
            }
            //传入state作为参数
            entry(this.state, playoad)
        }
        
        dispatch(type, payload){
        	//获取type对应的action
            const entry = this._actions[type]
            const entry = this._mutations[type]
            
            if(!entry) {
            	console.error('unknow action')
                return
            }
            //传入state作为参数
            return entry(this, playoad)
        }
    }
    
    function install(Vue) {
    	_Vue = Vue
        
        //混入
        Vue.mixin({ //解决install插件注册时实例还不存在的问题
          beforeCreate(){
          	if(this.$options.store) {
            	Vue.prototype.$store = this.$options.store
            }
          }
        })
    }
    
    //导出的对象是Vuex
    export default {Store, install}

补充组件

//传入的是组件配置对象
const Ctor = Vue.extend(Component);
// propsData选项传递属性 -- VueComponent实例
const ctor= new Ctor({propsData: props}) 
//vm Vue实例, 根实例, $root
// App 根组件、 实例化
//vm.children[0] VueComponent
comp.$mount();
document.body.appendChild(comp.$el)
comp.remove = () => {
	//移除dom
    document.body.removeChild(cokmp.$el)
    //销毁组件
    comp.$destroy()
}
  • 使用插件进一步封装便于使用, create.js
	import Notice from '@/components/Notice.vue'
    export default {
    	install(Vue) {
        	Vue.prototype.$notice = function(options) {
            	const comp = create(Notice, options)
                comp.show()
                return comp
            }
		}
    }

递归组件

组件内部调用自身, 一般生成树形结构