vue3
- 1.认识vue3
- 1.1了解相关信息
- 1.2 性能提升:
- 1.3 新增特性
- 1.4 使用 vue-cli 创建vue项目
- 1.5 使用 vite 创建
- 2.全局api
- 2.1 createApp()
- 2.2 app.mount()
- 2.3 app.unmount()
- 2.4 app.provide()
- 2.5 app.component()
- 2.6 app.use()
- 2.7 app.version
- 2.8 app.config
- 2.9 app.config.errorHandler
- 3.0 app.config.warnHandler
- 3.setup()
- 3.1 setup
- 3.2 ref
- 3.3 访问 Props
- 3.4 Setup 上下文
- 3.5 暴露公共属性
- 3.6 reactive 引用类型数据响应式
- 3.7 vue2的响应式
- 3.8 Vue3的响应式
- 3.9 setup细节
- 3.10 reactive与ref-细节
- 3.11 readonly()
- 4.计算属性与监视
- 4.1计算属性
- 4.2监视
- 4.3 watchEffect函数
- 5.vue3生命周期
- 6.新组件
- 6.1 Fragment(片断)
- 6.2 Teleport(瞬移)
- 6.3 Suspense(不确定的)
- 6.4 Transition
- 6.5 KeepAlive
- 6.5.1 最大缓存实例数
- 6.5.2 包含/排除
- 6.5.3 缓存实例的生命周期
1.认识vue3
1.1了解相关信息
- Vue.js 3.0 “One Piece” 正式版在今年9月份发布
- 2年多开发, 100+位贡献者, 2600+次提交, 600+次PR
- Vue3支持vue2的大多数特性
- 更好的支持Typescript
1.2 性能提升:
- 打包大小减少41%
- 初次渲染快55%, 更新渲染快133%
- 内存减少54%
- 使用Proxy代替defineProperty实现数据响应式
- 重写虚拟DOM的实现和Tree-Shaking
1.3 新增特性
- Composition (组合) API
- setup
- ref 和 reactive
- computed 和 watch
- 新的生命周期函数
- provide与inject
- …
- 新组件
- Fragment - 文档碎片
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的loading界面
- 其它API更新
- 全局API的修改
- 将原来的全局API转移到应用对象
- 模板语法变化
1.4 使用 vue-cli 创建vue项目
文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project
接下来的步骤:
Please pick a preset - 选择 Manually select features
Check the features needed for your project - 选择上 TypeScript ,特别注意点空格是选择,点回车是下一步
Choose a version of Vue.js that you want to start the project with - 选择 3.x (Preview)
Use class-style component syntax - 直接回车
Use Babel alongside TypeScript - 直接回车
Pick a linter / formatter config - 直接回车
Use history mode for router? - 直接回车
Pick a linter / formatter config - 直接回车
Pick additional lint features - 直接回车
Where do you prefer placing config for Babel, ESLint, etc.? - 直接回车
Save this as a preset for future projects? - 直接回车
1.5 使用 vite 创建
- 文档: https://v3.cn.vuejs.org/guide/installation.html
- vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,
- 它做到了本地快速开发启动, 在生产环境下基于 Rollup 打包。
- 快速的冷启动,不需要等待打包操作;
- 即时的热模块更新,替换性能和模块数量的解耦让更新飞起;
- 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
2.全局api
2.1 createApp()
创建一个应用实例。
创建一个应用实例。
-
类型
function createApp(rootComponent: Component, rootProps?: object): App
-
详细信息
第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props。
-
示例
可以直接内联根组件:
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App)
2.2 app.mount()
将应用实例挂载在一个容器元素中。
-
类型
interface App { mount(rootContainer: Element | string): ComponentPublicInstance }
-
详细信息
参数可以是一个实际的 DOM 元素或一个 CSS 选择器 (使用第一个匹配到的元素)。返回根组件的实例。
如果该组件有模板或定义了渲染函数,它将替换容器内所有现存的 DOM 节点。否则在运行时编译器可用的情况下,容器元素的
innerHTML
将被用作模板。在 SSR 激活模式下,它将激活容器内现有的 DOM 节点。如果出现了激活不匹配,那么现有的 DOM 节点将会被修改以匹配客户端的实际渲染结果。
对于每个应用实例, mount() 仅能调用一次。
-
示例
import { createApp } from 'vue' const app = createApp(/* ... */) app.mount('#app')
也可以挂载到一个实际的 DOM 元素。
app.mount(document.body.firstChild)
2.3 app.unmount()
卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。
-
类型
interface App { unmount(): void }
2.4 app.provide()
提供一个值,可以在应用中的所有后代组件中注入使用。
-
类型
interface App { provide<T>(key: InjectionKey<T> | symbol | string, value: T): this }
-
详细信息
第一个参数应当是注入的 key,第二个参数则是提供的值。返回应用实例本身。
-
示例
import { createApp } from 'vue' const app = createApp(/* ... */) app.provide('message', 'hello')
在应用的某个组件中:
export default { inject: ['message'], created() { console.log(this.message) // 'hello' } }
2.5 app.component()
如果同时传递一个组件名字符串及其定义,则注册一个全局组件;如果只传递一个名字,则会返回用该名字注册组件(如果存在的话)。
-
类型
interface App { component(name: string): Component | undefined component(name: string, component: Component): this }
-
示例
import { createApp } from 'vue' const app = createApp({}) // 注册一个选项对象 app.component('my-component', { /* ... */ }) // 得到一个已注册的组件 const MyComponent = app.component('my-component')
可以在别的模板中直接使用注册到全局的组件
2.6 app.use()
安装一个插件。
-
类型
interface App { use(plugin: Plugin, ...options: any[]): this }
-
详细信息
第一个参数应是插件本身,可选的第二个参数是要传递给插件的选项。
插件可以是一个带
install()
方法的对象,亦或直接是一个将被用作install()
方法的函数。插件选项 (app.use()
的第二个参数) 将会传递给插件的install()
方法。若
app.use()
对同一个插件多次调用,该插件只会被安装一次。 -
示例
import { createApp } from 'vue' import MyPlugin from './plugins/MyPlugin' const app = createApp({ /* ... */ }) app.use(MyPlugin)
2.7 app.version
提供当前应用所使用的 Vue 版本号。这在插件中很有用,因为可能需要根据不同的 Vue 版本执行不同的逻辑。
-
类型
interface App { version: string }
-
示例
在一个插件中对版本作判断:
import { version } from 'vue' console.log(version)
2.8 app.config
每个应用实例都会暴露一个 config
对象,其中包含了对这个应用的配置设定。你可以在挂载应用前更改这些属性 (下面列举了每个属性的对应文档)。
import { createApp } from 'vue'
const app = createApp(/* ... */)
console.log(app.config)
2.9 app.config.errorHandler
用于为应用内抛出的未捕获错误指定一个全局处理函数。
-
类型
interface AppConfig { errorHandler?: ( err: unknown, instance: ComponentPublicInstance | null, // `info` 是一个 Vue 特定的错误信息 // 例如:错误是在哪个生命周期的钩子上抛出的 info: string ) => void }
-
详细信息
错误处理器接收三个参数:错误对象、触发该错误的组件实例和一个指出错误来源类型信息的字符串。
它可以从下面这些来源中捕获错误:
- 组件渲染器
- 事件处理器
- 生命周期钩子
setup()
函数- 侦听器
- 自定义指令钩子
- 过渡 (Transition) 钩子
-
示例
app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 }
3.0 app.config.warnHandler
用于为 Vue 的运行时警告指定一个自定义处理函数。
-
类型
interface AppConfig { warnHandler?: ( msg: string, instance: ComponentPublicInstance | null, trace: string ) => void }
-
详细信息
警告处理器将接受警告信息作为其第一个参数,来源组件实例为第二个参数,以及组件追踪字符串作为第三个参数。
这可以用户过滤筛选特定的警告信息,降低控制台输出的冗余。所有的 Vue 警告都需要在开发阶段得到解决,因此仅建议在调试期间选取部分特定警告,并且应该在调试完成之后立刻移除。
3.setup()
3.1 setup
- 新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
- 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h1>名字{{ age }}</h1>
<h1>年龄{{ name }}</h1>
<button @click="showMessage">showMessage</button>
<MessageComponent></MessageComponent>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(){
let name = 'admin';
let age = 20;
let showMessage = ()=>{
alert(`我是${name},今年${age}岁了`)
}
return {name,age,showMessage};
}
}
</script>
<style scoped>
</style>
- 可以返回一个渲染函数,渲染函数渲染的结果会覆盖掉组件中原有的元素【不常用】
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h1>名字{{ age }}</h1>
<h1>年龄{{ name }}</h1>
<button @click="showMessage">showMessage</button>
<MessageComponent></MessageComponent>
</div>
</template>
<script>
import {h} from 'vue'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(){
return ()=> h('h1','helloWorld')
}
}
</script>
<style scoped>
</style>
3.2 ref
- 作用: 定义一个数据的响应式
- 语法: const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: xxx.value
- 模板中操作数据: 不需要.value
- 一般用来定义一个基本类型的响应式数据
<template>
<div class="hello">
<h1>父组件:{{ msg }}</h1>
<h1>名字:{{ age }}</h1>
<h1>年龄:{{ name }}</h1>
<div>行业:{{job.type}} 薪资:{{job.salary}}K</div>
<button @click="updateData">updateData</button>
<MessageComponent></MessageComponent>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(props){
let name = ref('admin');
let age = ref( 20);
let job = ref({type:'java,',salary:10});
let updateData = ()=>{
name.value = 'root';
age.value = 25;
job.value.salary = ++job.value.salary;
}
let {msg} = props;
console.log()
return {name,age,msg,job,updateData};
}
}
</script>
<style scoped>
</style>
3.3 访问 Props
setup
函数的第一个参数是组件的 props
。和标准的组件一致,一个 setup
函数的 props
是响应式的,并且会在传入新的 props 时同步更新。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
请注意如果你解构了 props
对象,解构出的变量将会丢失响应性。因此我们推荐通过 props.xxx
的形式来使用其中的 props。
3.4 Setup 上下文
传入 setup
函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup
中可能会用到的值:
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
该上下文对象是非响应式的,可以安全地解构:
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
attrs
和 slots
都是有状态的对象,它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们,并始终通过 attrs.x
或 slots.x
的形式使用其中的属性。此外还需注意,和 props
不同,attrs
和 slots
的属性都不是响应式的。如果你想要基于 attrs
或 slots
的改变来执行副作用,那么你应该在 onBeforeUpdate
生命周期钩子中编写相关逻辑。
3.5 暴露公共属性
expose 函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose 函数暴露出的内容:
export default {
setup(props, { expose }) {
// 让组件实例处于 “关闭状态”
// 即不向父组件暴露任何东西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有选择地暴露局部状态
expose({ count: publicCount })
}
}
3.6 reactive 引用类型数据响应式
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
<template>
<div class="hello">
<h1>父组件:{{ msg }}</h1>
<div>行业:{{job.type}} 薪资:{{job.salary}}K</div>
<button @click="updateData">updateData</button>
<MessageComponent></MessageComponent>
</div>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup(props){
let job = reactive({type:'java,',salary:10});
let updateData = ()=>{
console.log(job)
job.salary = ++job.salary;
}
let {msg} = props;
return {msg,job,updateData};
}
}
</script>
<style scoped>
</style>
3.7 vue2的响应式
- 核心:
- 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
- 问题
- 对象直接新添加的属性或删除已有属性, 界面不会自动更新
- 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
3.8 Vue3的响应式
- 核心:
- 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
- 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
- 文档:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
3.9 setup细节
- setup执行的时机
- 在beforeCreate之前执行(一次), 此时组件对象还没有创建
- this是undefined, 不能通过this来访问data/computed/methods / props
- 其实所有的composition API相关回调函数中也都不可以
- setup的返回值
- 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合并成功组件对象的方法
- 如果有重名, setup优先
- 注意:
- 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
- setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
- setup的参数
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含props配置声明且传入了的所有属性的对象
- attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
3.10 reactive与ref-细节
- 是Vue3的 composition API中2个最重要的响应式API
- ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
- 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
- ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
- reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
- ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
3.11 readonly()
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
-
类型
function readonly<T extends object>( target: T ): DeepReadonly<UnwrapNestedRefs<T>>
-
详细信息
只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与
reactive()
相同,但解包得到的值是只读的。
4.计算属性与监视
4.1计算属性
- computed函数:
- 与computed配置功能一致
- 只有getter
- 有getter和setter
- watch函数
- 与watch配置功能一致
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
- 通过配置deep为true, 来指定深度监视
- watchEffect函数
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
<template>
<h2>App</h2>
性: <input v-model="user.firstName"/><br>
名: <input v-model="user.lastName"/><br>
只有getter的计算属性: <input v-model="fullName1"/><br>
有getter与setter的计算属性: <input v-model="fullName2"><br>
</template>
<script lang="js">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
computed,
} from 'vue'
export default {
setup () {
const user = reactive({
firstName: 'admin',
lastName: 'root'
})
// 只有getter的计算属性
const fullName1 = computed(() => {
return user.firstName + '-' + user.lastName
})
// 有getter与setter的计算属性
const fullName2 = computed({
get () {
return user.firstName + '-' + user.lastName
},
set (value) {
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
return {
user,
fullName1,
fullName2
}
}
}
</script>
4.2监视
vue2写法:
<template>
<h2>当前sum:{{sum}}</h2>
<button @click="sum++">按钮</button>
</template>
<script lang="js">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
computed,
watch,
ref
} from 'vue'
export default {
name: "HelloWorld",
// vue2写法
watch: {
// 简单版本
sum(newValue, oldValue) {
console.log('handler-sum', {newValue, oldValue})
}
/* // 复杂版本形式 具有配置化
sum: {
// 采用一开始就监视
immediate: true,
// 开启深度监视
deep: true,
handler(newValue, oldValue) {
console.log('handler-sum', {newValue, oldValue})
}
}*/
},
setup() {
let sum = ref(0)
return {
sum,
}
}
}
</script>
vue3写法:
监视 ref 数据
<template>
<h2>当前sum:{{sum}}</h2>
<button @click="sum++">按钮</button>
</template>
<script lang="js">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
computed,
watch,
ref
} from 'vue'
export default {
name: "HelloWorld",
setup() {
let sum = ref(0);
let msg = ref("hello");
// 监视:ref所有定义的一个响应式数据
// watch(sum,(newValue,oldValue)=>{
// console.log("watch:",{newValue,oldValue})
// })
// 监视:ref所有定义的多个响应式数据
// 如果监视的普通数据类型不需要.vlaue【ref】,如果监听的是对象类型需要.value【reactive】
watch([sum,msg],(newValue,oldValue)=>{
console.log("watch:[sum,msg]",{newValue:newValue.toString(),oldValue:oldValue.toString()})
// 配置一开始就监视和深度监视
},{immediate:true,deep:true})
return {
sum,
msg
}
}
}
</script>
监视 reactive 数据
<template>
<h2>当前sum:{{person}}</h2>
<button @click="person.name = (Date.now())">修改姓名</button>
<button @click="person.age = (Date.now())">修改年龄</button>
<button @click="person.job.salary++">修改薪资</button>
</template>
<script lang="js">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
computed,
watch,
ref
} from 'vue'
export default {
name: "HelloWorld",
setup() {
let person = reactive({name:'admin',age:20,job:{type:'java',salary:15000}});
// 监视:watch reactive响应式数据
// 1.无法正确获取oldValue
// 2.强制开启了深度监视
// watch(person,(newValue,oldValue)=>{
// console.log("watch:[person]",{newValue:newValue,oldValue:oldValue})
// // 配置一开始就监视和深度监视
// },{immediate:true,deep:true})
// return {
// person
// }
// 监视:watch reactive响应式数据的某个数据
// 1.oldValue和new能正确获取到
// watch(()=>person.age,(newValue,oldValue)=>{
// console.log("watch:[age]",{newValue:newValue,oldValue:oldValue})
// // 配置一开始就监视和深度监视
// },{immediate:true,deep:true})
// return {
// person
// }
// 监视:watch reactive响应式数据的某些数据
// watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
// console.log("watch:[age,name]",{newValue:newValue,oldValue:oldValue})
// // 配置一开始就监视和深度监视
// },{immediate:true,deep:true})
// return {
// person
// }
// 监视:watch reactive响应式数据的深度监视
// 定义的deep有效
// 无法正确获取oldValue
watch([()=>person.job],(newValue,oldValue)=>{
console.log("watch:[job]",JSON.stringify({newValue:newValue,oldValue:oldValue}))
// 配置一开始就监视和深度监视
},{immediate:true,deep:true})
return {
person
}
}
}
</script>
4.3 watchEffect函数
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
<template>
<h2>当前sum:{{person}}</h2>
<button @click="person.name = (Date.now())">修改姓名</button>
<button @click="person.age = (Date.now())">修改年龄</button>
<button @click="person.job.salary++">修改薪资</button>
</template>
<script lang="js">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
computed,
watchEffect,
ref
} from 'vue'
export default {
name: "HelloWorld",
setup() {
let person = reactive({name:'admin',age:20,job:{type:'java',salary:15000}});
// 一开始的时候会被执行一次 然后里面监听的变量值改变的时候,该函数还会被再次执行
// 跟react的useEffect相似
watchEffect(()=>{
const name = person.name;
console.log("watchEffect回调执行");
})
return {
person
}
}
}
</script>
5.vue3生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
- onRenderTracked
- onRenderTriggered
MainApp.vue
<template>
<h2>ChildComponent</h2>
<button @click="isShow=!isShow">切换</button>
<hr>
<Message v-if="isShow"/>
</template>
<script >
import Message from './ChildComponent'
export default {
data () {
return {
isShow: true
}
},
components: {
Message
}
}
</script>
ChildComponent.vue
<template>
<div class="about">
<h2>msg: {{msg}}</h2>
<hr>
<button @click="update">更新</button>
</div>
</template>
<script >
import {
ref,
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from "vue"
export default {
beforeCreate () {
console.log('beforeCreate()')
},
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
beforeUnmount () {
console.log('beforeUnmount')
},
unmounted () {
console.log('unmounted')
},
setup() {
const msg = ref('abc')
const update = () => {
msg.value += '--'
}
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
return {
msg,
update
}
}
}
</script>
配置项生命周期钩子函数和组合式api生命钩子函数,尽量不要重复使用,组合式api生命钩子函数会优先于配置项生命周期钩子函数
6.新组件
6.1 Fragment(片断)
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
6.2 Teleport(瞬移)
- Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
ModalButton.vue
<template>
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'modal-button',
setup () {
const modalOpen = ref(false)
return {
modalOpen
}
}
}
</script>
<style>
.modal {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(0,0,0,.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
App.vue
<template>
<h2>App</h2>
<modal-button></modal-button>
</template>
<script lang="ts">
import ModalButton from './ModalButton.vue'
export default {
setup() {
return {
}
},
components: {
ModalButton
}
}
</script>
6.3 Suspense(不确定的)
-
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
-
App.vue
<template>
<Suspense>
<template v-slot:default>
<AsyncComp/>
<!-- <AsyncAddress/> -->
</template>
<template v-slot:fallback>
<h1>LOADING...</h1>
</template>
</Suspense>
</template>
<script lang="ts">
/*
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from './AsyncAddress.vue'
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
setup() {
return {
}
},
components: {
AsyncComp,
AsyncAddress
}
}
</script>
- AsyncComp.vue
<template>
<h2>AsyncComp22</h2>
<p>{{msg}}</p>
</template>
<script lang="ts">
export default {
name: 'AsyncComp',
setup () {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve({
// msg: 'abc'
// })
// }, 2000)
// })
return {
msg: 'abc'
}
}
}
</script>
- AsyncAddress.vue
<template>
<h2>{{data}}</h2>
</template>
<script lang="ts">
import axios from 'axios'
export default {
async setup() {
const result = await axios.get('/data/address.json')
return {
data: result.data
}
}
}
</script>
6.4 Transition
<Transition>
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
<component>
切换的动态组件
以下是最基本用法的示例:
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
6.5 KeepAlive
<KeepAlive>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
在组件基础章节中,我们已经介绍了通过特殊的 <component>
元素来实现动态组件的用法:
<component :is="activeComponent" />
默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态 —— 当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。
6.5.1 最大缓存实例数
我们可以通过传入 max
prop 来限制可被缓存的最大组件实例数。<KeepAlive>
的行为在指定了 max
后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
6.5.2 包含/排除
<KeepAlive>
默认会缓存内部的所有组件实例,但我们可以通过 include
和 exclude
prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组:
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
它会根据组件的 name
选项进行匹配,所以组件如果想要条件性地被 KeepAlive
缓存,就必须显式声明一个 name
选项。
6.5.3 缓存实例的生命周期
当一个组件实例从 DOM 上移除但因为被 <KeepAlive>
缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新被激活。
一个持续存在的组件可以通过 activated
和 deactivated
选项来注册相应的两个状态的生命周期钩子:
export default {
activated() {
// 在首次挂载、
// 以及每次从缓存中被重新插入的时候调用
},
deactivated() {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
}
}
请注意:
activated
在组件挂载时也会调用,并且deactivated
在组件卸载时也会调用。- 这两个钩子不仅适用于
<KeepAlive>
缓存的根组件,也适用于缓存树中的后代组件。