Vue Router,响应式,diff算法

发布于:2024-04-20 ⋅ 阅读:(24) ⋅ 点赞:(0)

Vue Router模式

**
   hash模式和history模式
       hash模式
       *1.hash是url存在 # 的一种方式,是vueRouter默认模式
       *2.当 # 后面的url地址发生变化会触发hashChange事件,通过监听来更新页面
       *3.只可修改hash部分
       *4.hash模式会创建hashHistory对象,hashHistory有两个方法push()和replace()
       history模式
       *1.history模式url无 # 号,就是普通的url形式
       *2.history模式发生跳转通过history.pushState()和history.replaceState()方法改变浏览器地址
       *3.当浏览器前进后退或调用back(), go(), forward()等方法时,会触发popstate事件,通过监听popstate事件来获取路由地址,从而更新页面
       *4.需要和服务器配合使用,否则会出现404的情况
       history模式在node服务中配置

	```
	const path = require('path')
	// 导入处理history模式的模块
	const history = require('connect-history-api-fallback')
	// 导入express
	const express = require('express');
	
	const app = express();
	// 处理history
	app.use(history());

	app.use(express.static(path.join(__dirname, 'dist')));

	const port = process.env.PORT || 8080;
	app.listen(port);
	```

       history模式在nginx服务中配置

   在nginx.conf文件中找到
   server {
   				listen 80
   				.
   				.
   				.
   				loaction / {
   					root  html
   					index index.html index.htm
   					try_files $uri $uri/ index.html  // 新增($uri当前请求的路径)
				}
   }

   *Vue Router原理

                hash模式

  •          1.url # 后面内容作为地址

  •          2.监听hashChange事件

  •          3.根据当前的地址找到对应的组件重新渲染

             hsitory模式

  •          1.通过history.pushState()方法改变地址栏

  •          2.监听popstate事件

  •          3.根据当前的地址找到对应的组件重新渲染
       

1. Vue Router实现*

  •    1.内部创建一个静态install方法(Vue.use()注入中会默认执行install方法);
  •    2.通过传入options解析配置项routes;
  •    3.监听路由变化,根据路由匹配到对应组件;
  •    4.实现组件router-link(通过render函数渲染a标签 指定a标签的href为跳转的路由)和router-view(查找当前路由对应的组件再通过render函数渲染组件)
	let _vue = null
	export default class VueRouter {
		// vue参数是。vue的构造函数
		static install(vue) {
			// 1.判断插件是否已经安装
			if (VueRouter.install.installed) return
			VueRouter.install.installed = true
			// 2.把vue构造函数设置为全局变量
			_vue = vue
			// 3.把创建的router对象注入到vue所有的实例上
			_vue.prototype.$router = this.$options.router
			
			this.init()
			
		}
		constructor(options) {
			this.options = options // 传入的options
			this.routeMap = {} // 解析出来的route
			// this.data是一个响应式。通过observable创建响应式 
			this.data =  _vue.observable({
				current: '/', // 当前路由地址(根据当前路由加载对应的组件)
			})  
		}
		init() {
			this.createRouteMap()
			this.initComponent(_vue)
			this.initEvent()
		}
		
		// 遍历所有的路有规则 解析成键值对 存放在routeMap中
		createRouteMap() {
			this.$options.routes.forEach(route => {
				this.routeMap[route.path] = route.component
			})
		}

		// 创建 router-link 组件
		initComponent(vue) {
			vue.component('router-link', {
				props: {
					to: String // 类型为字符串
				},
				render(h) {
					return h('a', {
						attrs: {
							href: this.to
						},
						on: { // 添加事件阻止默认事件
							click: this.clickHander
						}
					}, [this.$slots.default])
					methods: {
						// 阻止默认事件 通过pushState 改变当前地址。将当前地址同步
						clickHander(e) {
							// pushState() data title 路径
							history.pushState({}, '', this.to)
							this.$router.data.current = this.to
							e.preventDefault()
						}
					}
				}
				// template: `<a :href='to'><slot></slot></a>` 	// *运行会报错 因为cli创建的vue是运行时版本的(运行效率高)。所以要开始编译器,通过在vue.config.js 设置runtimeCompiler来开启编译器
			})
			
			const self = this
			vue.component('router-view', {
				const component = self.routeMap[self.data.current]
				render(h) {
					return h(component)
				}
			})
		}
		
		// 创建popState 事件来解决当浏览器回退,前进 组件没有更新问题
		initEvent() {
			window.addEventListener('popState', () => {
				this.data.current = window.location.pathname
			})
		}
	}

2. Vue响应式原理*

**
       vue2通过Object.defineProperty来实现响应式

	// 模拟 vue 中的data
	let data = {
    	msg: 'hello',
    	count: 10
	}
	
	// 模拟 vue 实例
	let vm = {}
	
	Object.keys(data).forEach(key => {
		// 数据劫持。当访问和设置vm中的成员时,做一些干预操作
		Object.defineProperty(vm, key, {
	    	// 可枚举(可遍历)
	    	enumerable: true,
	    	
	    	// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义当前属性)
	    	configurable: true,
    	
	    	// 当获取值的时候执行
	    	get() {
	        	return data.msg
	    	},
	    	
	    	// 当设置值的时候执行
	    	set(newVal) {
	        	if (newVal === data.msg) return
	        	data.msg = newVal
	        	document.querySelector('#app').textContent = data.msg
	    	}
		})
	})
	
	vm.msg = 'hell workd'
	console.log(vm.msg) // ====> hell workd
	

       vue3通过Proxy来实现响应式

	// 模拟 vue 中的data
	let data = {
    	msg: 'hello',
    	count: 10
	}
	
	// 模拟 vue 实例
	// new Proxy 监听的是对象而非属性
	let vm = new Proxy(data, { // 执行代理行为的函数
	
		// 当访问vm成员的时候执行
		get(target, key) {
	        console.log(`获取 ${key}`)
	        return target[key]
	    },
	    set(target, key, value) {
	        if (target[key] === value) {
            	return
	        }
	        target[key] = value
	        console.log(`设置 ${key}=${value}`)
	    }
	})
	

