// vue2 初始化实例
const app = new Vue({ /* 选项 */ });
// vue3 初始化实例
const app = Vue.createApp({ /* 选项 */ });
// vue2
Vue.use(/* ... */);
Vue.mixin(/* ... */);
Vue.component(/* ... */);
Vue.directive(/* ... */);
// vue3
app.use(/* ... */);
app.mixin(/* ... */);
app.component(/* ... */);
app.directive(/* ... */);
两种形式声明触发的事件:
null
或一个验证函数。<!-- 父组件 -->
<template>
<!-- 引用子组件 -->
<HelloWorld :msg="msg" @onSayHello="sayHello" />
</template>
<!-- 子组件 -->
<script>
export default {
name: 'Helloworld',
props: {
msg: String
},
// 声明父组件传递的事件
emits: ['onSayHello'],
setup(props, { emit }) {
// 引用
emit('onSayHello', 'bbb');
}
}
</script>
// 对象语法
export default {
emits: {
// 没有验证函数
click: null,
// 具有验证函数
submit: (payload) => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
}
<!-- 在 methods 里定义 one、two 两个函数 -->
<button @click="one($event), two($event)">Submit</button>
vue2
模板里面只能是单一节点;vue3
里面可以是多个节点<!-- vue2 组件模板 -->
<template>
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
</template>
<!-- vue3 组件模板 -->
<template>
<h3>{{ title }}</h3>
<div v-html="content"></div>
</template>
<!-- vue2 -->
<MyComponent v-bind:title.sync="title" />
<!-- 是以下的简写 -->
<MyComponent
v-bind:title="title"
v-on:update:title="title = $event"
/>
<!-- vue3 -->
<MyComponent v-model:title="title" />
<!-- 是以下的简写 -->
<MyComponent :title="title" @update:title="title = $event" />
// vue2
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component.vue')
}
});
// vue3
import { createApp, defineAsyncComponent } from 'vue';
createApp({
// ...
components: {
AsyncComponent: defineAsyncComponent(() => import('./my-async-component.vue'))
}
});
<!-- 以下 filter 在 vue3 中不可用了 -->
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | fomatId"></div>
把弹窗放到组件外面去。
<!-- 在 data 中设置 modalOpen: false -->
<button @click="modalOpen = true">
Open full screen modal!(With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
teleport 弹窗(父元素是 body)
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
<Suspense>
<template>
<!-- Test1 是一个异步组件 -->
<Test1 />
</template>
<!-- #fallback 就是一个具名插槽
即 Suspense 组件内部,有两个 slot
其中一个具名为 fallback -->
<template #fallback>
loading...
</template>
</Suspense>
composition API
带来了什么?
Option API
代码逻辑是分散的;组合式API
代码逻辑是组合到一起的。出处:https://coding.imooc.com/lesson/419.html
Options API
不利于推导类型和属性。{
data() {
return {
a: 10
};
}
methods: {
fn1() {
const a = this.a;
}
},
mounted() {
this.fn1(); // 获取 a
// 不利于推导类型和属性
}
}
Composition API
函数在哪定义,传入什么,返回什么都是很清晰的。
出处:https://coding.imooc.com/lesson/419.html
出处:https://coding.imooc.com/lesson/419.html
通过它来创建引用类型的响应式数据。
reactive
.value
修改值dom
节点<template>
<p>ref demo {{ageRef}} {{state.name}}</p>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
name: 'Ref',
setup() {
const ageRef = ref(20); // 值类型 响应式
const nameRef = ref('章三');
const state = reactive({
name: nameRef
});
setTimeout(() => {
console.log('ageRef', ageRef.value);
ageRef.value = 25; // .value 修改值
nameRef.value = '章三A';
}, 1500);
return {
ageRef,
state
};
}
}
</script>
<template>
<p ref="elemRef">我是一行文字</p>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'RefTemplate',
setup() {
const elemRef = ref(null)
onMounted(() => {
console.log('ref template', elemRef.value.innerHTML, elemRef.value)
})
return {
elemRef
}
}
}
</script>
reactive
封装)的 prop
ref
,具有响应式<template>
<p>toRef demo - {{ageRef}} - {{state.name}} {{state.age}}</p>
</template>
<script>
import { toRef, reactive } from 'vue';
export default {
name: 'ToRef',
setup() {
const state = reactive({
age: 20,
name: '章三'
});
// // toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式
// const state = {
// age: 20,
// name: '章三'
// };
const ageRef = toRef(state, 'age');
setTimeout(() => {
state.age = 25;
}, 1500);
setTimeout(() => {
ageRef.value = 30; // .value 修改值
}, 3000);
return {
state,
ageRef
};
}
}
</script>
普通对象要实现响应式,用 reactive
;对象的某个属性要实现响应式,用 toRef
。
reactive
封装)转换成普通对象prop
都是对应的 ref
<template>
<p>toRefs demo {{age}} {{name}}</p>
</template>
<script>
import { toRefs, reactive } from 'vue';
export default {
name: 'ToRefs',
setup() {
const state = reactive({
age: 20,
name: '章三'
})
const stateAsRefs = toRefs(state) // 将响应式对象,变成普通对象
// const { age: ageRef, name: nameRef } = stateAsRefs; // 每个属性,都是 ref 对象
// return {
// ageRef,
// nameRef
// };
setTimeout(() => {
state.age = 25;
}, 1500);
return stateAsRefs;
// 正常写是 return { state }; 使用时是 {{state.name}} {{state.age}}
// 如果想直接使用 {{name}} {{age}} 就需要解构 state
// 直接解构 state 会失去响应式
// return {
// ...state
// };
}
}
</script>
// 合成函数返回响应式对象
function useFeatureX() {
const state = reactive({
x: 1,
y: 2
});
// 逻辑运行状态,省略 n 行
// 返回时转换为 ref
return toRefs(state);
}
export default {
setup() {
// 可以在不失去响应式的情况下破坏结构
const { x, y } = useFeatureX();
return {
x,
y
};
}
}
reactive
做对象的响应式,用 ref
做值类型响应式setup
中返回 toRefs(state)
,或者 toRef(state, 'xxx')
ref
的变量命名都用 xxxRef
toRefs
setup
、computed
、合成函数,都有可能返回值类型Vue
如果不定义 ref
,用户将自造 ref
,反而混乱ref
是一个对象,才能不丢失响应式,value
用来存储值.value
属性的 get
和 set
实现响应式reacttive
时,不需要 .value
,其他情况都需要ref
也是靠 reactive
实现的。类似于 ref = reactive({ value: '张三' }) ;
。
初衷:在不丢失响应式的情况下,把对象数据分解/扩散。
前提:针对的是 reactive
封装的响应式对象,并非普通对象。
reactive、ref
是创造响应式;toRef
和 toRefs
是延续响应式。
创建一个只读的数据。
setup
和其他 Composition API
中没有 this
getCurrentInstance
获取当前实例Options API
可照常使用 this
<template>
<p>get instance</p>
</template>
<script>
import { onMounted, getCurrentInstance } from 'vue';
export default {
name: 'GetInstance',
data() {
return {
x: 1,
y: 2
};
},
setup() {
console.log('this1', this); // undefined
onMounted(() => {
console.log('this in onMounted', this);
console.log('x', instance.data.x); // 1 应该放在 onMounted 这里获取
});
const instance = getCurrentInstance();
console.log('instance', instance);
console.log('x', instance.data.x); // undefined 因为 setup 等于 beforeCreate 和 created 此时还没有初始化完成 所以是 undefined
},
mounted() {
console.log('this2', this); // Proxy
console.log('y', this.y); // 2
}
}
</script>
watch
和 watchEffect
的区别:
data
属性变化watch
需要明确监听哪个属性watchEffect
会根据其中的属性,自动监听其变化<template>
<p>watch vs watchEffect</p>
<p>{{numberRef}}</p>
<p>{{name}} {{age}}</p>
</template>
<script>
// watch 监听
import { reactive, ref, toRefs, watch } from 'vue';
export default {
name: 'Watch',
setup() {
const numberRef = ref(100);
const state = reactive({
name: '章三',
age: 20
});
watch(
numberRef,
(newNumber, oldNumber) => {
console.log('ref watch', newNumber, oldNumber);
},
{
immediate: true; // 初始化之前就监听,可选
}
);
setTimeout(() => {
numberRef.value = 200;
}, 1500);
watch(
// 第一个参数,确定要监听哪个属性
() => state.age,
// 第二个参数,回调函数
(newAge, oldAge) => {
console.log('state watch', newAge, oldAge);
},
// 第三个参数,配置项
{
immediate: true, // 初始化之前就监听,可选
// deep: true // 深度监听
}
);
setTimeout(() => {
state.age = 25;
}, 1500);
setTimeout(() => {
state.name = '章三A';
}, 3000);
return {
numberRef,
...toRefs(state);
};
}
}
</script>
<template>
<p>watch vs watchEffect</p>
<p>{{numberRef}}</p>
<p>{{name}} {{age}}</p>
</template>
<script>
// watchEffect 监听
import { reactive, ref, toRefs, watchEffect } from 'vue';
export default {
name: 'Watch',
setup() {
const numberRef = ref(100);
const state = reactive({
name: '章三',
age: 20
});
watchEffect(() => {
// 初始化时,一定会执行一次(收集要监听的数据)
console.log('hello watchEffect');
});
watchEffect(() => {
// 写了哪个就监听哪个
console.log('state.name', state.name);
});
watchEffect(() => {
console.log('state.age', state.age);
});
// watchEffect(() => {
console.log('state.age', state.age);
console.log('state.name', state.name);
});
setTimeout(() => {
state.age = 25;
}, 1500);
setTimeout(() => {
state.name = '章三A';
}, 3000);
return {
numberRef,
...toRefs(state);
};
}
}
</script>
useXxxx
格式(React Hooks
也是)setup
中引用 useXxxx
函数// useMousePosition.js
import { reactive, ref, onMounted, onUnmounted } from 'vue';
function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
console.log('useMousePosition mounted');
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
console.log('useMousePosition unMounted');
window.removeEventListener('mousemove', update);
});
return {
x,
y
};
}
// function useMousePosition2() {
// const state = reactive({
// x: 0,
// y: 0
// });
// function update(e) {
// state.x = e.pageX;
// state.y = e.pageY;
// }
// onMounted(() => {
// console.log('useMousePosition mounted');
// window.addEventListener('mousemove', update);
// });
// onUnmounted(() => {
// console.log('useMousePosition unMounted');
// window.removeEventListener('mousemove', update);
// });
// return state;
// }
export default useMousePosition;
// export default useMousePosition2;
<template>
<p>mouse position {{x}} {{y}}</p>
</template>
<script>
import useMousePosition from './useMousePosition';
// import useMousePosition2 from './useMousePosition';
export default {
name: 'MousePosition',
setup() {
const { x, y } = useMousePosition();
return {
x,
y
};
// const state = useMousePosition2();
// return {
// state
// };
}
}
</script>
Composition API
的 setup
只会被调用一次,而 React Hooks
函数会被多次调用Composition API
无需 useMemo
、useCallback
,因为 setup
只调用一次Composition API
无需顾虑调用顺序,而 React Hooks
需要保证 hooks
的顺序一致Composition API
的 reacttive + ref
比 React Hooks
的 useState
要难理解vue3
给了两种生命周期的方式,可以用 Options API
,也可以用 Composition API
。
beforeDestroy
改为 beforeUnmount
destroyed
改为 unmounted
vue2
的生命周期vue3
中可以使用 vue2
的生命周期<template>
<p>生命周期 {{msg}}</p>
</template>
<script>
export default {
name: 'LifeCycles',
props: {
msg: String
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
// beforeDestroy 改名
beforeUnmount() {
console.log('beforeUnmount')
},
// destroyed 改名
unmounted() {
console.log('unmounted')
}
}
</script>
setup
:等于 beforeCreate
和 created
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
<template>
<p>生命周期 {{msg}}</p>
</template>
<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
name: 'LifeCycles',
props: {
msg: String
},
// 等于 beforeCreate 和 created
setup() {
console.log('setup')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
onUnmounted(() => {
console.log('onUnmounted')
})
},
}
</script>
Object.defineProperty
的缺点:
Vue.set
和 Vue.delete
这两个 API
去 做这个事情)Reflect
是和 Proxy
配合使用的。
proxy
可以看做是一个拦截器,会拦截对某个属性的操作;Reflect
是提供操作对象的 api
。Reflect
的作用:
Proxy
能力一一对应const data = {
name: 'zhangsan',
age: 20
};
const proxyData = new Proxy(data, {
get(target, key, recevier) {
// get 方法和直接读取对象的属性实现的效果是一样的,
// 但直接读取对象的属性是一种魔法,不符合函数式编程的理念,
// 而 get 方法是一种基于底层的 API,更符合理念。
const result = Reflect.get(target, key, recevier);
console.log('get', key);
return result; // 返回结果
},
set(target, key, val, recevier) {
const result = Reflect.set(target, key, val, recevier);
console.log('set', key, val);
return result; // 是否设置成功 true / false
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log('delete property', key);
return result; // 是否删除成功 true / false
}
});
// 测试监听数组
const data = ['a', 'b', 'c'];
const proxyData = new Proxy(data, {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target); // 获取不是原型的属性
if (ownKeys.includes(key)) {
console.log('get', key); // 监听
}
const result = Reflect.get(target, key, receiver);
return result; // 返回结果
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const result = Reflect.set(target, key, val, receiver);
console.log('set', key, val);
// console.log('result', result); // true
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log('delete property', key);
// console.log('result', result) // true
return result; // 是否删除成功
}
});
使用 Proxy
实现响应式:能规避 Object.defineProperty
的问题。
缺点:
Proxy
无法兼容所有浏览器,无法 polyfill
。
// 创建响应式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target;
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log('get', key); // 监听
}
const result = Reflect.get(target, key, receiver);
// 深度监听
return reactive(result);
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log('已有的 key', key);
} else {
console.log('新增的 key', key);
}
const result = Reflect.set(target, key, val, receiver);
console.log('set', key, val);
// console.log('result', result); // true
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log('delete property', key);
// console.log('result', result); // true
return result; // 是否删除成功
}
};
// 生成代理对象
const observed = new Proxy(target, proxyConf);
return observed;
}
// 测试数据
const data = {
name: 'zhangsan',
age: 20,
info: {
city: 'beijing',
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
};
const proxyData = reactive(data);
Proxy
深度监听,性能如何提升的?
之前 Object.defineProperty
是一开始就进行递归了,没有任何条件;而 Proxy
是在 get
的时候才进行递归,获取到哪一层哪一层才触发响应式,没有获取到那一层的时候不会触发响应式。
TEXT
、PROPS
diff
算法时,可以区分静态节点,以及不同类型的动态节点可以区分出动态和静态节点,静态节点就不用比较了,因为静态节点是不会变的,这样就可以优化 diff
算法。(从输入来源做的优化,并不是针对 diff
算法这个流程做的优化)
出处:https://coding.imooc.com/lesson/419.html
开启 ssr
后直接渲染字符串,没有经过 vdom
的一个转换。
API
根据模板的指令,选择需要 import
的 api
,需要才引入
编译时尽可能的减少体积,从而进行优化
Vite
是一个轻量级的、速度极快的构建工具。
浏览器的原生 ES Modules
能力允许在不将代码打包到一起的情况下运行 JavaScript
应用。Vite
的核心理念,就是借助浏览器原生 ES Modules
能力,当浏览器发出请求时,为浏览器按需提供 ES Module
文件,浏览器获取 ES Module
文件会直接执行。
ES6 Module
,无需打包(非常快)roolup
,并不会快很多开发环境下,由于浏览器原生 ES6 Modules
的支持,当浏览器发出请求时,Vite
可以在不将源码打包为一个 Bundle
文件的情况下,将源码文件转化为 ES Modules
文件之后返回给浏览器。这样 Vite
的应用启动和热更新 HMR
时的速度都不会随着应用规模的增加而变慢。
<!-- test1.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demo</title>
</head>
<body>
<p>基本演示</p>
<script type="module">
import add from './src/add.js';
const res = add(1, 2);
console.log('add res', res);
</script>
</body>
</html>
// print.js
export default function (a, b) {
console.log(a, b);
}
// add.js
import print from './print.js';
export default function add(a, b) {
print('print', 'add');
return a + b;
}
Vite
直接使用 ES6 Module
这种模块化的形式去执行;
Webpack
要把 ES6
打包成 ES5
。
// print.js
export default function (a, b) {
console.log(a, b)
}
// add.js
import print from './print.js'
export default function add(a, b) {
print('print', 'add')
return a + b
}
// math.js
export function add(a, b) {
return a + b
}
export function multi(a, b) {
return a * b
}
// index.js
import { add, multi } from './math.js'
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
<!-- test2.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demo</title>
</head>
<body>
<p>外链</p>
<script type="module" src="./src/index.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demo</title>
</head>
<body>
<p>远程引用</p>
<script type="module">
import { createStore } from 'https://unpkg.com/redux@latest/es/redux.mjs'
console.log('createStore', createStore)
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demo</title>
</head>
<body>
<p>动态引入</p>
<button id="btn1">load1</button>
<button id="btn2">load2</button>
<script type="module">
document.getElementById('btn1').addEventListener('click', async () => {
const add = await import('./src/add.js')
const res = add.default(1, 2)
console.log('add res', res)
})
document.getElementById('btn2').addEventListener('click', async () => {
const { add, multi } = await import('./src/math.js')
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
})
</script>
</body>
</html>
Webpack
:ES Modules
,CommonJS
和 AMD Modules
Dev Server
:通过 webpack-dev-server
托管打包好的模块webpack
Vite
:ES Modules
Dev Server
:原生 ES Modules
,浏览器托管执行Rollup
JSX
最早是 React
提出的概念。
.jsx
格式的文件和 defineComponent
// child.jsx
import { defineComponent } from 'vue'
export default defineComponent({
props: ['a'],
setup(props) {
const render = () => {
return <p>Child {props.a}</p>
}
return render
}
})
<!-- Demo.vue -->
<template>
<p @click="changeFlag">Demo {{flagRef}}</p>
<child a="abc" v-if="flagRef"></child>
<ul>
<li v-for="item in state.list" :key="item">{{item}}</li>
</ul>
</template>
<script>
import { ref, reactive } from 'vue'
import Child from './Child'
export default {
name: 'Demo',
components: { Child },
setup() {
const flagRef = ref(true)
function changeFlag() {
flagRef.value = !flagRef.value
}
const state = reactive({
list: ['a', 'b', 'c']
})
return {
flagRef,
changeFlag,
state
}
}
}
</script>
<!-- Demo1.vue -->
<script>
import { ref } from 'vue'
import Child from './Child'
export default {
components: { Child },
setup() {
const countRef = ref(200)
const render = () => {
return <p>demo1 {countRef.value}</p> // jsx
}
return render
}
}
</script>
// Demo1.jsx
import { defineComponent, ref, reactive } from 'vue'
import Child from './Child'
export default defineComponent(() => {
const flagRef = ref(true)
function changeFlag() {
flagRef.value = !flagRef.value
}
const state = reactive({
list: ['a1', 'b1', 'c1']
})
const render = () => {
return <>
<p onClick={changeFlag}>demo1 {flagRef.value.toString()}</p>
{flagRef.value && <Child a={flagRef.value}></Child>}
<ul>
{state.list.map(item => <li>{item}</li>)}
</ul>
</>
}
return render
})
// 1. setup 函数
// 2. 组件的配置
语法上有很大区别:
JSX
本质上就是 js
代码,可以使用 js
的任何能力template
只能嵌入简单的 js
表达式,其他需要指令,如 v-if
JSX
已经成为 ES
规范, template
还是 Vue
自家规范本质是相同的:
js
代码(render
函数)具体示例:插值,自定义组件,属性和事件,条件和循环
<!-- Demo.vue -->
<template>
<tabs default-active-key="1" @change="onTabsChange">
<tab-panel key="1" title="title1">
<div>tab panel content 1</div>
</tab-panel>
<tab-panel key="2" title="title2">
<div>tab panel content 2</div>
</tab-panel>
<tab-panel key="3" title="title3">
<div>tab panel content 3</div>
</tab-panel>
</tabs>
</template>
<script>
import Tabs from './Tabs'
import TabPanel from './TabPanel'
export default {
components: { Tabs, TabPanel },
methods: {
onTabsChange(key) {
console.log('tab changed', key)
}
},
}
</script>
<!-- TabPanel.vue -->
<template>
<slot></slot>
</template>
<script>
export default {
name: 'TabPanel',
props: ['key', 'title'],
}
</script>
<!-- Tabs.vue -->
<template>
<div>
<!-- tabs 头,按钮 -->
<button
v-for="titleInfo in titles"
:key="titleInfo.key"
:style="{ color: titleInfo.key === actKey ? 'blue' : '#333' }"
@click="changeActKey(titleInfo.key)"
>
{{titleInfo.title}}
</button>
</div>
<slot></slot>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Tabs',
props: ['defaultActiveKey'],
emits: ['change'],
setup(props, context) {
const children = context.slots.default()
const titles = children.map(panel => {
const { key, title } = panel.props || {}
return {
key,
title
}
})
// 当前 actKey
const actKey = ref(props.defaultActiveKey)
function changeActKey(key) {
actKey.value = key
context.emit('change', key)
}
return {
titles,
actKey,
changeActKey
}
}
}
</script>
<!-- Demo.vue -->
<template>
<tabs default-active-key="1" @change="onTabsChange">
<tab-panel key="1" title="title1">
<div>tab panel content 1</div>
</tab-panel>
<tab-panel key="2" title="title2">
<div>tab panel content 2</div>
</tab-panel>
<tab-panel key="3" title="title3">
<div>tab panel content 3</div>
</tab-panel>
</tabs>
</template>
<script>
import Tabs from './Tabs.jsx'
import TabPanel from './TabPanel'
export default {
components: { Tabs, TabPanel },
methods: {
onTabsChange(key) {
console.log('tab changed', key)
}
},
}
</script>
<!-- TabPanel.vue -->
<template>
<slot></slot>
</template>
<script>
export default {
name: 'TabPanel',
props: ['key', 'title'],
}
</script>
// Tabs.jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Tabs',
props: ['defaultActiveKey'],
emits: ['change'],
setup(props, context) {
const children = context.slots.default()
const titles = children.map(panel => {
const { key, title } = panel.props || {}
return {
key,
title
}
})
// 当前 actKey
const actKey = ref(props.defaultActiveKey)
function changeActKey(key) {
actKey.value = key
context.emit('change', key)
}
// jsx
const render = () => <>
<div>
{/* 渲染 buttons */}
{titles.map(titleInfo => {
const { key, title } = titleInfo
return <button
key={key}
style={{ color: actKey.value === key ? 'blue' : '#333' }}
onClick={() => changeActKey(key)}
>{title}</button>
})}
</div>
<div>
{children.filter(panel => {
const { key } = panel.props || {}
if (actKey.value === key) return true // 匹配上 key ,则显示
return false // 否则,隐藏
})}
</div>
</>
return render
}
})
<!-- Child.vue -->
<template>
<p>child</p>
<slot :msg="msg"></slot>
</template>
<script>
export default {
data() {
return {
msg: '作用域插槽 Child'
}
}
}
</script>
<!-- Demo.vue -->
<template>
<child>
<!-- <p>scoped slot</p> -->
<template v-slot:default="slotProps">
<p>msg: {{slotProps.msg}} 123123</p>
</template>
</child>
</template>
<script>
import Child from './Child'
export default {
components: { Child }
}
</script>
// Child.jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: ['render'],
setup(props) {
const msgRef = ref('作用域插槽 Child - JSX')
return () => {
return <p>{props.render(msgRef.value)}</p>
}
}
})
// Demo.jsx
import { defineComponent } from 'vue'
import Child from './Child'
export default defineComponent(() => {
function render(msg) {
return <p>msg: {msg} 123123</p>
}
return () => {
return <>
<p>Demo - JSX</p>
<Child render={render}></Child>
</>
}
})
Vue3
引入了 Composition API
Composition API
最重要的是 setup
函数<script>
只有一个 setup
函数,显得有点多此一举,所以使用 script setup
进行简化ref
、reactive
、computed
的能力<script>
同时使用<!-- Demo.vue -->
<script>
function add(a, b) { return a + b }
</script>
<script setup>
import { ref, reactive, toRefs, onMounted } from 'vue'
const countRef = ref(100)
function addCount() {
countRef.value++
}
const state = reactive({
name: '双越'
})
const { name } = toRefs(state)
console.log( add(10, 20) )
</script>
<template>
<p @click="addCount">{{countRef}}</p>
<p>{{name}}</p>
</template>
defineProps
:定义属性defineEmits
:定义事件<script setup>
<!-- Child2.vue -->
import { defineProps, defineEmits } from 'vue'
// 定义属性
const props = defineProps({
name: String,
age: Number
})
// 定义事件
const emit = defineEmits(['change', 'delete'])
function deleteHandler() {
emit('delete', 'aaa')
}
</script>
<template>
<p>Child2 - name: {{props.name}}, age: {{props.age}}</p>
<button @click="$emit('change', 'bbb')">change</button>
<button @click="deleteHandler">delete</button>
</template>
<!-- Demo.vue -->
<script>
function add(a, b) { return a + b }
</script>
<script setup>
import { ref, reactive, toRefs, onMounted } from 'vue'
import Child2 from './Child2'
const countRef = ref(100)
function addCount() {
countRef.value++
}
const state = reactive({
name: '章三'
})
const { name } = toRefs(state)
console.log( add(10, 20) )
function onChange(info) {
console.log('on change', info)
}
function onDelete(info) {
console.log('on delete', info)
}
</script>
<template>
<p @click="addCount">{{countRef}}</p>
<p>{{name}}</p>
<child-2 :name="name" :age="countRef" @change="onChange" @delete="onDelete"></child-2>
<hr>
</template>
<!-- Child3.vue -->
<script setup>
import { ref, defineExpose } from 'vue'
const a = ref(101)
const b = 201
defineExpose({
a,
b
})
</script>
<template>
<p>Child3</p>
</template>
<!-- Demo.vue -->
<script setup>
import { ref, reactive, toRefs, onMounted } from 'vue'
import Child3 from './Child3'
const child3Ref = ref(null)
onMounted(() => {
// 拿到 Child3 组件的一些数据
console.log(child3Ref.value)
console.log(child3Ref.value.a)
console.log(child3Ref.value.b)
})
</script>
<template>
<child-3 ref="child3Ref"></child-3>
</template>
Vue3
比 Vue2
有什么优势?
ts
支持描述 vue3
的生命周期。
Options API
生命周期:
beforeDestroy
改为 beforeUnmount
destroyed
改为 unmounted
vue2
的生命周期Composition API
生命周期:
setup
:等于 beforeCreate
和 created
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
如何看待 Composition API
和 Options API
?
composition API
带来了什么?
Options API
Composition API
Composition API
Composition API
属于高阶技巧,不是基础必会Composition API
是为解决复杂业务逻辑而设计Composition API
就像 Hooks
在 React
中的地位如何理解 ref
、toRef
和 toRefs
?
ref
reactive
.value
修改值dom
节点toRef
reactive
封装的响应式对象的 属性ref
,具有响应式toRefs
reactive
封装的响应式对象转换成普通对象ref
关于 ref
为什么是一个对象。
因为 Object.defineProperty
、proxy
都只能监听复杂数据类型,只不过 vue2
中是放在 data
中进行监听,而 vue3
不存在这样一个数据收集的地方,所以只能单个去收集数据。本质上 vue2
、vue3
都是需要外面“包裹”一层对象作为被监听数据的“承载容器”来进行 defineProperty
、proxy
监听。
为什么需要 ref
?
不管是 Object.definePropety
还是 proxy
,都是用来监听复杂数据类型的,object
或 array
,不能对简单数据类型 string,number,boolean
这些的变化进行直接监听。vue2
中是把监听内容放在 data
的返回对象中,在初始化的时候使用 Object.definePropety
对该对象进行监听,所以在对 data
的返回对象新添加数据的时候,需要使用特殊方法,而不能直接 this.xxx
这样添加,因为 vue2
这时候不会对该对象进行重新监听,数据会不具有响应式特性。
而 vue3
中没有 vue2
这种一个特定地方(即 vue2
中的 data
函数返回的那个对象)去对所有数据进行统一监听处理,所以我们需要告诉 vue3
,哪些是我们需要使用响应式属性的数据。本质上 ref
和 reactive
是在收集这些数据。关于 ref
,因为 proxy
无法直接监听简单数据类型,所以需要包裹成一个对象的形式来进行监听,proxy
就是去监听 ref
定义的这个对象,以此来监听 ref.value
这个值的变化。
vue3
升级了哪些重要功能?
new Vue()
变成 createApp
创建实例emits
属性:在子组件中使用 emits
属性声明父组件传递的事件v-on
可以传递多个事件Fragment
: vue2
模板里面只能是单一节点; vue3
里面可以是多个节点.sync
改为 v-model
参数defineAsyncComponent
filter
Teleport
可以把弹窗放到组件外面去Suspense
:具有深层异步依赖的组件,使用 Suspense
,在初始渲染时,Suspense
将在内存中渲染其默认的插槽内容Composition API
Options API
和 Composition API
;Options API
将 vue2
的beboreDestroy
和destroyed更名为beforeUnmount
和unmounted
,其余沿用vue2
的生命周期;Composition API
生命周期包括 onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmout、onUnmounted
,setup
等于 beforeCreate
和 created
。Composition API
如何实现代码逻辑复用?
useXxxx
格式(React Hooks
也是)setup
中引用 useXxxx
函数Vue3
如何实现响应式?
使用 Proxy
实现响应式:
get
中进行递归,获取到哪一层才进行递归。deleteProperty
属性监听删除;可在 set
中判断出是已有属性还是新增属性。缺点:
Proxy
无法兼容所有浏览器,无法 polyfill
。watch
和 watchEffect
的区别是什么?
data
属性变化watch
需要明确监听哪个属性watchEffect
会根据其中的属性,自动监听其变化setup
中如何获取组件实例?
setup
和其他 Composition API
中没有 this
getCurrentInstance
获取当前实例Vue3
为何比 Vue2
快?
Proxy
实现响应式:Object.defineProperty
的问题。
PatchFlag
静态标记:hoistStatic
静态提升:
TEXT
、PROPS
diff
算法时,可以区分静态节点,以及不同类型的动态节点cacheHandler
缓存事件:SSR
优化:ssr
后直接渲染字符串,不用经过 vdom
的一个转换。Tree-shaking
优化:API
,尽可能的减少使体积Vite
是什么?
Vite
是一个轻量级的、速度极快的构建工具。
Vite
是旨在提升开发者体验的下一代 JavaScript
构建工具,核心借助了浏览器的原生 ES Modules
和像 esbuild
这样的将代码编译成 native code
的打包工具。极大的降低了应用的启动和热更新时间。
Composition API
和 React Hooks
的对比。
Composition API
的 setup
只会被调用一次,而 React Hooks
函数会被多次调用Composition API
无需 useMemo
、useCallback
,因为 setup
只调用一次Composition API
无需顾虑调用顺序,而 React Hooks
需要保证 hooks
的顺序一致Composition API
的 reacttive + ref
比 React Hooks
的 useState
要难理解