Vue3入门(基础篇)

Vue3 新增方法:

1. 创建Vue实例(main.js)

Vue2中,我们使用 Vue 这个构造函数,创建 Vue实例,而在 Vue3中,新增了 createApp()方法,用于创建应用实例。语法:

// 引入的不再是Vue构造函数,而是一个名为 createApp 的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例,并挂在到根元素
createApp(App).mount('#app')

2. 组件中的变化(template)

在Vue2中,template 标签需要有一个根标签,而这样做会产生一些不必要的嵌套。在Vue3中,template 标签中可以不使用根标签,因为Vue3中,会默认用<fragment></fragment>标签将多个标签进行包裹,fragment 标签表示文档碎片,是一个虚拟Dom,不会渲染为真实的 Dom 元素。

3. 组合式 API 与 setup() 函数

何为组合式API

在Vue2 中,要写一个功能,我们通常会:

1)在 data 中,声明数据;

2)在 methods 中定义方法;

3)以及在 watch 和 computed 中进行监听数据

以及。。。

这样做会使我们的代码看起来,东一块,西一块,看起来非常不优雅。

而在 Vue3中,我们可以使用组合式API,将需要的API按需引入,并在setup() 函数中进行使用,我们可以像正常写 js 那样,进行书写,同一个功能的代码都会放在一起,逻辑非常清晰,也不需要频繁的使用 this.xxx 。

setup() 函数:

1). setup是 Vue3 新增的一个钩子函数,他会在 beforeCreate 之前调用。

2). setup 默认接收两个参数:

  • props: 表示父组件传递的参数对象

  • context 表示组件上下文对象,主要有这几个属性:

attrs 组件外部传递过来的值,但没有在props中声明,相当于 Vue2 的 this.$attrs;

emit 父组件传递的函数,相当于 this.$emit;

slots 父组件传递的插槽,相当于 this.$slots。

// 父组件
<template>
  <h1>父组件</h1>
  <h2 v-show="visible">这是父组件身上的</h2>
  <Demo :msg="person.msg" @hello="change" :name="person.name" v-model:visible="visible">
    <template v-slot:test>
      <div>这是插槽</div>
    </template>
  </Demo>
</template>

<script>
import {ref,reactive} from 'vue'
import Demo from './components/Demo'
export default {
  name: 'App',
  components:{
    Demo
  },
  setup(){
    const visible = ref(true)
    const person = reactive({
      name: '张三',
      msg: '雷猴啊'
    })

    function change(){
      alert('你好,我是父组件身上的函数')
    }

    return {
      visible,
      person,
      change
    }
  }
}
</script>
// 子组件
<template>
  <h2>姓名:{{ name }}</h2>
  <button @click="test">点击触发父组件传递过来的Hello事件</button>
  <button @click="updateVisible">点击修改父组件身上的visible</button>
  <slot name="test"></slot>
</template>

<script>
export default {
  props: {
    name: String,
    msg: String,
    visible: Boolean
  },
  emits: {
    hello: Function
  },
  setup(props, context) {
    console.log(props);
    console.log(context);
    console.log(context.slots);
    function test(){
      context.emit('hello')
    }
    function updateVisible(){
      context.emit('update:visible', !props.visible)
    }
    return {
      test,
      updateVisible
    }
  }
}
</script>

4. 响应式数据

1). ref 引用对象

引入:import { ref } form 'vue'

声明:构造一个 ref 引用对象(返回一个 refImpl 对象):

let name = ref('张三')  // (基本类型)   
let job = ref({type: '前端', salary: '20k'}) // (引用类型)
// 修改 引用对象 值 需要 .value
 name.value = '李四'  //(基本类型) 
job.value.type = 'UI'  // (引用类型)

ref 构造基本类型值,底层使用的是 defineProperty 实现响应式 ;

构造引用类型值,借用了 reactive函数, 底层使用 proxy 实现响应式。

2). reactive

引入:import { reactive } form 'vue'

声明 一个 reactive 代理对象(proxy 实例):