3. Virtual Dom 的实现原理

虚拟DOM是一个普通的javaScript对象用来描述真实DOM,创建虚拟DOM的开销比普通DOM开销小很多。

为什么要是使用虚拟Dom
1.MVVM框架 解决视图和状态同步问题
2.普通模版(jquery等)可以简化视图操作,没办法跟踪状态
3.虚拟DOM跟踪状态变化

虚拟DOM的作用
1.维护视图和状态的关系(可以保存视图的状态)
2.复杂情况下提高渲染性能(首屏加载或第一次加载DOM时并没有提高渲染性能)
3.跨平台(因为虚拟DOM是普通的js对象可以做任何处理)

vue2是基于snabbdom库来实现的

	import { init } from '/node_modules/snabbdom/build/package/init';
	import { h } from '/node_modules/snabbdom/build/package/h';
	
	// 1.导入模块
	import { styleModule } from '/node_modules/snabbdom/build/package/modules/style'
	import { eventListenersModule } from '/node_modules/snabbdom/build/package/modules/eventlisteners';
	// 2.注册模块
	// init 初始化所引用的一些模块,返回patch函数,并且可以定义以哪种方式来转换虚拟DOM(默认是浏览器下的DOM对象)
	const patch = init([
	 styleModule,
	 eventListenersModule
	])
	// 3.使用h() 函数创建vnode, 第二个参数传入模块中使用数据(是一个对象)
	// h() 用到了函数重载(函数名字相同,参数不同)
		// 1. 参数个数或参数类型不同的函数
		// 2. js中没有重载概念
		// 1. ts中有重载,不过重载的实现还是通过代码来调整参数
	
	// vnode = {
	//		sel: '', // 选择器和类名
	//		data: '', // 模块中的数据
	//		childern: '', // 子节点
	//		elm: '', // vnode转化的真实DOM元素
	//		text: '', // 文本内容。和chindern互斥只有一个有值
	//		key: '', // 唯一标识,有利于对比重用DOM, 并且避免渲染错误(如果不设置key会判断sel属性进行重用DOM,假如子节点有checked选中,会重用dom导致显示错误)
	// }
	let vnode = h('div', [
	    h('h1', {
	        style: {
	            backgroundColor: 'red',
	        }
	    }, 'Hello world'),
	    h('p', {
	        on: {
	            click: eventHandler
	        }
	    }, 'hell p')
	])
	function eventHandler () {
	    console.log('click p')
	}
	// 4.使用patch()函数的第一个参数传入要操作的元素,第二个参数传入要操作的元素 
	// patch() 
	//	 1.把新新节点变化内容渲染到真实DOM,返回新节点作为下次的旧节点
	//	 2.对比新旧vnode 是否是相同节点(判断节点的key和sel相同)
	//   3.如果不是相同节点,删除之前的内容,重新渲染
	//   4.如果相同节点对比节点来更新(diff算法)
	//   diff
	//		1. 首先判断新旧节点是否相同,如果相同直接返回,如果新节点的data属性有值,遍历data属性更新节点
	//      2. 判断新节点是否有childern属性,如果存在,再去对比新旧节点的子节点
					// 四种情况
	//				2-1. 从旧开始节点和新开始节点开始做比较
	//				2-2. 从旧结束节点和新结束节点开始做比较
	//				2-3. 从旧开始节点和新结束节点开始做比较
	// 				2-4. 从旧结束节点和新开始节点开始做比较
	//				先对比旧开始节点和新开始节点,如果是相同节点(会重用旧节点的DOM从而优化性能),会将下一个节点作为开始节点,如果不是相同节点会通过旧的结束节点和新的结束节点做比较,如果不相同然后从旧开始节点和新结束节点做比较,如果是相同节点,会把旧元素移动到最后,然后对比下一组节点,如果不是相同节点,从旧节点的结束节点和新节点的开始节点做比较,如果是相同节点,把当前元素移动到最前面,如果以上情况都不满足,从旧节点中依次查找新节点的开始节点,如果找到把当前元素移动到最前面,如果没找到,创建新的DOM元素,插入到最前面位置,如果旧节点的开始节点大于旧节点的结束节点,创建新的节点插入到旧节点,然后从开始对比,对比结束后发现新节点中有剩余的节点,创建新的DOM放在旧节点DOM的后面,如果新节点的开始节点大于旧结束节点,对比新旧节点,对比完成后发现旧节点有剩余的节点,删除旧节点上的节点和DOM
	
	
    //    内部实现
	//	  (1) 判断vnode中sel属性 => 解析出标签和选择器 => 创建元素
	//	  (2) 判断vnode中是否有childern属性 => 如果存在遍历子节点然后递归调用createElm()
	//	  (3) 判断vnode中text属性 => 如果有值,创建文本节点 => 挂载到vnode的elm属性上
	//	  (4) 判断是否有hook(钩子函数) => 执行hook
	//	  (5) 把创建的DOM插入到DOM元素上
	//	  (6) 删除旧的节点
	let app = document.getElementById('app')
	let oldVnode = patch(app, vnode)