赞
踩
虚拟DOM是真实DOM的JavaScript表示。它是一个轻量级的JavaScript对象,可以表示DOM的结构和属性,虚拟DOM与真实DOM一一对应。
在Vue中,每个Vue组件都会维护一个对应的虚拟DOM树。当组件的数据发生变化时,Vue的响应式系统会触发虚拟DOM的重新渲染过程。Vue的虚拟DOM实现(Vue 2.x使用的是snabbdom的一个分支,而Vue 3.x则完全重写了虚拟DOM的实现)会生成一个新的虚拟DOM树,并与旧的虚拟DOM树进行对比(这个过程称为diff算法)。diff算法会找出最小量的需要更新的DOM节点,然后Vue会将这些变化应用到真实的DOM上,从而更新用户界面。
虚拟DOM带来最直接的好处:性能优化。因为:直接操作DOM是昂贵的,因为DOM操作会触发浏览器的重排(reflow)和重绘(repaint)。Vue可以对抽象虚拟DOM树进行增、删、改等节点操作,经过diff算法得到一些需要修改的最小单位,再更新视图,减少了DOM操作,提高性能。
虚拟DOM最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力。以及由diff算法实现减少JS操作真实DOM带来的性能消耗。
Vue通过createElement
生成VNode。每个VNode有children,children每个元素也是一个VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树。
diff算法是一种通过同层的树节点进行比较的高效算法。特点:
- 比较只会在同层级比较;
- diff比较过程中,循环从两边向中间比较
比较方式
diff算法整体策略:深度优先,同层比较
原理分析
set()
方法会调用Dep.notify()
通知所有Watcher,Watcher会调用patch(oldNode, Vnode)
给真实DOM打补丁,更新相应的视图。isSameVNode
进行判断,相同则会调用patchVNode
方法。组件(Components)是构成APP的业务模块;插件(Plugins)是对Vue功能的增强与补充。
在Vue中,每一个.vue
文件都可以视为一个组件。优势:
Vue.component()
)、局部注册(components
)插件通常是用来给全局添加功能的,一般分为:
Vue.prototype
)插件实现
Vue插件的实现是暴露一个install
方法。这个方法由构造器、可选的选项对象两个参数构成,即function(Vue, options)
插件注册:Vue.use(插件名, {...options})
注意:插件注册,需要在调用new Vue()
启动之前完成。Vue.use()
会自动阻止多次注册相同插件。
Vue的生命周期:从创建到销毁的过程。
Vue的生命周期共分为8个阶段 + 2个特殊特殊阶段:
vm.$el
未创建created
常用于异步数据获取$el
挂载完成mounted
阶段创用于获取访问数据和dom常见问题
数据请求放在created
和mounted
中有什么区别?
数据请求放在
mounted
阶段有可能会导致页面闪动,主要原因是由于此时页面DOM结构已经生成。所以建议在页面加载前完成请求,也就是在created
阶段完成。
双向数据绑定,主要是Model和View两者的数据更新问题。Vue的双向数据绑定主要由三个部分组成:
以上分层的架构方案即MVVM,VM即双向数据绑定的核心功能。主要职责:
ViewModel主要由两部分组成:
Vue的双向绑定流程:
new Vue()
执行初始化,对data
执行响应式处理,Observer执行;data
中获取并初始化视图,即compiler执行;data
中的某个key
在一个视图中可能出现多次,所以每个key
会有一个Dep管家来管理多个Watcher;template的解析步骤
vNode
)_update
方法主要调用patch
,将vNode转换为真是DOM,并且更新到页面中。Vue的挂载过程:
new Vue()
会调用_init
方法,定义$set
、$get
等方法、事件以及生命周期钩子;$mount
进行页面挂载,主要是通过mountComponent()
方法updateComponent()
更新函数render
函数生成虚拟DOM_update
函数将虚拟DOM生成真是DOM,渲染到页面组件通信的本质就是信息同步,即共享到Vue中。每个组件之间都有独立的作用域,数据无法共享。但实际使用中,需要共享一些信息数据,所以组件通信的目的就是让各组件之间能进行通讯。
组件通信的8中方案:
props
$emit
$ref
$emit
与$on
搭配使用$parent
或者$root
:.on()
与.emit()
attrs
与listeners
:祖先传递数据给子孙,包含了父级作用域中不作为props
被识别的特性绑定。可以使用v-bind=$attrs
传入内部组件provide
与inject
:组先传递数据给后代组件data
属性在我们定义好一个组件以后,Vue最终会通过Vue.extend()
构成组件实例。
data
可以是对象也可以是函数,由于根实例是单例,所以不会出现数据污染的情况。data
必须是函数,防止多个组件实例对象之间公用一个data
,产生数据污染。Vue2是通过Object.defineProperty()
实现数据响应式的。动态给Vue添加一个新的属性,无法实现响应式,因此无法触发事件属性的拦截,因此页面不会更新。
解决方案
Vue不允许在已经创建的实例上动态添加新的响应式属性,若想实现数据与视图的同步更新,可以采取以下3种方案:
Vue.set(target, key, vaue)
:内部实现即再次调用Vue.defineReactive()
方法实现响应式;Object.assign({}, oldObj, newObj)
:创建一个新的对象,合并原对象和混入对象的属性$forceUpdated()
:强制重新渲染v-if
和 v-for
的优先级v-for
与v-if
都是Vue模板系统中的指令。在Vue编译的时候,会先将指令系统转换为可执行的render
函数。
编译渲染过程中,都是线渲染,再判断,即:v-for
比v-if
优先级要高。
常见问题
v-for
和v-if
不建议同时使用在同一个元素上;由于实现都是每次渲染都是先循环再进行条件判断,会带来不必要的性能浪费。必要时候可使用
<template>
标签做v-if
判断操作。
computed
提前过滤不需要显示的项数据;v-show
和 v-if
v-show
原理不论初始条件是什么,元素总是会被渲染。有transaction
就执行,没有则设置display
属性。仅表示css属性的切换;
v-if
原理返回一个Node节点,render
函数通过表达式的值决定是否生成DOM。
v-show
和 v-if
都能控制DOM的显示,但是 v-if
有更高的切换消耗,v-show
有更好的初始渲染消耗。两者利弊取舍。
key
key
是每一个虚拟DOM的唯一id,也是diff算法的一种优化策略。根据key
,可以更准确、快速的定位到对应的节点。
mixin
Mixin(混入),提供了一种非常灵活的方式来分发Vue组件中可复用的功能。
分类
Vue.mixin(mixin)
mixins: []
属性赋值注意事项
当组件存在于mixins
对象相同的选项时,进行递归合并的时候,组件的选项会覆盖mixins
的选项。如果相同选项为生命周期钩子的时候,会合并为一个数组,先执行mixins
,再执行组件的钩子。(mergeOptions
方法的使用)
分类
v-model
):.lazy
、.trim
、.number
@xx
):.stop
(event.stopPropagation)、.prevent
(event.preventDefault)、.self
、.once
、.native
、.capture
、.passive
v-bind
修饰符:.sync
、.prop
(设置自定义便签属性,避免暴露数据,防止污染HTML结构)$nextTick
Vue 中的
$nextTick
是用于在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用该方法,获取更新后的DOM。
主要原因:Vue在更新DOM时是异步执行的,视图需要等待更新队列中所有数据变化完成之后,再统一更新、去重。因此,如果在数据变化之后,直接获取DOM,有可能会拿到旧的DOM。
$nextTick
本质就是一种优化策略,使同一时间的多次更新、多个数据更新影响到的视图,做一次更新即可。
keep-alive
是Vue的一个内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive
的属性:
include
exclude
max
其中,keep-alive
是在组件的mounted
钩子函数中观测include
和exclude
的变化并处理的,this.cache
对象对缓存的组件做存取以及移除等。
keep-alive
的基本用法
<keep-alive>
<component :is="view" />
</keep-alive>
设置了keep-alive
缓存的组件,会有activated
和deactivated
l两个生命周期钩子。
缓存的组件如何获取新数据
解决方案:
beforRouterEnter(to, from, next)
activated
(服务器端渲染期间,activated
不可用)跨域的本质实际上就是浏览器的基于同源策略的一种安全手段。
所谓同源(即在同一个域)具有三个相同点:
反之非同源请求,即:协议、端口、主机三者其中一项不同,则会产生跨域。
解决跨域的方法由很多,比如:
Access-Control-Allow-Origin
响应头设置)。Vue中,通过vue-cli脚手架搭建的项目,可在vue.config.js文件下配置proxy参数:
devServer: {
...
proxy: {
'/api': { // 代理标识
target: '目标服务器地址',
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/api': ""
}
}
}
}
再通过axios配置请求的根路径:
axios.defaults.baseURL = '/api'
其它跨域解决办法:
nginx
实现代理等注册指令的方式
Vue.directive()
directive
属性配置bind(el, binding)
:指定第一次绑定到元素时调用,用于初始化配置等;inserted(el, binding)
:被绑定元素插入到父节点时调用;update(el, binding)
:所在组件Vnode更新时调用,可能发生在其子Vnode更新之前componentUpdated(el, binding)
:指令所在Vnode及其子Vnode全部更新后调用;unbind(el, binding)
:只调用一次,指令与元素解绑时每个钩子函数都有4个参数:
示例:
防止表单重复提交
Vue.directive('throttle', {
bind: (el, binding) => {
let throttleTime = binding.value
if(!throttleTime ) throttleTime = 2000 // 节流时间默认2s
}
let cbFnc
el.addEventListener('click', evt => {
if(!cbFnc) { // 第一次执行
cbFnc = setTimeout(_=>{
cbFnc = null
}, throttleTime )
} else {
evt && evt.stopImmediatePropagation()
}
}, true)
})
分类
Vue.filter()
filters
注意事项
原理分析
3. 在编一阶段通过parseFilters
将过滤器编译为函数调用;
4. 编译后通过resolveFilter
函数找到过滤器并返回结果
5. 执行结果作为参数传递给toString
,toString
执行后,其结果保存在Vnode的Text属性中,渲染到视图。
slot其实就是Vue组件内的一个占位符,作为承载分发内容的出口,允许用户在使用的时候自定义内容。
通过插槽,可以让用户自定义拓展所需的组件,去更好的复用以及定制化处理。
插槽可以分为3类:
name
属性+父#name
。#default=slotProps
或者结构获取#testProps = {user, id}
;v-slot
属性只能在<template>
标签上使用,但在只有默认插槽时可以在组件标签上使用。slot本质上是返回Vnode的函数。Vue中的组件要渲染到页面上,流程:template - render function - Vnode - DOM。
渲染插槽函数renderSlot
。_render
渲染函数通过_normalizeScopedSlots
得到vm.$scopedSlots
。
axios是一个轻量的HTTP客户端:基于XMLHTTPRequest服务来执行HTTP请求,支持Promise。
class Axios {
constructor() {},
request(consifg) {
return new Promise(resolve, reject) => {
const {url = '', method = 'get', data = {}, } = config
const xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.onload = function() {
resolve(xhr.responseText)
}
xhr.send(data)
}
}
}
function CreateAxios() {
const req = new Axios()
return req.request.bind(axios)
}
const axios = CreateAxios()
export default axios
axios.interceptors.request.use(
config => {
token && (config.headers.Authorization = token);
return config
},
error => {
return Promise.error(error)
}
)
axios.interceptors.response.use(
res=> {
const {status, data} = res
if(status === 200) {
if(data.code === 511) {
// 未授权
} else if(data.code === 510) {
// 未登录
} else {
return Promise.resolve(res)
}
} else {
return Promise.reject(res)
}
},
err => {
if(err.response.status) {
return Promise.reject(error.response)
}
}
)
export function httpRequest({url, type, {params = {}, data = {}}}) {
const method = toUpperCase(type || 'get')
return new Promise((resolve, reject) => {
axios({
method,
url,
data: data || {},
params: params || {}
})
.then(res=> resolve(res))
.catch(err => reject(err))
})
}
CancekTiken.source().cancel()
const CancelToken = axios.CancelToken;
const source = CancekTiken.source()
axios.get(url, {cancelToken: source.token})
// 取消请求
source.cancel('请求原因')
const CancelToken = axios.CancelToken;
let cancel = null
axios.get(url, {
cancelToken: new CancelToken(function exectutor(x) {
cancel = x
})
})
// 取消请求
cancel('请求原因')
错误类型
处理办法
axios.interceptors.response.use()
);errorHandler
:指定组件的渲染和观察期间未捕获错误的处理函数。)Vue.config.errorHandler = (err, vm, info) => {
// 只在v2.2.0+可用
}
前端权限控制分为4个方面:
一般在请求拦截器进行token校验与拦截处理以及响应拦截处理(没有权限一般为401)
菜单权限可以理解为将页面与路由进行解耦。
实现方法
一般采用自定义按钮权限鉴权指令实现。
Vue.directive('permit', {
bind(el, binding, vnode) {
let pemitArr = []
if(binding.value) {
permitArr = Array,of(bingding.value)
} else {
permitArr = vnode.context.$route.meta.permits
}
}
})
SPA单页面应用,通过动态重写当前页面与用户交互,根据需要动态状态适当资源并装载到页面上,页面任何时间点都不会重新加载。
MPA多页面应用,每个页面都是一个主页面,每次访问一个页面,都需要重新加载html/css/js文件。
优点
缺点
hash模式
核心是通过监听URL中的hash来进行路由跳转
history模式
核心是借用HTML5 history API:
history.pushState
:添加历史记录history.replaceState
:修改历史记录基于Vue的SPA实现SEO的3种方式:
user-agent
判断),转发大一个node server,再通过Phantomjs来解析完整的HTML,返回给爬虫。首屏加载时间可以通过DOMContentLoad或者performance来计算。
// 方案一
document.addEventListener('DOMContentLoad', evt => {
console.log('first contentful painting')
})
// 方案二
performance.getEntriesByName('first-contentful-paint')[0].startTime
SPA首屏加载慢的原因:
解决办法
commonsChunkPlugin
中的minChunks
compression-webpack-plugin
SSR:服务器端渲染。只由服务器完成页面的HTML结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
缺点
所以使用SSR,需要慎重考虑:
对于同构开发,我们依然使用webpack打包。实现:
服务端首屏渲染:生成一个服务端bundle文件
客户端激活:生成一个客户端bundle文件
代码结构:新增两个入口,其它与之前的Vue应用相同
|- src
|--router
|--index.js # 路由声明文件
|--store
|--router # 全局状态
|--main.js # Vue实例创建
|--entry-client.js # 客户端入口
|--entry-server.js # 服务端入口
import Vue from 'vue'
import Router from 'vue-router'
Vue.user(Router)
export function createRouter() {
return new Router({
mode: 'history',
routers: [
// 客户端没有编译器 要写成渲染函数
{path: '/', component: {render: h=>h('div', 'index page')}},
{path: '404', component: {render: h=>h('div', '404 page')}}
]
})
}
import Vue from 'vue'
import Vuex from 'vuex'
Vue.user(Vuex)
export function createStore() {
return new Vuex.store({
state: {},
mutations: {},
actions: {
// 异步请求
}
})
}
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
// 客户端数据预取处理
Vue.mixin({
beforeMount() {
const { asyncData } = this.$options
if(asyncData) {
this.dataPromise = asyncData({
store: this.$store,
route: this.$route
})
}
}
})
// 导出Vue实例工厂函数 为每次请求创建独立的实例(上下文用于给Vue实例传递参数)
export function createApp(context) {
const router = createRouter()
const app = new Vue({
router,
context,
store,
render: h=>h(App)
})
return {app, router}
}
import {createApp} from './main'
// 返回一个函数 接收请求上下文 返回创建的实例
export default context => {
// 返回一个Promise 确保路由、组件准备就绪
return new Promise((resolve, reject) => {
const {app, router} = createApp(context)
// 跳转到首屏地址
router.push(context.url)
// 路由准备就绪 返回结果
router.onReady(_=>{
// 获取匹配的路由组件数组
const matchedComponents = router.getMatchedComponents()
// 无匹配 抛出异常
if(!matchedComponents.length) return reject({code: 404})
// 对所有匹配的路由组件调用可能存在的异步数据获取`asyncData()`
Promise.all(
matchedComponents.map(com => {
if(com.asyncData) return com.asyncData({
store,
router: router.currentRoute
})
})
).then( _ => {
// 所有预取钩子 resolve 后,store已经填入渲染应用所需的状态 将状态附加到上下文
// 且 template 渲染用于 renderer 时,状态将自动序列化为 window.__INITIAL_STATE__,并注入HTML
content.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
import { createApp } from './main'
// 创建vue、router实例
const { app, router, store } = createApp()
// 当时用template时,context.state将作为 window.__INITIAL_STATE__ 状态自动嵌入到HTML汇总
// 客户端挂载到应用程序之前 store就应该获取到状态
if(window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
// 路由就绪 执行挂载激活
router.onReady(_ => {
app.$mount('#app')
})
yarn add webpack-node-externals loadsh.merge -D
yarn add cross-env -D
const VueSSRServePlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
// 根据环境变量决定入口文件和配置项
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'
module.exports = {
css: { extract : false },
outputDir: `./dist/${target}`,
configureWebpack: _ => ({
// 入口文件配置
entry: `./src/entry-${target}.js`,
// 对 bundle renderer 提供 source map支持
devtool: 'source-map',
// target 设置为node:使webpack以Node适用的方式处理动态导入,并且还会在编译Vue组件时告知 vue-loader 输出面向服务器的代码
target: TARGET_NODE ? 'node' : 'web',
// 是否模拟node全局变量
node: TARGET_NODE ? undefined : false,
output: {
// 适用Node峰哥导出模块
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
// 外置化应用程序依赖模块 提高构建速度,生成较小打包文件
externals: TARGET_NODE ? nodeExternals({ whitelist: [/\.css$/] }) : undefined,
optimization: { splitChunks: undefined },
plugins: [TARGET_NODE ? new VueSSRServePlugin() : VueSSRClientPlugin]
}),
chainWebpack: config => {
// cli4项目 补充添加
if(TARGET_NODE) {
config.optimization.delete('splitChunks')
}
config.module
.rule('vue')
.use('vue-loader')
.tap(opt => {
merge(opt, {optimizeSSR: false})
})
}
}
package.json脚本配置
"scripts": {
"build:client": "vue-cli-service build",
"build:server": "cros-env WEBPACK_TARGET=node vue-cli-service build",
"build": "npm run build:server && npm run build:client"
}
index.html文件配置
服务端渲染入口位置。(不能为了好看而在后面加空格!!!)
<body>
<!--vue-ssr-outlet-->
</body>
beforeMount
钩子完成数据获取速度更快
update
性能提高1.3 ~ 2倍体积更小
通过Webpack的tree-shaking
功能,可以将无用模块“剪辑”,仅打包需要的能够tree-shaking
,有两大好处:
更易维护
更接近原生
更易使用
<teleport>
:任意门,使可以在组件的逻辑位置写模板代码,然后在Vue应用范围之外渲染它。Composition API的使用
setup
入口refs
Vue3其它变更
tree-shakable
v-model
用法更改<template v-for>
和v-for
节点上的key
用法已更改v-if
和v-for
的优先级已更改v-bind="object"
排序敏感v-for
中的ref
不再注册ref
数组functional
属性在单文件组件(SFC)defineAsyncComponent()
方法创建$scopedSlots
属性已删除,所有插槽都通过$slots
作为函数暴露class
名调整:v-enter-form
、v-leave-from
watch
选项和实例方法$watch
不在支持点分隔字符串路径,请改用计算函数作为参数outerHTML
将替换为根组件模板,Vue3使用应用程序容易的innerHTML
。destoryed
重命名为unmounted
;beforDestroy
重命名为beforeUnmount
beforeCreate
和created
钩子在setup
合并使用;<template>
没有特殊指令的标记会被视为普通元素,并将生成原生的<template>
元素。filter
attribute
$destory
实例方法(用户不应再手动管理单个Vue组件的生命周期)Vue2 项目普遍存在的以下问题:
以上问题,通过使用 Composition API都解决了。
Options API:选项API,即以.vue
为后缀的文件。通过methods
、computed
、watch
、data
等属性与方法,共同处理页面逻辑。
export default {
data: _=>({}),
computed: {},
watch: {},
methods: {},
}
Composition API中,组件根据逻辑功能来组织,一个功能所定义的所有API会放一起(高内聚,低耦合)。
逻辑组织
Options API将参数和实现等分离组织,对逻辑关注点存在含糊不清等问题;Composition API则是支持将某个关注点相关的代码全部放在一个函数里,修改功能时,能快速定位和实现。
逻辑复用
Vue2使用mixins
实现逻辑实现复用,支持多个mixin
文件使用。存在问题:
而通过Composition API,编写hook函数,组件内请求使用,数据来源清晰,且命名冲突问题也会解决了。
总结
tree-skaking
更友好this
的使用,减少了this
指向不明的情况更小、更快、更友好。
tree-shaking
,将无用模块剪辑,按需打包;示例:获取鼠标位置
utils.js
import {toRefs, reactive, onMounted, onBeforeUnmount} from 'vue'
export function getMouse() {
const state = reactive({
x: 0,
y: 0
})
const update = e => {
state.x = e.pageX
state.y = e.pageY
}
onMounted(_ => {
window.addEventListener('mousemove', update)
})
onBeforeUnmount(_ => {
window.removeEventListener('mousemove', update)
})
return toRefs(state)
}
使用:
<script>
import {getMouse} from '@/utils/utils'
export function {
setup() {
const {x, y} = getMouse()
return {
x,
y
}
}
}
</script>
源码:
性能
Object.defineProperty
调整为Proxy监听整个对象。注意:Proxy并不能监听到内部深层次的对象变化,Vue3处理方式:在getter
中递归响应式(使得真正访问到的内部对象才变成响应式)。
语法API:即Composition API
实现一个 Modal组件,要求:
实现流程
body
上,因为要在当前Vue实例外独立存在(使用Teleport
传送门)。<Teleport to='body' :disabled='!isTeleport'>
<div v-if='modelValue' class='ui_modal'>
<!--遮罩层-->
<div class='mask' :style='style' @click='maskClose && !loading && handleCancel()' />
<!--主体-->
<div class='modal__main'>
<!--标题栏-->
<div class='modal__title line line--b'>
<span>{{title || '标题'}}</span>
<span v-if='close' title='关闭' class='close' @click='!loading && handleCcancel()'>x</span>
</div>
<div class='model__content'>
<Content v-if='typeof content === "function"' :render='content' />
<slot v-else>{{ content }}</slot>
</div>
<div class='model__bottom line line--t'>
<button :disabled='loading' @click='handleConfirm()'>
<span class='loading' v-if='loading'>...</span>确认
</button>
<button @click='!loading && handleCancel()'>
取消
</button>
</div>
</div>
</div>
</Teleport>
使用
$modal.show({
title: 'Test',
content(h){
return h(
'div',
{
style: 'color:red',
onClick:($event: Event) => console.log('click', $event.target),
},
'hello world'
)
}
})
// JSX 语法
$modal.show({
title: 'Test',
content(){
return (
<div onClick={($event:Event) => console.log('click', $event.target)}>
hellow world
</div>
)
})
Vue.extend
的方式获取组件实例,然后挂载到body
上。Vue3移除了Vue.extend()
方法,但是可以通过createVNode
实现:
import Modal from '@/components/Modal/Index'
const container = document.createElement('div')
const vnode = createVNode(Modal)
render(vnode, container)
const instance = vnode.component
document.body.appendChild(container)
在Vue2中,可以通过this
调用全局API,但是Vue3的setup
中已经没有this
的概念了,需要调用app.config.globalProperties
挂载到全局。
setup(props, ctx) {
let instance = getCurrentInstance(); // 获取当前组件实例
onBeforeMount(_ => {
instance._hub = {
'on-confirm': _=>{},
'on-cancel': _ => {}
}
});
const handleConfirm = _ => {
ctx.emit('on-confirm')
instance._hub['on-confirm']()
}
const handleCancel = _ => {
ctx.emit('on-cancel')
ctx.emit('update:modalValue', false)
instance._hub['on-cancel']()
}
return {
handleConfirm,
handleCancel
}
}
_hub
实现:
app.config.globalProperties.$modal = {
show({
onConfirm,
onCancel
}) {
const {props, _hub} = instance;
const _closeModal = _ => {
props.modalValue = false
constainer.parentNode!.removeChild(container)
}
// _hub 新增具体事件实现
Object.assign(_hub, {
async 'on-confirm'() {
if(confirm) {
const fn = onConfirm()
if(fn && fn.then) {
try {
props.loading = true
await fn;
props.loading = false
_closeModal()
} catch (err) {
console.log(err)
props.loading = false
}
} else {
_closeModal()
}
} else {
_closeModal()
}
},
'on-cancel'() {
onCancel && onCancel()
_closeModal()
}
})
}
}
Vue2中,每个组件实例都会对应一个 watcher
实例,在组件渲染过程中把用到的数据记录为依赖,改变会触发setter
,通给值watcher
,从而使关联的组件重新渲染。对于静态和动态节点共存时的变化,diff算法和遍历会浪费。
因此,Vue3在编译阶段,做了以下优化:
createStaticVNode
方法在客户端生成一个static node,这些静态node会直接被innerHTML
。Vue3 移除了一些不常用的API,并使用Tree Shaking
。任何一个函数,如:ref
、reactive
等,仅仅在用到的时候才打包,没用到的模块都会被摇掉。
Vue2采用Object.defineProperty
来劫持整个对象,进行深度遍历属性,添加getter
和setter
实现响应式。
Vue3采用Proxy
重写了响应式系统。由于Proxy
可以对整个对象进行监听,因此不需要深度遍历,就可以监听数组的增、删、改等操作。
**Object.defineProperty()**方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有对象,并返回此对象。
**Object.defineProperty()**通过getter
和setter
两个属性的使用,实现defineReactive()
函数:
function updateVal() {
APP.innerText = obj.foo
}
function defineReactive(obj, key, value) {
Object.definerProperty(obj, key, {
get() {
return value
},
set(newValue) {
if(newValue !== value) {
value = newValue
updateVal()
}
}
})
}
defineReactive(obj, 'foo', '')
数据发生变化的时候,就会触发updateValue
方法,实现数据响应。
在对象存在多个key
值时,需要进行遍历、递归遍历等:
function observe(obj) {
if(typeof obj !== 'object' || obj === null) {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
当给key
赋值为对象时,还要再set
属性中递归、并且在给一个对象进行删除、添加属性等操作时,无法劫持到、如果存在深层嵌套对象关系,需要深层的进行监听,造成很大性能问题等。
总结
Proxy的监听是针对一个对象的,对对象的所有操作都会进入监听操作,这就完全可以代理所有属性了。
定义:Proxy用于定义基本操作的自定义行为。
本质:Proxy的修改的是程序默认行为,属于元编程。Proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如:属性查找、赋值等)
用法
Proxy为构造函数,用来生成Proxy实例
// target:要拦截的目标对象
// handler:通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理`P`的行为
const proxy = new Proxy(target, handler)
若在Proxy内部调用对象的默认行为,建议使用Reflect,基本特点
object
方法的返回结果,让其变得更合理object
操作编程函数行为Proxy的几种用法
get(target, key, receiver)
:能够对数组增删改查进行拦截;set(target, key, value, receiver)
Object.defineProperty()
只能遍历对象属性进行劫持,Proxy直接可以劫持整个对象,并返回一个新对象。我们可以只操作新的对象达到响应式目的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。