Vue探索细节记录 —— 实现

Vue基础语法

初始化

// main.js
import Vue from "Vue"
import App from "App"
new Vue({
	render: h => h(App)
}).$mount("#app")
  • h创建虚拟dom,$mount函数 将虚拟dom转为真实dom
  • Vue Router原理分析与实现

生命周期与主要事件

创建

  • new Vue()
    • 初始化事件
    • 初始化生命周期钩子
    • 初始化h函数
    • 初始化props
  • beforeCreate
    • 初始化注入与校验,注入到Vue实例
      • props
      • data
      • methods
  • created
    • Vue实例创建完毕

编译

  • 将template编译到render函数中
    • 存在 el ,执行vm.$mount(el)
    • 指定template,render template
    • 未指定template,将el外部HTML作为 template编译
  • beforeMount
    • 无法获取新dom结构的内容
    • 创建vm.$el,替换 el
  • mounted
    • 能够获取新dom结构的内容
  • 修改data时
    • 触发beforeUpdate,能获取旧的渲染内容
    • 内部完成新旧虚拟dom对比,重新渲染,完成应用更新
    • 触发updated,能获取新的渲染内容

销毁

  • 销毁阶段,vm.$destroy()
    • 解除绑定
    • 销毁子组件
    • 销毁事件监听器
  • destroyed

如果SPA,模板编译工作是在打包或者构建时候完成的,不在运行时处理模板编译工作,就是说,模板编译是提前执行的。

语法

  • 插值表达式
  • 指令
    内置指令共14个 ,Vue支持按需自定义指令。
  • 计算属性和侦听器
  • Class和style绑定
  • 条件渲染与列表渲染
  • 表单输入绑定

其它特性

  • 组件
  • 插槽
  • 插件
  • 混入mixin
  • 深入响应式原理
  • 不同构建版本的Vue

VueRouter

基本使用

  • 注册
// router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
// Vue.use用来注册插件,会调用传入对象的install方法
// Vue.use用来注册组件,会直接调用传入的函数
Vue.use(VueRouter)
// main.js
import Vue from 'vue'
import App from './app.vue'
import router from './router'
Vue.config.productionTip = false

new Vue({
	router,
	render h => h(App)
}).$mount('#app')
  • 创建
// router/index.js
//  路由懒加载
const mainPage = () => import(/* webpackChunkName: "MainPage" */ './MainPage.vue')
const routes = [
	{ path: '/main', component: mainPage },
	{
		// 嵌套路由
		path: "/",
		component: import(/* webpackChunkName: "MainPage" */ './RootPage.vue'),
		children: [
			{
				name: "SubPage1",
				// path是相对路径
				path: "",
				component: import(/* webpackChunkName: "MainPage" */ './SubPage01.vue'),
			},
			{
				name: "SubPage1",
				path: "sub2",
				component: import(/* webpackChunkName: "MainPage" */ './SubPage01.vue'),
			},
		]
	}
]
const router = new VueRouter()
export default router
// RootPage.vue
<template>
	<div id="app">
		<div>公共头部</div>
		<router-view />
		<div>公共尾部</div>
	</div>
</template>
  • 调用
// app.vue
<template>
	<div id="app">
		<router-link to="/"> mainPage <router-link>
		<router-view />
	</div>
</template>

在router注册到new Vue对象中时,注入了 $route 和 $router 对象。 $route存储了当前路由规则信息,包括路径、参数。也可从 $router.currentRoute 获取当前路由信息。 $router是VueRouter的实例对象,提供路由相关的方法。

源码仿写

仿写代码地址

注意点

// vue.config.js 文件
module.exports = {
	// 默认为false,即不带编译器版本的Vue,改为true,启用完整版Vue,即带编译器版本的Vue,可将template模板字符串编译成render函数
	// 为什么单文件组件可以正常运行?
	// 因为Vue在打包过程中运行了预编译,将template编译成render函数
	// 完整版体积比运行时版大10k左右
	runtimeCompiler:true
}

编程式导航

  • replace
  • push
  • go

