vue2中的diff算法 vue2.x中的虚拟dom是进行全量的对比,例如css
vue2的在线编译 vue-template-explorer.netlify.app/html
function render() {
with(this) {
return _c('div', [_c('p', [_v("hello world ")]), _c('p', [_v(_s(msg) +
" ")])])
}
}
复制代码
vue3中的diff算法,只会给须要动态改变的标签会打上一个flag,这样就减小了diff算法的比对次数前端
vue3的在线编译 vue-next-template-explorer.netlify.app/vue
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "hello world "),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
复制代码
那么这个flag为1是什么意思呢node
TEXT = 1 // 动态文本节点
CLASS=1<<1,1 // 2//动态class
STYLE=1<<2,// 4 //动态style
PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
FULLPR0PS=1<<4,// 16 //具备动态key属性,当key改变时,须要进行完整的diff比较。
HYDRATE_ EVENTS = 1 << 5,// 32 //带有监听事件的节点
STABLE FRAGMENT = 1 << 6, // 64 //一个不会改变子节点顺序的fragment
KEYED_ FRAGMENT = 1 << 7, // 128 //带有key属性的fragment 或部分子字节有key
UNKEYED FRAGMENT = 1<< 8, // 256 //子节点没有key 的fragment
NEED PATCH = 1 << 9, // 512 //一个节点只会进行非props比较
DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot
HOISTED = -1 // 静态节点
复制代码
Vue2.x中虚拟dom每次都会从新建立,进行比对渲染 Vue3.0中对不参与更新的元素,会作静态提高,只会被建立一次,在渲染时直接复用便可 未被提高前react
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "hello world "),
_createVNode("p", null, "hello "),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
复制代码
被提高后webpack
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "hello world ", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "hello ", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
复制代码
在vue2中,在元素身上绑定事件后,由于事件是动态变化的,因此会认为dom发生了变化web
import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", { onClick: _ctx.handleClick }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["onClick"]))
}
// Check the console for the AST
复制代码
而在vue3中。会默认给事件加上缓存算法
import { toDisplayString, createVNode, openBlock, createBlock, withScopeId } from "vue"
// Binding optimization for webpack code-split
const _toDisplayString = toDisplayString, _createVNode = createVNode, _openBlock = openBlock, _createBlock = createBlock, _withScopeId = withScopeId
const _withId = /*#__PURE__*/_withScopeId("scope-id")
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick(...args)))
}, _toDisplayString(_ctx.msg), 1 /* TEXT */))
})
// Check the console for the AST
复制代码
npm init vite-app hello-vue3 (# OR yarn create vite-app hello-vue3)
复制代码
npm install -g @vue/cli (# OR yarn global add @vue/cli)
vue create hello-vue3
# select vue 3 preset
复制代码
setup 函数是一个新的组件选项。它做为在组件内部使用组合 API 的入口点。
调用时间:在建立组件实例时,在初始 prop 解析以后当即调用 setup。在生命周期方面,它是在 beforeCreate 钩子以前调用的。 其余能够参考官方文档(好比setup里面的两个参数)vue-cli
在vue2中只须要在data里定义数据,就能够实现数据层-视图层的双向绑定,而在vue3中使用ref接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具备指向内部值的单个 property.value 例如:
<template>
<div>
{{num}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let num = ref(0)
function handleAdd() {
num.value= 2
// num = 2 这种写法是错误的。由于ref把里面的数据包装成了一个对象,可是在template中不须要.value vue会根据__v_isRef进行处理
}
console.log(num)
/*{ __v_isRef: true
_rawValue: 0
_shallow: false
_value: 2
value: 2
}
*/
return {num,handleAdd}
}
}
</script>
复制代码
reactive的做用和ref的做用是相似的,都是将数据变成可相应的对象,其实ref的底层其实利用了reactive。 二者的区别,ref包装的对象须要.value ,而reactive中的不须要
<template>
<div>
{{num.person.name}}----{{num.person.age}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { ref,reactive } from 'vue'
export default {
name: 'App',
setup() {
let num = reactive({
person:{
name:'yj',
age:18,
}
})
function handleAdd() {
num.person.age = num.person.age+1
}
return {num,handleAdd}
}
}
</script>
复制代码
建立一个 ref,它跟踪本身的 .value 更改,但不会使其值成为响应式的。 使用trigger主动出发视图更新
<template>
<div>
{{num.person.name}}----{{num.person.age}}---{{num.person.origin.aa.gf}}---{{num.a}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { shallowRef, triggerRef } from 'vue'
export default {
name: 'App',
setup() {
let num = shallowRef({
a:1,
person:{
name:'yj',
age:18,
origin:{
aa:{
gf:"bb"
}
}
}
})
function handleAdd() {
num.value.a = 2
triggerRef(num)
}
return {num,handleAdd}
}
}
复制代码
shallowReactive 只监听第一层值的变化,深层次的不监听(值会发生改变,可是视图不更新)
<template>
<div>
{{num.person.name}}----{{num.person.age}}---{{num.person.origin.aa.gf}}---{{num.a}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { shallowReactive } from 'vue'
export default {
name: 'App',
setup() {
let num = shallowReactive({
a:1,
person:{
name:'yj',
age:18,
origin:{
aa:{
gf:"bb"
}
}
}
})
function handleAdd() {
num.a++
}
return {num,handleAdd}
}
}
</script>
复制代码
返回 reactive 或 readonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引发代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
<template>
<div>
{{num}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { reactive,toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
let num = reactive(obj)
let obj1 = toRaw(num) // 暴露出原对象
console.log(obj1===obj) // true
function handleAdd() {
// num 这里是对obj进行了地址引用,可是改变obj的值,并不会出发视图更新
obj.a = '222233'
console.log(num)
}
return {num,handleAdd}
}
}
</script>
复制代码
<template>
<div>
{{num.a}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { ref,toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
let num = ref(obj)
let obj1 = toRaw(num.value)
console.log(obj1===obj)
function handleAdd() {
obj1.a = '22233333'
console.log(num)
// obj.a = '222233'
// console.log(num)
}
return {num,handleAdd}
}
}
</script>
复制代码
标记一个对象,使其永远不会转换为代理。返回对象自己。
<template>
<div>
{{num}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { markRaw, reactive,toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
let obj1 = markRaw(obj)
let num = reactive(obj1)
function handleAdd() {
num.a = '2222'
console.log(num)
}
return {num,handleAdd}
}
}
</script>
复制代码
<template>
<div>
{{num}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { markRaw, ref, } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
let obj1 = markRaw(obj)
let num = ref(obj1)
function handleAdd() {
num.value.a = '2222'
console.log(num.value)
}
return {num,handleAdd}
}
}
</script>
复制代码
若是利用toRef将一个数据变成响应式数据,是会影响到原始数据,可是响应式数据经过toRef。并不回出发ui界面更新(ref式改变,不会影响到原始数据)
<template>
<div>
{{state}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { ref, toRef } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
// let state = ref(obj.a)
let state = toRef(obj,'a')
console.log(state)
function handleAdd() {
// 修改响应式数据,并不回改变原始数据
state.value= "2222222"
console.log(state)
console.log(obj)
// 若是利用toref将一个数据变成响应式数据,是会影响到原始数据,可是响应式数据经过toref。并不回出发ui界面更新
}
return {state,handleAdd}
}
}
</script>
复制代码
相似toRef,只是一次性处理屡次toRef
<template>
<div>
{{num.a}}
</div>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { toRefs,toRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj ={
a:1,
b:2,
c:3
}
let num = toRefs(obj)
function handleAdd() {
num.a.value="1111"
num.b.value="222"
num.c.value="3333"
console.log(obj)
}
return {num,handleAdd}
}
}
</script>
复制代码
建立一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它须要一个工厂函数,该函数接收 track 和 trigger 函数做为参数,并应返回一个带有 get 和 set 的对象。
<template>
<div>
{{msg}}
</div>
<ul>
<li v-for="(item,index) in state" :key="index">{{item.name}}----{{item.age}}</li>
</ul>
<button @click="handleAdd">按钮</button>
</template>
<script>
import { customRef,ref} from 'vue'
export default {
name: 'App',
setup() {
let state = getData('../public/ab.json',[])
console.log(state)
let msg = initState(5)
function handleAdd(){
msg.value ++
}
return {msg,handleAdd,state}
}
}
function getData(url,value){
return customRef((track,trigger) => {
fetch(url).then(res=>{
return res.json()
}).then(data=>{
value = data.data
trigger()
})
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
trigger() // 出发视图更新
}
}
})
}
function initState(value) {
return customRef((track,trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
trigger() // 出发视图更新
}
}
})
}
</script>
复制代码
<template>
<div ref="box">
我是div
</div>
</template>
<script>
import {onMounted, ref} from 'vue'
export default {
name: 'App',
setup() {
onMounted(()=>{
console.log(box.value)
})
let box = ref(null)
console.log(box.value)
return {box}
}
}
</script>
复制代码
<template>
<div>
{{state.list.b.b1}}
<button @click="handleAdd">按钮</button>
</div>
</template>
<script>
import { readonly,shallowReadonly,isReadonly } from 'vue'
export default {
name: 'App',
setup() {
// let state = readonly({
// list:{
// a:'1111',
// b:{
// b1:"2222",
// c:{
// c2:"3333"
// }
// }
// }
// })
let state = shallowReadonly({
list:{
a:'1111',
b:{
b1:"2222",
c:{
c2:"3333"
}
}
}
})
function handleAdd() {
state.list= '3333333333'
console.log(state)
}
return {state,handleAdd}
}
}
</script>
复制代码
<template>
<div>
{{state.name}}
<button @click="handleAdd">按钮</button>
</div>
</template>
<script>
export default {
name: 'App',
setup() {
let obj = {
name:"1111",
age:19,
info:{
bb:"55555"
}
}
// let state = _shallowreactive(obj)
// function handleAdd(){
// state.name ='fghhrt'
// state.info.bb ='bdfgsfsd'
// }
let state = _shallowref(obj)
function handleAdd(){
state.value = '222'
}
return {state,handleAdd}
}
}
function _shallowreactive(obj){
return new Proxy(obj,{
get(obj,prop){
return obj[prop]
},
set(obj,prop,value){
obj[prop] = value
console.log("更新ui界面")
console.log(obj)
return true
}
})
}
function _shallowref(val) {
return _shallowreactive({value:val})
}
</script>
复制代码
<template>
<div>
{{state.name}}
<button @click="handleAdd">按钮</button>
</div>
</template>
<script>
export default {
name: 'App',
setup() {
let obj = {
name:"1111",
age:19,
info:{
bb:"55555"
}
}
let state = _reactive(obj)
function handleAdd(){
state.name = '444444'
state.info.bb = '222'
}
return {state,handleAdd}
}
}
function _reactive(obj){
if(typeof obj === 'object'){
if(obj instanceof Array){
// 若是是数组,取出遍历
obj.forEach((item,index)=>{
// 若是数组里面的值是仍是对此安好
if(typeof item === 'object'){
obj[index] = _reactive(item)
}
})
}else{
// 若是是对象
for(let key in obj){
let item = obj[key]
if(typeof item === 'object'){
obj[key] = _reactive(item)
}
}
}
}else{
console.warn("不是对象")
}
return new Proxy(obj,{
get(obj,prop){
return obj[prop]
},
set(obj,prop,value){
obj[prop] = value
console.log("更新ui界面")
console.log(obj)
return true
}
})
}
</script>
复制代码
<template>
<div>
<!-- {{state.name}} -->
<button @click="handleAdd">按钮</button>
</div>
</template>
<script>
export default {
name: 'App',
setup() {
let obj = {
name:"1111",
age:19,
info:{
bb:"55555"
}
}
let state = _shallowReadonly(obj)
function handleAdd(){
state.name = '222'
// state.info.bb = '123123'
}
return {state,handleAdd}
}
}
function _shallowReadonly(obj){
return new Proxy(obj,{
get(obj,prop){
return obj[prop]
},
set(obj,prop,value){
console.warn("不能修改")
return true
// obj[prop] = value
// console.log("更新ui界面")
// console.log(obj)
// return true
}
})
}
</script>
复制代码
有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 以外的其余位置 最多见的就是相似于element的dialog组件 dialog是fixed定位,而dialog父元素的css会影响dialog。所以要将dialog放在body下
<teleport to="body">
<div class="modal__mask">
<div class="modal__main">
<div class="modal__header">
<h3 class="modal__title">弹窗标题</h3>
<span class="modal__close">x</span>
</div>
<div class="modal__content">
弹窗文本内容
</div>
<div class="modal__footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</div>
</teleport>
复制代码
在同一目标上使用多个 teleport 一个常见的用例场景是一个可重用的 组件,它可能同时有多个实例处于活动状态。对于这种状况,多个 组件能够将其内容挂载到同一个目标元素。顺序将是一个简单的追加——稍后挂载将位于目标元素中较早的挂载以后。
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- result-->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
复制代码
前端开发中异步请求是很是常见的事情,好比远程读取图片,调用后端接口等等 Suspense是有两个template插槽的,第一个default表明异步请求完成后,显示的模板内容。fallback表明在加载中时,显示的模板内容。 子组件 child
<template>
<h1>{{result}}</h1>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
result: 1000
})
}, 5000)
})
}
})
</script>
复制代码
父组件 当异步没有执行完的时候。使用fallback里面的组件,当执行成功以后使用default
<Suspense>
<template #default>
<Child />
</template>
<template #fallback>
<h1>Loading !...</h1>
</template>
</Suspense>
复制代码
在vue3.x的版本中,可使用onErrorCaptured这个钩子函数来捕获异常。 当上面的异步发生错误的时候,onErrorCaptured 会捕获错误
const app = {
name: "App",
components: { Child},
setup() {
onErrorCaptured((error) => {
return true
})
return {};
},
};
复制代码
在vue2中,咱们建立一个组件,他只有有一个根节点
<template>
<div>
<div>hello</div>
<div>world</div>
</div>
</template>
复制代码
为何vue2须要这样写,为了底层diff算法 在vue3中能够不用这样写
<template>
<div>hello</div>
<div>world</div>
</template>
复制代码
vue3中默认建立了一个Fragment,尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样咱们能够将组件功能绑定到一个单一的元素中,而不须要建立一个多余的DOM节点。
Vue 3 目前提供一个 emits 选项,和现有的 props 选项相似。这个选项能够用来定义组件能够向其父组件触发的事件。
在vue2中
emits默认要写上,不然当自定义事件和原生事件重名的时候,事件会默认被调用两次
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text']
}
</script>
复制代码
在vue3中
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
复制代码
<template>
<div>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
export default{
emits: {'click':(type)=>{
return type===1
}},
setup() {
},
methods:{
handleClick() {
this.$emit('click',1)
}
}
}
</script>
复制代码
<div id="app">
<input v-model="price">
</div>
<script>
new Vue({
el: '#app',
data: {
price: ''
}
});
</script>
复制代码
其实v-model只是一个语法糖,内部实现是这样
<input type="text" :value="price" @input="price=$event.target.value">
复制代码
在vue中父子组件传值的方法
父组件
<template>
<div>
<!--在子组件中用emit("test")传达给父组件的getData方法中-->
<child @test="getData" :keywords="keywords"></child>
<button @click="submit">提交</button>
</div>
</template>
<script>
import child from './child.vue'
export default {
data() {
return {
keywords: '123143'
}
},
components: {
child
},
methods: {
// 在这里实现更改父组件的值
getData(val){
this.keywords = val
},
submit() {
console.log('keywords:', this.keywords)
}
}
}
</script>
子组件
<template>
<div>
<input @input="inputChange" type="text" :value="keywords">
</div>
</template>
<script>
export default {
props: ['keywords'],
methods: {
inputChange(e) {
// 传给父元素的test函数
this.$emit('test', e.target.value)
}
}
}
</script>
复制代码
这种办法一相对麻烦一点,须要定义在父组件中也要定义一个事件
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,可是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不一样的目的。model 选项能够用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
复制代码
如今在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
复制代码
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
<template>
<div class="details">
<myComponent
:show.sync='valueChild'
style="padding: 30px 20px 30px 5px;border:1px solid #ddd;margin-bottom: 10px;"
>
</myComponent>
<button @click="changeValue">toggle</button>
</div>
</template>
<script>
import Vue from 'vue'
Vue.component(
'myComponent', {
template: `
<div v-if="show">
<p>默认初始值是{{ show }},因此是显示的</p>
<button @click.stop="closeDiv">关闭</button>
</div>`,
props: ['show'],
methods: {
closeDiv() {
this.$emit('update:show', false); //触发 input 事件,并传入新值
}
}
})
export default {
data() {
return {
valueChild: true,
}
},
methods: {
changeValue() {
this.valueChild = !this.valueChild
}
}
}
</script>
复制代码
父组件
<model02
v-model:age="age02"
v-model:name="name02"
></model02>
复制代码
子组件
<template>
<div class="custom-input">
<h1>vue3中的v-model</h1>
<input type="text" :value="age" @input="onAgeInput"/>
<br>
<input type="text" :value="name" @input="onNameInput"/>
</div>
</template>
<script>
export default {
name: "Model02",
props: [
'age',
'name'
],
methods: {
onAgeInput(e) {
this.$emit('update:age', parseFloat(e.target.value));
},
onNameInput(e) {
this.$emit('update:name', e.target.value)
}
}
}
</script>
复制代码
在vue2中
export default {
render(h) {
return h('div')
}
}
复制代码
在vue3中 从vue中导出h
import { h, reactive } from 'vue'
export default {
setup(props, { slots, attrs, emit }) {
const state = reactive({
count: 0
})
function increment() {
state.count++
}
// 返回render函数
return () =>
h(
'div',
{
onClick: increment
},
state.count
)
}
}
复制代码
vue2中 domProps 包含 VNode props 中的嵌套列表 点击事件是在on里面
{
class: ['button', 'is-outlined'],
style: { color: '#34495E' },
attrs: { id: 'submit' },
domProps: { innerHTML: '' },
on: { click: submitForm },
key: 'submit-button'
}
复制代码
在vue3中
{
class: ['button', 'is-outlined'],
style: { color: '#34495E' },
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}
复制代码
在vue2中data能够是一个函数或者一个对象,在vue3中data只能是一个函数
在vue2中,函数组件的两种写法
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(`h${props.level}`, data, children)
}
}
复制代码
<template functional>
<component
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
复制代码
在vue3中 不支持函数组件,你能够像下面这样书写
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
复制代码
在 3.x 中,有状态组件和函数式组件之间的性能差别已经大大减小,而且在大多数用例中是微不足道的。所以,在 SFCs 上使用 functional 的开发人员的迁移路径是删除该 attribute,并将 props 的全部引用重命名为 attrs。
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
复制代码
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
复制代码
const AsyncComponent = () => ({
// 须要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展现加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 若是提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
复制代码
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)
复制代码
AsyncPageWithOptions: defineAsyncComponent({
loader: () => import(".NextPage.vue"),
delay: 200,
timeout: 3000,
errorComponent: () => import("./ErrorComponent.vue"),
loadingComponent: () => import("./LoadingComponent.vue"),
})
复制代码
vue2中的 off 和 $once 实例方法已被移除,应用实例再也不实现事件触发接口 在vue3中若是还想用,就使用第三方库mitt
vue3中不在支持 非兼容:再也不支持使用数字 (即键码) 做为 v-on 修饰符 非兼容:再也不支持 config.keyCodes
<!-- 键码版本 -->
<input v-on:keyup.13="submit" />
<!-- 别名版本 -->
<input v-on:keyup.enter="submit" />
复制代码
此外,你能够经过全局 config.keyCodes 选项。
Vue.config.keyCodes = {
f1: 112
}
复制代码
<!-- 键码版本 -->
<input v-on:keyup.112="showHelpText" />
<!-- 自定别名版本 -->
<input v-on:keyup.f1="showHelpText" />
复制代码
<!-- Vue 3 在 v-on 上使用 按键修饰符 -->
<input v-on:keyup.delete="confirmDelete" />
复制代码
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 当被绑定的元素挂载到 DOM 中时……
mounted(el) {
// 聚焦元素
el.focus()
}
})
复制代码
created:在绑定元素的 attribute 或事件监听器被应用以前调用。在指令须要附加需要在普通的 v-on 事件监听器前调用的事件监听器时,这颇有用。 beforeMount:当指令第一次绑定到元素而且在挂载父组件以前调用。 mounted:在绑定元素的父组件被挂载后调用。 beforeUpdate:在更新包含组件的 VNode 以前调用。 updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。 beforeUnmount:在卸载绑定元素的父组件以前调用 unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。 每个钩子函数都有以下参数: el: 指令绑定的元素,能够用来直接操做DOM binding: 数据对象,包含如下属性 instance: 当前组件的实例,通常推荐指令和组件无关,若是有须要使用组件上下文ViewModel,能够从这里获取 value: 指令的值 oldValue: 指令的前一个值,在beforeUpdate和Updated 中,能够和value是相同的内容。 arg: 传给指令的参数,例如v-on:click中的click。 modifiers: 包含修饰符的对象。例如v-on.stop:click 能够获取到一个{stop:true}的对象 vnode: Vue 编译生成的虚拟节点, prevVNode: Update时的上一个虚拟节点
假设咱们如今须要开发一个定位指令,就是把某个元素固定在页面的某个位置。
<div v-fix:left="20">
{{count}}
</div>
directives: {
fix: {
mounted(el) {
el.style.position = 'fixed'
},
}
}
复制代码
directives: {
fix: {
mounted(el) {
el.style.position = 'fixed'
const s = binding.arg || 'top'
el.style[s] = binding.value + 'px'
},
}
}
复制代码
这样咱们就完成了一个自定义指令的开发
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
复制代码
<template>
<div>
<div>
123123123
</div>
<div>
{{count}}
</div>
<button @click="handleClick">增长</button>
</div>
</template>
<script>
import { ref,watchEffect } from 'vue'
export default {
emits:['click'],
setup() {
let count = ref(0)
const handleClick = () =>{
count.value++
}
watchEffect(() => console.log(count.value))
return {
count,
handleClick
}
},
}
</script>
复制代码
watchEffect 会返回一个用于中止这个监听的函数
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
复制代码
假设咱们如今用一个用户ID去查询用户的详情信息,而后咱们监听了这个用户ID, 当用户ID 改变的时候咱们就会去发起一次请求,这很简单,用watch 就能够作到。 可是若是在请求数据的过程当中,咱们的用户ID发生了屡次变化,那么咱们就会发起屡次请求,而最后一次返回的数据将会覆盖掉咱们以前返回的全部用户详情。这不只会致使资源浪费,还没法保证 watch 回调执行的顺序。而使用 watchEffect 咱们就能够作到。
// 当id改变的时候,以前的异步没有执行完成,会取消以前的异步
watchEffect((onInvalidate) => {
const token = asyncOperation(id.value);
onInvalidate(() => {
// run if id has changed or watcher is stopped
token.cancel();
});
});
复制代码
Vue 的响应性系统会缓存反作用函数,并异步地刷新它们,这样能够避免同一个“tick” 中多个状态改变致使的没必要要的重复调用。 若是你但愿反作用函数在组件更新前发生,能够将flush设为'post'(默认是'pre') 通常用于获取dom以后的值,会用到post
watchEffect(
() => {
/* ... */
},
{
flush: 'post'//默认值为 'pre'
}
)
复制代码
和watchEffect相比较
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
复制代码
watch ([countObj, count], ([oneNewName, twoNewName], [oneOldName, twoOldName]) => {
console.log(oneNewName, oneOldName, twoNewName, twoOldName)
})
复制代码
watch 与 watchEffect共享中止侦听,清除反作用 (相应地 onInvalidate 会做为回调的第三个参数传入)、反作用刷新时机和侦听器调试行为。
把 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
export default{
data(){
return{
}
},
created() {
// do something...
},
methods:{...}
}
// vue页面中引入
import mixin from 'mixin.js'
export default{
data(){},
mixins: [mixin]
}
复制代码
const myMixin = {
data() {
return {
message: 'hello',
foo: 'abc'
}
}
}
const app = Vue.createApp({
mixins: [myMixin],
data() {
return {
message: 'goodbye',
bar: 'def'
}
},
created() {
console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
}
})
复制代码
const myMixin = {
data() {
return {
message: 'hello',
foo: 'abc'
}
}
created() {
console.log(this.$.message) // => { message: "goodbye", foo: "abc", bar: "def" }
}
}
const app = Vue.createApp({
mixins: [myMixin],
data() {
return {
message: 'goodbye',
bar: 'def'
}
},
created() {
console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
}
})
复制代码
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。(这个和vue2同样)
自定义合并策略
const app = Vue.createApp({
custom: 'hello!'
})
app.config.optionMergeStrategies.custom = (toVal, fromVal) => {
console.log(fromVal, toVal)
// => "goodbye!", undefined
// => "hello", "goodbye!"
return fromVal || toVal
}
app.mixin({
custom: 'goodbye!',
created() {
console.log(this.$options.custom) // => "hello!"
}
})
复制代码
vue3中
createApp(Main).mount('#app')
vue2中
new Vue({
})
复制代码
其余api的调整
Vue.config app.config
Vue.config.productionTip removed
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties
复制代码
所有调整为 import {nextTick,observable} from 'vue'
Vue.nextTick
Vue.observable (用 Vue.reactive 替换)
Vue.version
Vue.compile (仅完整构建版本)
Vue.set (仅兼容构建版本)
Vue.delete (仅兼容构建版本)
复制代码
后期更新
后期更新
<template>
<ul>
<input type="text" placeholder="id" v-model="addform.form.id">
<input type="text" placeholder="姓名" v-model="addform.form.name">
<input type="text" placeholder="年龄" v-model="addform.form.age">
<button @click="handleAdd">按钮</button>
<li v-for="(item,index) in todoList.list" :key="item.id" @click="handleDelete(index)">{{item.name}}---{{item.age}}</li>
</ul>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let {todoList,handleDelete} = DeleteTodo()
let {addform,handleAdd} = AddTodo(todoList)
return {todoList,addform,handleDelete,handleAdd}
},
}
function DeleteTodo() {
let obj = [
{id:1,name:'zs',age:10},
{id:2,name:'ls',age:20},
{id:3,name:'ww',age:30}
]
let todoList = reactive({list: obj});
function handleDelete(i){
todoList.list = todoList.list.filter((item,index)=>{
return index !== i
})
}
return {todoList,handleDelete }
}
function AddTodo(todoList) {
let form = {
id: '',
name: '',
age: ''
}
let addform = reactive({form:form})
function handleAdd() {
todoList.list.push(form)
}
return { addform, handleAdd}
}
</script>
复制代码
如上图: 在vue2中若是咱们须要实现一个todolist,首先咱们须要在data里面定义数据,而后在methods里面定义方法,能够还须要computed,或者watch,这样一个功能的代码逻辑在多处使用到了,不利于维护。而vue3中能够将一个功能的代码逻辑抽离出来,这样便于维护