Vue3.0学习笔记
Vue3.0 新概念特性理解
一.初始化项目
1.安装vue-cli3
npm intsall -g @vue/cli
#OR
yarn global add @vue/cli
2.创建项目
vue create my-project
3.在项目中安装 composition-api体验vue3新特性
npm install @vue/composition-api -- save
#OR
yarn add @vue/composition-api
4.在使用任何@vue/compotion-api提供的能力前,必须先通过Vue.use()进行安装
import VueCompositionApi from '@vue/composition-api'
Vue.config.productionTip = false
Vue.use(VueCompositionApi)
-
若想整个项目完全转换为Vue3.x,则在用Vue Cli 3.x安装后使用命令
vue add vue-next进行转换
vue add vue-next
二.Vue3.0新特性
1.setup
setup()函数是vue3中,专门为组件提供的新属性,它为我们使用vue3的Composition API 新特性提供了统一的入口
1.1 执行时机
setup 函数是个新的入口函数,相当于 vue2.x 中 beforeCreate 和 created,在 beforeCreate 之后 created 之前执行
1.2 接受props数据
1.在props中定义当前组件允许外界传递过来的参数
props:{
name:String
},
2.通过setup函数的第一个参数进行接收
setup(props){
console.log(props.name);
}
3.注意:不要破坏props的结构,否则使用watch监听时将失效
运行时还可能报错!!!
export default {
props: {
name: String
},
setup(props) {
watchEffect(() => {
console.log(`name is: ` + props.name)
})
}
}
export default {
props: {
name: String
},
setup({ name }) {
watchEffect(() => {
console.log(`name is: ` + name) // Will not be reactive!
})
}
}
1.3 context
- setup函数的第二个参数是上下文对象,包含一些有用的属性,从原来2.x中this选择性地暴露了一些属性。this 在 setup() 中不可用:
setup(props, context) {
context.attrs
context.slots
context.emit
},
- attrs和slots都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值。所以可以解构,无需担心后面访问到过期的值:
setup(props, { attrs }) {
// 一个可能之后回调用的签名
function onClick() {
console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性
}
},
- 组件使用props的场景更多,有时候甚至只使用props
1.4 this的用法
- this在
setup()中不可用。由于setup()在解析2.x选项前被调用,setup()中的this将与2.x选项中的this完全不同。同时在setup()和2.x选项中使用this时将造成混乱。在setup()中避免这种情况的另一个原因是:这对于初学者而言,取代这两种情况的this是非常常见的错误:
setup() {
function onClick() {
this // 这里 `this` 与你期望的不一样
}
}
-
类型定义
interface Data {
[key: string]: unknown
}
interface SetupContext {
attrs: Data
slots: Slots
emit: (event: string, ...args: unknown[]) => void
}
function setup(props: Data, context: SetupContext): Data
- 为了获得传递给
setup()参数的类型预设,需要使用defineComponent。
响应式系统API
2.reactive
reactive()函数接收一个普通对象然后返回该普通对象的响应式代理。等同于2.x的 Vue.observable()
###2.1 基本语法
- 使用 return 将响应式数据对象返回出去 才能在模板中使用
- 基于ES2015的Proxy实现,返回的代理对象不等于原始对象
import {reactive} from '@vue/composition-api'
setup(){
//reactive 创建响应式数据
const state = reactive({count:0})
//返回出去直接使用
return state
}
<template>
<div>
<h1>{{count}}</h1>
<button @click="count++">增加</button>
</div>
</template>
3.ref
- 接受一个参数值并返回一个响应式且可更改的ref对象。ref对象拥有一个指向内部值的单一属性.value。 ###3.1基本语法
import {ref} from '@vue/composition-api'
setup(){
const refCount = ref(0)
}
注意:新的ref会替代旧的ref
3.2 .value
.value才是真正的值,只能在setup函数中访问
setup(){
const refCount = ref(0)
console.log(refCount.value)
}
3.3 在template中使用
请注意,setup在模板中访问时,从ref返回的引用会自动解包,因此不需要.value模板。
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
return {
count: ref(0),
}
},
}
</script>
3.4 ref 和 reactive 可以结合使用
setup(){
//ref和reactive结合使用
const refCount = ref(0)
const state = reactive({
refCount
})
console.log(state.refCount)
return state
}
- 作为响应式对象的属性访问 当ref作为reactobject的属性被访问或修改时,也将自动解套value值,其行为类似普通属性:
const count = ref(0)
const state = reactive({
count,
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
- 注意如果将一个新的ref分配给现有的ref,将替换旧的ref:
const newCount = ref(2)
state.count = newCount
console.log(state.count) // 2
console.log(count.value) // 1
- 注意当嵌套在反应性Object。中时,参考解套才会从Array或者Map等原生集合类中访问REF时,不会自动解套:
const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)
const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
-
类型定义
interface Ref<T> {
value: T
}
function ref<T>(value: T): Ref<T>
有时我们可能需要为ref做一个多个复杂的类型标注。我们可以通过在调用ref时传递泛型参数来覆盖替换推导:
const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number>
foo.value = 123 // 能够通过!
4.isRef
isRef() 函数是用来判断某个值是否为 ref() 创建出来的对象;应用场景:当需要展开某个为ref()创建出来的值的时候,例如:
import {isRef} from '@vue/composition-api'
const wrapped = isRef(foo)?foo.value:foo
5.toRefs
toRefs()函数可以将reactive() 创建出来的响应式对象,转换为普通的对象,只不过这个对象的每个属性节点,都是ref()类型的响应式数据,例如:
setup(){
//多个属性值
const state = reactive({count:0,name:'zs'})
return {
//state error 报错 这样传出去的对象不是响应式的对象
//将state的固定对象转换为响应式对象
//若直接返回...state 此时不是响应式对象
...toRefs(state)
}
}
6.定义方法
在vue3.x中定义方法也是在setup函数中,例如我们定义一个增加1的函数
<template>
<div>
<h1>{{count}}</h1>
<button @click="add">+1</button>
</div>
</template>
setup(){
const state = reactive({count:0,name:'zs'})
//自定义方法 类似2.0method
let add = () => {
state.count ++;
}
return {
...toRefs(state),
add
}
}
7.Computed 计算属性的使用
7.1创建只读的计算属性
在调用computed()函数期间,传入一个function函数,可以得到一个只读的计算属性,computedCount的值依赖于count的值
setup(){
const count = ref(0)
//计算属性永远比count大1 只读
const computedCount = computed(() => count.value +1)
computedCount.value ++ //错误
return {
count,
computedCount
}
}
<template>
<div>
<h1>count的值:{{count}}</h1>
<h1>计算属性的值:{{computedCount}}</h1>
</div>
</template>
7.2创建可读写的计算属性
在调用computed()函数期间,传入一个包含get() 和 set()的对象,可以得到一个可读写的计算属性,即v-model绑定的计算属性和相关的属性会同时响应
//可读写的计算属性
const computedCount = computed({
get:() => count.value +1,
set:val => {
count.value = val
}
})
//初始化
let initCount = (val) => {
//调用set
computedCount.value = val;
}
<template>
<div>
<h1>count的值:{{count}}</h1>
<h1>计算属性的值:{{computedCount}}</h1>
<button @click="add">+1</button>
<button @click="initCount(0)">初始化</button>
</div>
</template>
-
类型定义
// 只读的
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>
// 可更改的
function computed<T>(options: {
get: () => T
set: (value: T) => void
}): Ref<T>
-
readOnly(目前beta版本暂不支持)
- 指向一个对象(响应式或普通)或ref,返回一个原始对象的替换代理。一个替换的代理是“深层的”,对象内部任何替换的属性也都是替换的。
- 使用 readonly 函数,可以把 普通 object 对象、reactive 对象、ref 对象 返回一个只读对象。
- 返回的 readonly 对象,一旦修改就会在 console 有 warning 警告。程序还是会照常运行,不会报错。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
8.Watch 监听
- watch函数用来监听某些数据的变化,触发特定的操作,使用前需导入。watch函数第一个参数是监听的属性,第二个参数是函数,函数参数包括新的值和旧的值。
- watchAPI完全等效于2.x this.$watch(以及watch中相应的选项)。watch需要侦听特定的数据源,并在其中函数中执行替代。有时情况是懒执行的,只是仅在侦听的源变化时才执行。 ###8.1监听 reactive类型的数据
import {watch,reactive} from '@vue/composition-api'
setup(){
const state = reactive({count:0})
watch(
() => state.count,
(count,preCount) => {
}
)
}
8.2 监听 ref类型的数据
import {watch,ref} from '@vue/composition-api'
setup(){
const count = ref(0)
watch(count,(newCount,preCount)=> {
console.log(newCount);
console.log(preCount);
},{lazy:true})
}
注意:lazy是开启 第一次组件创建时不监听
8.3 watch 监听多个数据
reative 类型写法
const state = reactive({count:0,name:'zs'})
watch([() => state.count,() => state.name],([count,name],[precount,prename])
=> {
console.log(count);
console.log(name);
console.log(precount);
console.log(prename);
},{lazy:true})
ref类型写法简便,不建议数据对象使用reactive去监听,因为写法比较冗杂
//监听多个值 写成数组格式
const count = ref(0)
const name = ref('mike')
watch([count,name],([count,name],[precount,prename]) => {
console.log(count);
console.log(name);
console.log(precount);
console.log(prename);
},{lazy:true})
8.4 清除监视
在setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止,如果想要明确的停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:
//创建监视,得到停止函数
const stop = watch(() => {
})
//调用停止函数,清除对应监视
stop()
8.5 在watch中清除无效的异步任务
有时,当被 watch 监听的值发生变化时,或watch自身被stop之后,我们希望能清除无效的异步任务,此时watch回调函数提供了一个 cleanUp函数来执行清除的工作,这个清除函数在此情况下被调用:
- watch被重复执行
- watch被强制stop
<template>
<div>
<input type="text" v-model="kw">
</div>
</template>
import {ref, watch} from '@vue/composition-api'
export default {
name: "WatchCleanUp",
setup() {
//初始化 kw
const kw = ref('')
//异步任务 打印输入关键词
const asyncPrint = val => {
// 延迟1秒打印
return setTimeout(() => {
console.log(val)
}, 1000)
}
//监视kw 同时定义onCleanUp
watch(kw, (newVal, oldVal, onCleanUp) => {
//执行异步任务,同时得到timeId
const timerId = asyncPrint(newVal)
//清除多余的异步任务
onCleanUp(() => clearTimeout(timerId))
})
return {
kw
}
}
}
8.6 watchEffect
- watchEffect函数 : 如果响应性的属性有变更,就会触发这个函数,必须属性值有依赖,自动检测
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
##9.生命周期Hook
-
Vue3.x的生命周期函数,可以按需导入到组件中,这些生命周期钩子注册函数只能在
setup()期间同步使用,因为它们依赖于内部的变量状态来定位内部组件实例(正在调用setup()的组件实例),而在当前组件下调用这些函数会引发一个错误。 -
组件实例本身也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除
import {onMounted,onUpdated,onUnmounted} from '@vue/composition-api'
export default {
name: "LifeCycle",
setup(){
onMounted(() => {
console.log('onMounted');
})
onUpdated(() => {
console.log('onUpdated');
})
onUnmounted(() => {
console.log('onUnmounted');
})
}
}
下面列表中,是 vue2.x 的声明周期函数与新版Compostion API之间的映射关系:
beforeCreate-> use setup()created-> use setup()- beforeMount -> onbeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
10.provide & inject 依赖注入
provide()和inject()可以实现嵌套组件之间的数据传递,这两个函数只能在setup()函数中使用。父级组件中使用provide()函数向下传递数据;子级组件中使用inject()获取上级传递过来的数据。inject如果未提供预设值,并且在提供大部分中未找到该属性,则inject返回undefined。 ##11.共享普通数据 注意:父组件可以直接通过provide向子组件的子组件传递数据,也就是孙子组件,而子组件不能通过provide传递数据给父组件。
APP根组件
<template>
<div id="app">
<h1>我是父组件</h1>
<son></son>
</div>
</template>
import {provide} from '@vue/composition-api'
export default {
components: {Son},
setup(){
provide('themeColor','red')
}
}
子组件
<template>
<div>
<h3>
我是子组件
</h3>
<grandson></grandson>
</div>
</template>
import Grandson from "./GrandSon";
export default {
name: "Son",
components: {Grandson},
}
孙子组件 通过inject获取数据
<template>
<div>
<h5 :style="{color:color}">
孙子组件
</h5>
</div>
</template>
import {inject} from '@vue/composition-api'
export default {
name: "Grandson",
setup(){
const color = inject('themeColor')
return {
color
}
}
}
11.1同时,也可以共享ref类型的动态数据。
- 注入的响应性
- 可以使用ref来保证provided和injected之间值的响应
- 如果注入一个响应式对象,则它的状态变化也可以被侦听。 要实现按钮点击改变孙子组件的颜色,可以通过provide 和 inject的方式。
<template>
<div id="app">
<h1>我是父组件</h1>
<button @click="color='red'">红色</button>
<button @click="color='yellow'">黄色</button>
<button @click="color='blue'">蓝色</button>
<son></son>
</div>
</template>
import {provide,ref} from '@vue/composition-api';
setup(){
const color = ref('red')
provide('themeColor',color)
return{
color
}
}
孙子组件
<template>
<div>
<h5 :style="{color:color}">
孙子组件
</h5>
</div>
</template>
import {inject} from '@vue/composition-api'
export default {
name: "Grandson",
setup(){
const color = inject('themeColor')
return {
color
}
}
}
12.template refs
- 通过 ref() 可以引用页面上的元素dom 或组件
- ref 被用在模板中时和其他 ref 一样:都是响应式的,并可以传递进组合函数(或从其中返回)。
###12.1元素的引用 代码如下:
<template>
<div>
<h3 ref="h3Ref">我是ref Dom</h3>
</div>
</template>
import {ref,onMounted} from "@vue/composition-api"
export default {
name: "refDom",
setup(){
//创建一个DOM引用
const h3Ref = ref(null)
//在DOM加载完毕后 拿到dom修改属性
onMounted(() => {
//h3Ref.value为原生的DOM对象
console.log(h3Ref.value);
h3Ref.value.style.color = 'blue'
})
return {
h3Ref
}
}
}
12.2 组件的引用
子组件 ref-cpn 定义一个count属性,同样也可以引用其子组件的方法,子组件必须return才可以在ref调用得到。
import {ref} from "@vue/composition-api"
export default {
name: "refCpn",
setup(){
const count = ref(0)
const showMes = () => {
console.log('我是refcpn组件');
}
return {
count,
showMes
}
}
}
App父组件
<template>
<div id="app">
//为ref-cpn组件添加ref属性
<ref-cpn ref="refCpn"></ref-cpn>
</div>
</template>
setup(){
//创建一个ref引用
const refCpn = ref(null);
onMounted(() => {
//通过ref引用拿到refCpn的count值
console.log(refCpn.value.count);
refCpn.value.showMes()
})
return {
refCpn,
}
}
13.nextTick
在vue2.x中,我们需要使用nextTick时需要调用this.$nextTick()去进行处理,但在vue3.x已经没有this关键字,所以我们需要从composition API中获取。 ##13.1 小案例
- 实现一个点击按钮展示输入框,同时自动聚焦到输入框中。
<template>
<div>
<button v-if="!isShowInput" @click="showIpt">展示输入框</button>
<input type="text" v-else ref="ipt">
</div>
</template>
import {ref,nextTick} from '@vue/composition-api'
export default {
name: "nextTick",
setup(props,ctx){
//创建ref引用
const isShowInput = ref(false)
const ipt =ref(null)
const showIpt = () => {
isShowInput.value = true
//使用引入的nextTick获取所需的DOM,执行进一步操作
nextTick(() => {
console.log(ipt.value)
ipt.value.focus()
})
}
return{
isShowInput,
showIpt,
ipt,
}
}
}
#响应式工具集
- ##unref
如果参数是一个 ref 则返回它的 value,否则返回参数本身。它是
val = isRef(val) ? val.value : val的语法糖。
import {Ref,unref,ref} from '@vue/composition-api'
function useFoo(x: string | Ref<string>) {
// unref是‘val = isRef(val) ? val.value : val’ 的语法糖。
const unwrapped = unref(x) // unwrapped 一定是 number 类型
return unwrapped
}
export default {
name: "Unref",
setup(){
const val =useFoo('mike');
console.log(val);
// const str = ref('mike');
// const other = unref(str);
// console.log(other);
}
}
-
toRef
toRef() 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。
const state = reactive({
foo: 1,
bar: 2,
})
//toRef的第二个参数必须是state中有声明的属性字段
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
console.log(state.bar) // 2
state.foo++
console.log(fooRef.value) // 3
当您要将一个prop 中的属性作为 ref 传给组合逻辑函数时,toRef 就派上了用场,对于单个数据绑定时,使用ref即可实现同步响应,但如果使用reactive绑定多个数据传递时,子组件需对prop进行toRef操作,当然也可以用toRefs对reactive的数据进行解构:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
},
}
-
toRefs 和 isRef 前面有所提及
-
isProxy
检查一个对象是否是由 reactive 或者 readonly 方法创建的代理。
-
isReactive
- 检查一个对象是否是由 reactive 创建的响应式代理。
- 如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹了一层,那么同样也会返回 true。
-
isReadonly
检查一个对象是否是由readonly创建的只读代理。
- ##Vuex的使用
可以直接使用useStore获取store实例,如果有多个store,可以用key来区分,Router和Route可以使用 useRoute和useRouter #三.Vue3.0目录解析(Updateing) 这里主要关注shims-tsx.d.ts和 shims-vue.d.ts两个文件
- shims-tsx.d.ts,允许你以.tsx结尾的文件,在Vue项目中编写jsx代码
- shims-vue.d.ts 主要用于 TypeScript 识别.vue 文件,Ts默认并不支持导入 vue 文件,这个文件告诉ts 导入.vue 文件都按VueConstructor处理。
在 shims-tsx.d.ts中
-
declare:当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。 declare var 声明全局变量 declare function 声明全局方法 declare class 声明全局类 declare enum 声明全局枚举类型 declare global 扩展全局变量 declare module 扩展模块
-
namespace:“内部模块”现在称做“命名空间”
-
module X { 相当于现在推荐的写法 namespace X {) ###综上所述
-
shims-tsx.d.ts, 在全局变量 global中批量命名了数个内部模块。
-
shims-vue.d.ts,意思是告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理
四.Vue3.x 新特性 和 Vue2.x的语法区别
-
template 和 style几乎无差别,主要是script的差别
差异分析
1. data
- ###Vue 2.x:
data() {return {}}实现响应式,主要定义在data中 或 使用Vue.set实现响应式。 - ###Vue3.x
在
setup()中使用从Composition API导入的reactive()或ref()进行处理,然后return出去。(多个响应式数据使用...toRefs()进行包裹return出去)
2.methods
- ###Vue 2.x: 在methods中声明方法
- ###Vue3.x
写在
setup()函数中,并通过return返回出去使用 ##3.计算属性Computed
- Vue2.x:
在computed:{}中声明计算属性函数
- Vue3.x:
- 方式一:写在
setup()函数中
const getDoubleNum = computed(() => state.num * 2)
- 方式二:写在state中
const state = reactive({
countComputed: computed(() => count.value + 1)
});