replace 与 push 都可以传递路由参数,跳转到指定路径,replace方法不会记录本次跳转历史。

	replace() {
		this.$router.replace("/login")
	},
	push() {
		this.$router.push({name: "detail", parmas: {id: 1}})
	},
	go() {
		this.$router.go(-1)
	}

路由

  • hash
    基于锚点和 onhashchange 事件
  • history
    基于HTML5中的history API
    • history.pushState() 在IE10以后才支持
    • history.replaceState()
    • 需要服务器支持

vue-cli 和 react-cli 都在内部做好了history模式兼容。

虚拟DOM

概述

  • 通过JS对象来描述真实DOM
  • 虚拟DOM只会更新发生变化的节点,通过diff算法寻找新旧节点的差异
  • diff的过程是比较新旧节点,最后将比对结果更新到真实DOM上。是对真实DOM打补丁的过程
    • 新旧VNode节点是否相同(key与sel相同)
      • 不相同,删除旧内容,重新渲染
      • 相同,判断VNode是否有text,如果有text,而与oldVNode 的text不同 ,则更新test
      • 如果新的VNode有children,判断子节点的差异,仅将差异更新到真实DOM

作用

  • 渲染生成真实DOM
  • SSR (Nuxt.js , Next.js)
  • 原生应用 (Weex,RN)
  • 小程序(nui-app,mpvue)

视图越复杂,虚拟DOM提升性能效果越明显。简单视图只需要更新特定的节点时,往往不需要虚拟DOM操作。所以,并不是任何时候都需要使用虚拟DOM。

虚拟DOM 开源库

  • Snabbdom(vue2.xx 基于此开源库)
  • virtual-dom (最早的虚拟DOM开源库)

Snabbdom源码解析

  • 主要函数
    • h
      h函数: 生成虚拟DOM的函数
      第一个参数 : 标签 + 选择器
      第二个参数 : 标签中的内容/子节点数组
    • patch
      第一个参数可以是虚拟DOM或者真实DOM,内部会转换成VNode
      第二个参数:VNode
      返回值:更新后的VNode
    • init
      注册模块
      h()函数的第二个参数设置模块需要的数据(对象)
      初始化patch函数,参数:数组[模块],不使用模块传 []
  • 有自己的生命周期函数
  • 所有组件的生命周期函数存储在回调函数数组中
    cbs[钩子函数名称] = [对应函数的数组]
    虚拟节点 = 虚拟节点 ? (vnode.sel === undefined) 拟节点 : 转换真实dom为虚拟节点
    判断相同节点:判断key和sel是相同的
import { h, thunk, init } from 'snabbdom';
// 导入模块
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
// 注册模块
// h()函数的第二个参数设置模块需要的数据(对象)
// 初始化patch函数,参数:数组[模块],不使用模块传 []
// let patch = init([])

let patch = init([style, eventlisteners])

// h函数: 生成虚拟DOM的函数
// 第一个参数 : 标签 + 选择器
// 第二个参数 : 标签中的内容/子节点数组
// let newVNode = h('div#newElement.className', 'this is a div element')
let newVNode = h('div#newElement.className', [
    h('h1', 'this is a h1 element'),
    h('div', 'this is a div element'),
])
// 获取占位div元素
const app = document.querySelector('#app')
// 第一个参数可以是虚拟DOM或者真实DOM,内部会转换成VNode
// 第二个参数:VNode
// 返回值:更新后的VNode
let lastVNode = patch(app, newVNode)

// 更新
// newVNode = h('div#newElement.className', 'new value')
newVNode = h('div#newElement.className', [
    h('h1', 'change h1 content'),
    h('div', 'this is a div element'),
])

lastVNode = patch(lastVNode, newVNode)

const modeVNode = h('div', {
    style: {
        backgroundColor: 'red'
    },
    on: {
        click: handler
    }
}, [h('p', 'this is a p element in div')])

// const modeVNode = h('div',{
//     style: {
//         backgroundColor : 'red'
//     },
//     on: {
//         click: handler
//     }
// }, '我是红色的')

function handler() {
    console.log('被点了');
}

setTimeout(() => {
    console.log(lastVNode, modeVNode);
    patch(lastVNode, modeVNode)
}, 2000);


// clean VNode
setTimeout(() => {
    patch(lastVNode, h('!'))
}, 2000);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值