const person = reactive({
    name: '张三',
    age: 18,
    job: {
        jobType: '前端攻城狮',
        salary: '30k',
        a: {
            b: {
                c: 666
            }
        }
    },
    hobby: ['吃','喝','嫖','赌']
 })
// 修改 reactive 代理对象的值
    person.name = '法外狂徒'
    person.age = 20
    person.job.jobType = '全干攻城狮'
    person.job.salary = '50k'
    person.job.a.b.c = 777
    person.hobby[2] = '学习' // reactive 可以实现对数组元素的响应式

总结:

如果源数据是基本类型,则使用 ref 进行声明,如果是引用类型,使用 reactive 进行声明;

ref 构造基本类型值,底层使用的是 defineProperty 实现响应式 ;构造引用类型值,借用了 reactive函数, 底层使用 proxy 实现响应式。

5. 计算属性- computed

引入:import {reactive, computed} from 'vue'

使用:

// 简写方式(传入一个方法,计算属性只读时使用):
  person.fullName = computed(() => {
    return person.firstName + '-' + person.lastName
  })
// 完整写法(传入一个对象,计算属性需要修改时使用):
  person.fullName = computed({
    get(){
        return person.firstName + '-' + person.lastName
    },
    set(value){
        let nameArr = value.split('-')
        person.firstName = nameArr[0]
        person.lastName = nameArr[1]
    }
  })

6. 监听属性 - watch

引入:import {ref, watch} from 'vue'

用法:

watch是一个函数,里面有三个参数:(监听的属性名, 回调函数(newValue, oldValue), 配置项)。

// 情况一: 监听一个 ref 响应式数据 immediate: true 表示数据加载就监听一次
watch(sum, (newValue, oldValue) => {
   console.log('sum的值变化了', newValue, oldValue);
}, { immediate: true })
// 情况二: 监听多个 ref 响应式数据(需要传入一个数组,newValue和oldValue 也是数组)
watch([sum, msg], (newValue, oldValue) => {
  console.log('sum/msg变化了', newValue, oldValue);
}, { immediate: true })
// 情况三:监听一个 reactive 响应式数据
/* 
  注意:
    1. 此方式中,无法获取正确的 oldValue,newValue 和 oldValue 的值相同
    2. 强制开启了深度监视 (deep 配置无效)
*/
watch(person, (newValue, oldValue) => {
  console.log('person变化了', newValue, oldValue);
})

// 情况四: 监听reactive 的某一个属性
watch(() => person.age, (newValue, oldValue) => {
  console.log('person的age变化了', newValue, oldValue);
})

// 情况五: 监听reactive 的某些属性
watch([() => person.age, ()=>person.name], (newValue, oldValue) => {
  console.log('person的age/name变化了', newValue, oldValue);
})
// 特殊情况: 监听reactive 的一个对象
watch(() => person.job, (newValue, oldValue) => {
  console.log('person的job变化了', newValue, oldValue);
}, {deep: true})

总结:

当监听 ref 定义的数据时,且为基本类型,可以正确监听;

当监听 reactive 定义的数据时:

- 监听整个 reactive 定义的数据

默认开启深度监听(deep 不生效)

无法获取正确的 oldValue

- 监听 reactive 中的某个属性

- 该属性为 基本类型:可以正确监听

- 该属性为 引用类型:oldValue无法正确获取,deep 生效

注意:

如果监听 ref 定义的 引用类型 的响应式数据,需要在第一个参数后加 .value或者 添加选项 deep:true,因为 ref 定义 引用类型时,默认会调用reactive, 而 ref 返回的是一个 RefImpl 对象, .value 是一个 Proxy 对象。

7. watchEffect 函数

引入: import {ref, watchEffect, reactive} from 'vue'

使用:

// watchEffect 返回值是一个函数,作用是停止监听
const stop = watchEffect((before) => {
  // before是一个函数,它会在执行副作用函数之前调用
  // 先输出before 后输出 watchEffect配置的回调执行了
  const a = sum.value
  const b = person.job.job1.salary
  console.log('watchEffect配置的回调执行了',a,b);
  before(() => {
    console.log('before')
  })
})
// 停止监听
const stopWatch = () => stop()

总结:

watchEffect 不需要指明监听哪个属性,回调函数中引入了哪个属性,就监听哪个;

watchEffect 接收一个参数,这个参数是一个函数,会在副作用函数之前调用;

watchEffect 默认开启了 immediate;

watchEffect 返回一个函数,可以调用这个函数来停止监听;

watchEffect 与 computed 区别:

- computed 需要写返回值,watchEffect 不需要写返回值

8. 生命周期

Vue3 与 Vue2 的生命周期类似:

组合式API

import { setup, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

使用:

onBeforeMount(() => {}) 传入一个函数

选项式API

beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted

使用:created(){}

总结:

Vue2 中的 Destroy 变成了 unmount

9. hooks

在了解了组合式API之后,我们可以将组件中,共有的逻辑,利用 组合式 API 分离出来,自定义为hooks(其实就是一个js方法),在需要的地方引入,此方式类似于 Vue2 中的 mixin。

示例:

// 组件内部
<template>
  <h2>当前求和为:{{ sum }}</h2>
  <button @click="sum++">点我加一</button>
  <hr>
  <h2>当前点击的鼠标坐标为:{x:{{ point.x }},y:{{ point.y }}}</h2>
</template>

<script>
import {ref} from 'vue'
// 引入自定义 hooks
import usePoint from '../hooks/usePoint'
export default {
  setup() {
    const sum = ref(0)
    
    let point = usePoint()

    return {
      sum,
      point
    }
  },
}
</script>
// 自定义 hooks usePoint.js
import {reactive, onMounted, onBeforeUnmount} from 'vue'
export default function (){
    // 数据
    const point = reactive({
        x:0,
        y:0
      })

    // 方法
    function savePoint(event){
    console.log({x:event.pageX,y:event.pageY});
        point.x = event.pageX
        point.y = event.pageY
    }

    // 钩子
    onMounted(()=>{
    window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(()=>{
    window.removeEventListener('click', savePoint)
    })
    return point
}

上方代码,定义了一个点击页面,输出鼠标坐标的 Hooks。

10. toRef 与 toRefs

引入: import {ref, toRef, toRefs} from 'vue'

toRef :

作用:创建一个 ref 对象,其 value值指向另一个对象中的某个属性(将一个对象的属性转为ref对象)

语法:

let name2 = toRef(person, 'name')

toRefs :

作用:与 toRef 作用类似,但是可以将一个对象的所有属性都变成 ref 对象。

语法:

const x = toRefs(person)

应用:将响应式对象中的某个属性,单独提供给外部使用。

11. shallowReactive 与 shallowRef

引入: import {ref, shallowReactive, shallowRef} from 'vue'

shallowReactive:

只对 对象最外层 属性做响应式处理

shallowRef

只处理基本数据类型的响应式,不进行对象的响应式处理

语法:

// 对象只有第一层属性是响应式
let person = shallowReactive({a:1,b:2,c:{d:3})

应用:

如果一个对象只是最外层的属性发生变化 ===> shallowReactive

如果一个对象不会修改其中的属性,而是生成新的对象来替换 ===> shallowRef

12. readonly 与 shallowReadonly

引入: import {ref, readonly, shallowReadonly} from 'vue'

readonly: 将响应式数据变为,深只读(所有属性都为只读属性)

shallowReadonly: 将相应是数据变为,浅只读(即最外层属性为只读)

语法: 都是函数,传入一个响应式对象。

应用:不希望数据被修改时使用

13. toRaw 与 markRaw

引入: import {ref, toRaw, markRaw} from 'vue'

toRaw:

作用: 将一个 reactive 生成的 响应式对象 转为 普通对象

用法:

- const p = toRaw(person)

使用场景: 用于读取响应式对象对应的普通对象,对这个对象的所有操作,不会引起页面更新

markRaw

作用: 标记一个对象,使其不变成响应式对象

用法:

- person.car = markRaw(car)

应用: 渲染具有不可变数据源的大列表时,跳过响应式转换,提高性能。

14. customRef 自定义ref

引入:

- import {customRef} from 'vue'

语法:

customRef 是一个函数,需要传入一个参数:

这个参数也是一个函数 (track, trigger) => { get(){}, set(){} }

此函数有两个参数:

- track:表示 追踪,通知Vue追踪 value 的变化(get() 中使用)

- trigger:表示 触发,通知Vue重新解析模板(set()中使用)

此函数需要返回一个对象,对象中有 get(), set() 两个方法。

实例:

<template>
  <h1>app</h1>
  <input type="text" v-model="keyWord" >
  <h3>{{ keyWord }}</h3>

</template>

<script>
import {customRef} from 'vue'
export default {
  name: 'App',
  setup(){
    // 自定义 ref ---> myRef
    function myRef(value, delay){
      return customRef((track, trigger) => {
        let timer
        return {
          get(){
            console.log('myRef中的数据被读取了', value);
            track() // 通知 Vue 追踪 value 的变化
            return value
          },
          set(newValue){
            if(timer) clearTimeout(timer)
            console.log('myRef中的数据被修改为', newValue);
            timer = setTimeout(()=>{
              value = newValue
              trigger() // 通知 Vue 去重新解析模板
            }, delay)
          }
        }
      })

    }
    // let keyWord = ref('hello')

    let keyWord = myRef('hello', 500)

    return {
      keyWord
    }
  }
}
</script>

上面代码中定义了一个 myRef的自定义 ref,属性被修改时,延迟500毫秒再显示到页面上。

15. provide 与 inject

作用:用于父组件与后代组件通信,传递数据

引入:

import {provide, inject} from 'vue'

语法:

父组件中提供数据:

provide('数据的引用名', 数据)

后代组件中注入数据:

inject('数据的引用名')

代码:

// 父组件:
    let car = reactive({
        name: '保时捷911',
        price: '99w'
    })
    // 为后代组件提供数据
    provide('car', car)
// 后代组件:
    let car = inject('car')

16. 响应式数据类型判断方法

Vue3 中,内置了几个判断响应式数据类型的方法:

isRef():

判断数据是否为 ref 类型的响应式数据

isReactive():

判断数据是否为 reactive 类型的响应式数据

isProxy():

判断数据是否为 proxy 类型的响应式数据

isReadonly():

判断数据是否为 readonly 类型的响应式数据

Vue3 内置组件

  1. Teleport 传送组件

作用:将组件传送到想要的元素下

语法:

<Teleport to="#test" :disabled="false"></Teleport>

to: 可以是选择器,也可以是body

disabled: 用于控制是否传送,true 表示不传送到指定位置,false 表示传送到指定位置。默认值为true。

实例:

<template>
  <div>
    <button @click="isShow=true">弹窗</button>
    <Teleport to="body">
    <!-- <Teleport to="#test"> -->
        <div class="mask" v-if="isShow">

            <div class="dialog" >
                <h3>我是一个弹窗</h3>
                <h4>一些内容</h4>
                <h4>一些内容</h4>
                <h4>一些内容</h4>
                <button @click="isShow=false">关闭</button>
            </div>
        </div>
    </Teleport>
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
    setup(){
        let isShow = ref(false)
        return {isShow}
    }
}
</script>

上方代码,写了一个弹窗组件,并将组件传送到body身上。

2. Suspense 组件 和 defineAsyncComponent 异步组件

defineAsyncComponent

作用: 用于组件的异步引入

语法:

import { defineAsyncComponent } from 'vue';
const Son = defineAsyncComponent(() => import('./components/Son.vue')

Suspense:

作用: 在等待异步组件加载时,额外渲染一些内容,比如加载loading,提高用户体验

语法:

<Suspense>
    <template v-slot:default>
        <Son></Son>
    </template>
    <template v-slot:fallback>
        <h2>Loading......</h2>
    </template>
</Suspense>

Suspense 组件有两个插槽

default,表示加载完成后,展示的组件;

fallback,表示组件加载时,展示的组件。

实例:

// 父组件
<template>
  <div class="grand">
      <h3>APP组件</h3>
      <Suspense>
        <template v-slot:default>
          <Son></Son>
        </template>
        <template #fallback>
          <h2>Loading......</h2>
        </template>
      </Suspense>
  </div>
</template>

<script>
// import Son from './components/Son.vue'  // 静态引入
// import {reactive,ref, toRefs} from 'vue'
import { defineAsyncComponent } from 'vue';
const Son = defineAsyncComponent(() => import('./components/Son.vue')) // 动态引入
export default {
  name: 'App',
  components: {
    Son
},
  setup(){

    return {
    }
  }
}
</script>
<template>
  <div class="son">
    <h3>我是子组件</h3>
    {{ sum }}
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
    components: {
    },
    async setup(){
        let sum = ref(0)
        let p = new Promise((resolve) => {
            setTimeout(()=>{
                resolve({sum})
            },1000)
        })
        return await p
    }
}
</script>

默认情况下,setup函数返回的是一个普通对象,但在异步组件中,setup函数可以是一个异步函数,返回一个promise对象。

异步组件的另一种引入方式(类似于Vue2中的异步引入)

<script lang="ts">
export default{
  components:{
      'Demo': ()=> import('./components/Demo.vue')
    }
}
</script>

为什么要用异步组件?

因为在项目打包时(npm run build),如果是静态引入,我们的组件会被打包为一个js文件,如果组件很多,这个js文件体积就会变得很大,在第一次加载时就比较慢。而动态引入时,我们的组件就会分别打包为一个个的js,加载速度就会快一点。

3. 递归组件

递归组件:一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用 <FooBar/> 引用它自己。

以下例子为一个树形组件:

父组件:

<template>
  <h1>树</h1>
  <Tree :data="data"/>
</template>

<script setup>
import { reactive } from 'vue'
import Tree from './components/Tree'
let data = reactive([
  {
    name: '1-1',
    checked: false,
    children: [
      {
        name: '1-1-1',
        checked: false,
        children: [
          {
            name: '1-1-1-1',
            checked: true
          }
        ]
      }
    ]
  },
  {
    name: '1-2',
    checked: false
  },
  {
    name: '1-3',
    checked: true,
    children: [
      {
        name: '1-3-1',
        checked: true,
      }
    ]
  }
])
</script>

子组件:

<template>
  <div @click.stop="onClick(item, $event)" class="tree" v-for="item in data" :key="item.name">
    <input type="checkbox" :checked="item.checked">
    <span class="color">{{ item.name }}</span>
    <TreeItem v-if="item.children" :data="item.children" />
  </div>
</template>

<script setup>
import {defineProps} from 'vue'

  defineProps({
    data: {
      type: Array,
      default: null
    }
  })
  function onClick(item, e){
    console.log(item);
    console.log(e);
  }
</script>
<!-- 递归组件中不想使用 Tree 表示自己,可以使用这种方式来重命名 -->
<script>
export default{
  name:"TreeItem"
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.color{
  color: transparent;
  background: linear-gradient(81.02deg, rgb(250, 85, 96) -23.49%, rgb(177, 75, 244) 45.66%, rgb(77, 145, 255) 114.8%);
  background-clip: text;
}
.tree{
  margin-left: 20px;
}
</style>

4. KeepAlive 缓存组件

用于缓存组件实例。

include: 哪些组件需要缓存;

exclude: 哪些组件不需要缓存;

max : 缓存组件的最大个数;

使用 include 和 exclude时需要在组件中声明 name 选项,不然会无法生效。

<KeepAlive :include="['AVue','BVue','CVue']" >
    <component :is="comId" ></component>
</KeepAlive>

缓存组件有两个生命周期钩子:

onActivated: 在组件挂载以及每次从缓存中插入时调用;

onDeactivated:在组件从dom移除,进入缓存或卸载时调用;

<script setup>
import { onActivated, onDeactivated } from 'vue';
onActivated(()=>{
  // 调用时机为首次挂载
  // 以及每次从缓存中被重新插入时
  console.log('我被加载了');
})
onDeactivated(()=>{
  // 在从 DOM 上移除、进入缓存
  // 以及组件卸载时调用
  console.log('我被切换/卸载了')
})
</script>

推荐观看尚硅谷VUE3视频,天禹yyds,还以有小满zs的vue教程,很细。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值