Vue3.0新特性学习

168 阅读9分钟

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操作,当然也可以用toRefsreactive的数据进行解构:

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)
});