vue3上部分

1.初识vue3:

vue3支持大多数vue2特性

性能提升

打包减少40%、初次渲染快55%、更新渲染快133%、内存减少54%、使用Proxy代替defineProperty实现数据响应式、重写虚拟dom的实现和Tree-shaking

新增特性:

Composition(组和)api

setup:(ref和reactive/computed和watch/新的生命周期/provide和inject)

新组件:Fragment-文档碎片、Teleport-瞬移组件位置、Suspenes-异步加载组件的loading界面

其他api: 全局api的修改、将原来的全局api转移到应用对象、模板语法变化

2.创建vue3项目:

一、使用vue-cli创建项目

1.如果是第一次创建vue项目,那么应该先安装vue脚手架,全局安装一次即可,其命令:

yarn add @vue/cli -g

2.查看是否安装成功,可以通过查看版本号得知是否安装成功,其命令:

vue --version 或 vue -V

3.通过vue-cli脚手架创建vue项目,其命令:

vue create my-test 

4.进入项目输入命令启动项目:npm run serve ,可以在浏览器看到项目正常启动了。

二、使用vite创建项目:

vite是一个由原生ESM驱动的web开发构建工具,在开发环境下基于浏览器原生ES imports开发,它做到了本地快速开发启动,在生产下基于rollup打包。快速的冷启动,无需打包操作,及时的热模块更新,替换性能和模块数量的解耦让更新更快,真正的按需编译,不再等待整个应用编译完成。

1.通过vite创建项目的命令:

npm init vite-app vitedemo

2.进入项目先下载依赖包:vite创建的项目,默认是没有下载依赖包的:

cd vitedemo
npm install

3.启动项目,其命令:

npm run dev

3.vue3项目源码分析:

main.ts文件分析:

// 1.createApp方法在vue3中用来创建一个应用
import { createApp } from 'vue'
// 2.App组件为所有组建的父级组件:
import App from './App.vue'
import router from './router'
import store from './store'
// 3.createApp传入App组件创建一个vue实例,并使用链式调用挂载vuex和router并通过mount挂载到#app上面:
createApp(App).use(store).use(router).mount('#app')

App.vue文件分析:

<template>
  <!-- 1.vue3中template中可以没有根标签: -->
  <nav>
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
  </nav>
  <router-view/>
</template>

HomeView.vue组件分析:

<!-- 1.lang="ts" 表示可以写ts: -->
<script lang="ts">
// 2.defineComponent方法用来定义组件:
import { defineComponent } from 'vue'
// 引入一个HelloWorld组件:
import HelloWorld from '@/components/HelloWorld.vue'
// 3.导出这个组件:
export default defineComponent({
  // 4.当前组件的名称:
  name: 'HomeView',
  // 5.给当前组件注册其他组件:
  components: {
    // 将HelloWorld组件注册到当前HomeView组件中:
    HelloWorld
  }
})
</script>

4.setup和ref:

setup是一个新的配置,所有的组合API函数都在此使用,它只在初始化时执行一次,函数如果返回对象,对象中的属性或方法,在模板中可以直接使用,setup可以理解为vue2中data中数据,但是并不是响应式的,如果需要响应式,那么还需要借助ref, setup方法组件每次进入都会执行一次。

ref是用来定义一个基本类型(测试引用型也会响应式被改)的响应式数据的方法,如果想要操作这个响应式数据,可以通过此属性返回对象的value方法对数据值进行操作,而模板中是不需要value的,如:

<template>
  <div class="about">
    <!-- 3.使用setup返回对象中的属性: -->
    <p>{
   
   { sayHello }}</p>
    <p>{
   
   { satHi }}</p>
    <button @click="changeStringHandle">更新变量的值</button>
  </div>
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'AboutView',
  // 1.setup:组件每次进入都会执行一次:
  setup () {
    console.log('setup函数执行了')
    let str = 'hello'
    // 3.ref用来定义一个响应式数据,ref返回一个对象,其属性value可以拿到具体的值,并用来对其值进行操作
    const str2 = ref('hi')
    function changeStringHandle () {
      str = '哈喽'
      str2.value = '嗨'
      console.log(str) // 点击按钮方法执行了,但是view中sayHello对应的视图并没有发生变化,是因为setup默认返回的数据并非响应式
    }
    // 3.setup中返回的对象的属性或方法可以直接在template中使用:
    return {
      sayHello: str,
      satHi: str2,
      changeStringHandle
    }
  }
})
</script>

引用型被改:

<template>
  <div class="about">
    <p>{
   
   { satHi.names }}</p>
    <button @click="changeStringHandle">更新变量的值</button>
  </div>
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'AboutView',
  setup () {
    const str2 = ref({
      names: '小明',
      age: 18
    })
    function changeStringHandle () {
      str2.value.names = '嗨'
    }
    return {
      satHi: str2,
      changeStringHandle
    }
  }
})
</script>

5.reactive:

reactive用来定义一个引用型的响应式数据,它返回一个Proxy的代理对象,被代理者就是reactive中的传入对象。

<template>
  <div class="about">
    <p>{
   
   { user.names }}</p>
    <p>{
   
   { user.age }}</p>
    <p>{
   
   { user.height }}</p>
    <p>{
   
   { user.sex }}</p>
    <button @click="updataHandle">更新变量的值</button>
  </div>
</template>
<script lang='ts'>
import { defineComponent, reactive } from 'vue'
export default defineComponent({
  name: 'AboutView',
  setup () {
    const obj:any = {
      names: '小明',
      age: 18
    }
    const user = reactive(obj)
    console.log(user) // 返回一个代理
    function updataHandle () {
      user.names = '小红'
      // user.age = 16
      obj.age = 16 // 教程中直接修改对象的方式是不能响应式的,但是经测试直接改变obj的属性,view试图中也是会改变的,不推荐直接修改
      obj.height = 180
      user.sex = '男'
      // delete obj.names // 可以被删除
      delete user.names // 可以被删除
    }
    return {
      user,
      updataHandle
    }
  }
})
</script>

6.vue3响应数据的原理:

vue2中数据响应是: 使用 Object.defineProperty 方法添加对象,重写了原有的 get 和 set 方法实现。

vue3中数据响应是通过Proxy代理实现的,它是深度更新的,具体如下:

// 目标对象:给对象设置any类型,表示可以接收任意类型的对象属性值
const obj:any = {
  names: '小明',
  age: 18
}

// 代理对象:Proxy第一个参数传入目标对象,第二个参数用于操作目标对象
const proxyObj = new Proxy(obj, {
  // 1.get方法用于获取某个对象的属性:参数一为目标对象,参数二为对象属性,此方法需要通过返回 Reflect.get才可以在代理对象中拿到属性值
  get (target, props) {
    console.log('get方法调用了')
    return Reflect.get(target, props)
  },
  // 2.set方法用于设置某个对象的属性值,同时可以给某个对象添加新的属性,同样需要通过返回Reflect.set才可以实现
  set (target, props, newValue) {
    console.log('set方法调用了')
    return Reflect.set(target, props, newValue)
  },
  // 3.deleteProperty方法用于删除某个对象的属性值,同样需要通过返回Reflect.deleteProperty才可以实现
  deleteProperty (target, props) {
    console.log('属性被删除了')
    return Reflect.deleteProperty(target, props)
  }

})

// 通过代理对象获取目标对象中的某个属性值:
console.log(proxyObj.names) // 未返回Reflect.get时打印内容为:undefined;返回Reflect.get时打印内容为:小明
// 通过代理对象设置目标对象中的某个属性值:
proxyObj.age = 22
// 通过代理对象新增某个属性:
proxyObj.height = 150
// 查看目标对象:
console.log(obj) // {names: '小明', age: 22, height: 150}
// 通过代理对象删除某个对象的属性:
delete proxyObj.names
// 查看目标对象:
console.log(obj) // {age: 22, height: 150}

7.setup详细介绍:

父组件:

<template>
  <div class="about">
    <p>父组件:{
   
   { satHi }}</p>
    <p>父组件:{
   
   { num }}</p>
    <button @click="changeStringHandle">父组件:更新变量的值</button>
    <!-- 1.vue3父子组件通信支持props型 -->
    <HelloWorld :msg="satHi" @onChange="onChangeHandle" />
  </div>
</template>
<script lang='ts'>
import HelloWorld from '@/components/HelloWorld.vue'
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'AboutView',
  // 2.vue3注册组件跟vue2一样
  components: {
    HelloWorld
  },
  // 3.vue3通过props接收父组件传递过来的属性值:props接收一个数组或对象
  props: [],
  // 4.beforeCreate()生命周期函数同样在vue3中支持
  beforeCreate () {
    console.log('beforeCreate执行了')
  },
  // 5.setup的执行时机要早于beforeCreate,由此说明setup执行时当前组件还没有被创建,所以在setup中this是无法使用的,也不能通过this去访问data/computed/methods/props
  // 6.setup不能是一个async函数,因为async返回一个promise,template中看不到对象的属性了
  setup (props, context) {
    console.log(props) // props是一个代理对象,其中有父级组件传递给子级组件的数据,并且是在子级组件中使用props接收到的所有属性,也就是通过props点第三步中prpos中的属性是可以拿到父组件传递过来的属性值的
    console.log(context) // context是一个函数对象,里面有attrs对象获取当前组件标签上的所有属性的对象、emit方法用于分发事件 、slots插槽
    console.log('setup执行了')
    const str2 = ref('hi') // ref响应式后,传递给子组件的值,也会跟着父组件的变化而变化
    function changeStringHandle () {
      str2.value = '嗨'
    }
    // 7. 监听子组件通过emit分发的事假处理方法:
    function onChangeHandle (e: any) {
      console.log('子组件触发了onChange事件,父组件监听到并执行了处理:')
      str2.value = e
    }
    return {
      satHi: str2,
      num: 99,
      changeStringHandle,
      onChangeHandle
    }
  },
  // 8.setup中返回的对象属性和data函数中返回对象的属性都可以在template模板中使用,setup和data中的对象属性和methods中的方法会合并为组件对象的属性和方法,这里的合并属性名和方法名不能重复,否则报错
  data () {
    return {
      num2: 88
    }
  },
  // 7.vue3同样支持data方法和methods对象,但是推荐在setup中定义属性和方法
  methods: {
    change2Handle () {
      console.log('methods中的方法')
    }
  },
  mounted () {
    console.log(this) // 这里this也是一个代理对象,target下属性和方法是setup和data中的所有属性
  }
})
</script>

子组件:

<template>
  <div class="hello">
    <h1>子组件中:{
   
   { msg }}</h1>
    <button @click="sendEmitHandle">子组件分发onChange事件</button>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup (props, context) {
  // setup (props, {attrs, emit, expose, slots}) { // 这里直接可以解构出来context的某个属性
    console.log(props.msg) // hi
    console.log(context)
    function sendEmitHandle () {
      console.log('子组件分发一个事件:')
      context.emit('onChange', '子组件分发传递过来的值')
    }
    return {
      sendEmitHandle
    }
  }
})
</script>

8.reactive与ref细节:

<template>
  <div class="about">
    <div>obj1: {
   
   { obj1 }}</div>
    <div>obj2: {
   
   { obj2 }}</div>
    <button @click="updataHandle">改变值</button>
  </div>
</template>
<script lang='ts'>
import { defineComponent, ref, reactive } from 'vue'
export default defineComponent({
  name: 'AboutView',
  setup (props, context) {
    // 1.ref和reactive都可以以传入一个对象使得传入的对象成为响应式的,并且深层次的属性也是可以响应式变化的(因为每个对象都是被代理的),但是ref底层还是通过reactive处理的,
    // ref内部通过给value属性添加getter和setter来实现对数据的劫持,reactive内部是通过proxy来实现对对象内部所有数据的劫持,并通过reflect操作对象内部数据
    const obj1 = ref({
      names: '小明',
      age: 20,
      info: {
        identitys: '职员'
      }
    })
    const obj2 = reactive({
      names: '小红',
      age: 18,
      info: {
        identitys: '学生'
      }
    })
    const updataHandle = () => {
      // 2.特别注意两种声明的响应式变量在js和template中的访问方式
      obj1.value.info.identitys = '网络管理员' // 视图中值被改变了
      obj2.info.identitys = '大学生' // 视图中值被改变了
    }
    return {
      obj1,
      obj2,
      updataHandle
    }
  }
})
</script>

9.计算属性和监视:

<template>
  <div class="hello">
    <div>
      <span>姓氏:</span>
      <input type="text" v-model="firstName">
      <p>firstName:{
   
   { firstName }}</p>
    </div>
    <div>
      <span>名字:</span>
      <input type="text" v-model="lastName">
      <p>lastName:{
   
   { lastName }}</p>
    </div>
    <div>
      <span>fullName:</span>
      <input type="text" v-model="fullName">
      <p>fullName:{
   
   { fullName }}</p>
    </div>
    <div>
      <span>fullName2:</span>
      <input type="text" v-model="fullName2">
      <p>compChange:{
   
   { compChange }}</p>
      <input type="text" v-model="compChange">
    </div>
    <span>-----------通过计算属性实现姓名显示:----------</span>
    <p>fullName:{
   
   { fullName }}</p>
    <p>fullName2:{
   
   { fullName2 }}</p>

    <span>----------------watch监听非响应式数据-------------</span>
    <div>
      <span>user.names</span>
      <input type="text" v-model="user.names">
      <p>user.names:{
   
   { user.names }}</p>
      <input type="text" v-model="compChange">
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, watchEffect } from 'vue'

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup (props, context) {
    const firstName = ref('')
    const lastName = ref('')
    const compChange = ref('')
    const user = reactive({
      names: '小明',
      age: 18
    })
    // 1.vue3中计算属性:使用计算属性时先引入,如果只传入一个参数回调函数,该函数表示get,且计算属性期望返回一个值(该值为ref对象),所有可以直接在计算属性前面定义一个变量用于接收计算后的值
    const fullName = computed(() => {
      return firstName.value + lastName.value
    })
    // 2.vue3中计算属性可以传入一个对象,对象中有set和get方法:其中get用于得到一个值并返回给计算属性对应的变量,而set接收计算属性对应的变量在视图中发生变化的值(通过计算属性视图是不能直接驱动数据变化的,也就是说在视图中改变fullName2,但是fullName2值在数据层是没有改变的,而视图中的最新值是在set方法的参数中接收)
    const fullName2 = computed({
      get () {
        console.log('get')
        return firstName.value + lastName.value
      },
      set (val: string) {
        console.log(val)
        compChange.value = val // 怪异现象,给compChange赋值的话是断断续续的,不能连着,就连console.log(val)中也是断断续续的了,如果注释当前行代码,console.log(val)中的值是连着的,很诡异
        // console.log(compChange.value)
      }
    })
    // 3.watch监听器在vue3中同样是需要引入的,该方法第一个参数为要监听的属性(该属性可以是单个值,也可以是一个数组,当为数组时,参数二函数回调参数也是一个数组),第二个参数是一个回调函数,回调函数的参数为最新要监听的属性值,watch可接第三个参数对象,用于配置此监听器{immediate:true}表示默认执行一次监听,因为watch默认是不会执行,只有监听到值变化时才会执行一次,deep: true开启深度监测
    watch(firstName, (val) => {
      console.log('watch监听到的值:')
      console.log(val)
      // lastName.value = val
    }, { immediate: true, deep: true })
    // 4.在这里user是一个响应式数据,但是user.names不是一个响应式数据,watch直接是无法监听非响应式数据的,只能通过回调函数的形式并返回值,如:[user.names, user.age]可以为:[() => user.names, () => user.age]
    watch([() => user.names], (val) => {
      console.log('watch监听非响应式数据执行了:')
      console.log(val)
    })
    // 5.watchEffect也是一个监听器,无需配置默认监听,它默认会自动监听一次,且它会监听回调函数中主动赋值的属性,如打印某个属性值时,这个属性值被监听了,如将某个属性的值赋值给另一个变量时,这个属性值被监听了
    watchEffect(() => {
      // compChange.value = lastName.value + 'watchEffect'
      console.log(lastName.value)
      console.log('watchEffect执行了')
    })
    return {
      firstName,
      lastName,
      fullName,
      fullName2,
      compChange,
      user
    }
  }
})
</script>

10.生命周期:

vue3中生命周期和vue2中生命周期基本一样,只有最后销毁前和销毁的不一样,在vue3中分别是beforeUnmount和unmounted;vue2中生命周期函数都是组件的配置对象,而vue3中都是组合式api,当然vue2中的配置对象生命周期在vue3中同样支持,vue2中的生命周期函数在vue3中对应的组合式api分别如下:

beforeCreated -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy(且vue3中vue2改名为:beforeUnmount) -> onBeforeUnmount
destroyed(且vue3中vue2改名为:unmounted) -> onUnmounted
errorCaptured -> onErrorCaptured

vue3还新增了2个生命周期函数:

onRenderTracked:

onRenderTriggered:

<template>
  <div class="hello">
    hello组件输出:{
   
   { msg }}
  </div>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

export default defineComponent({
  neme: 'HelloWorld',
  props: {
    msg: String
  },
  beforeCreate () {
    console.log('1.vue2中beforeCreate')
  },
  created () {
    console.log('2.vue2中created')
  },
  beforeMount () {
    console.log('3.vue2中beforeMount')
  },
  mounted () {
    console.log('4.vue2中mounted')
  },
  beforeUpdate () {
    console.log('5.vue2中beforeUpdate')
  },
  updated () {
    console.log('6.vue2中updated')
  },
  beforeUnmount () {
    console.log('7.vue2中beforeUnmount')
  },
  unmounted () {
    console.log('8.vue2中unmounted')
  },
  setup (props, context) {
    console.log('1-2.vue3中setup')
    onBeforeMount(() => {
      console.log('3.vue3中onBeforeMount')
    })
    onMounted(() => {
      console.log('4.vue3中onMounted')
    })
    onBeforeUpdate(() => {
      console.log('5.vue3中onBeforeUpdate')
    })
    onUpdated(() => {
      console.log('6.vue3中onUpdated')
    })
    onBeforeUnmount(() => {
      console.log('7.vue3中onBeforeUnmount')
    })
    onUnmounted(() => {
      console.log('8.vue3中onUnmounted')
    })
  }
})
</script>

执行结果:
请添加图片描述

11.hook函数:

vue3中的hook函数类似于vue2中mixin技术,hook函数的优势:很清楚复用功能代码的来源,vue2中mixin是将一段在多个组件中都用到的代码惊醒抽离,然后通过mixin引入到当前组件即可,而vue3中和vue2中类似。

封装hook函数:

// 引入需要用的到组合式api:
import { ref, onMounted, onBeforeUnmount } from 'vue'
// 1.导出一个公共的方法用来获取点击屏幕的坐标:
export const usePointerClick = () => {
  const x = ref(0)
  const y = ref(0)
  const clickHandle = (e: any) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  // 页面挂载时给window注册获取坐标事件
  onMounted(() => {
    window.addEventListener('click', clickHandle)
  })
  // 页面卸载时注销window获取坐标事件
  onBeforeUnmount(() => {
    window.removeEventListener('click', clickHandle)
  })
  // 将值返回出去
  return {
    x,
    y
  }
}

组件中使用:

<template>
  <div class="home">
    <div>home页:</div>
    <div>点击的坐标是:{
   
   { x + ',' + y }}</div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
// 2.组件中引入hook函数:
import { usePointerClick } from '@/hook/hookModule'
export default defineComponent({
  setup () {
    // 3.调用hook出想要的结果并挂载到当前组件中:
    const { x, y } = usePointerClick()
    return {
      x,
      y
    }
  }
})
</script>

12.toRefs的使用:

toRefs可以把一个响应式对象reactive转换成普通对象,改普通对象的每一个属性都是一个ref。

<template>
  <div class="home">
    {
   
   { age }} <!-- 不可以动态变化,但是通过toRefs将reactive转换后,此属性称为响应式变化的了 -->
    {
   
   { obj.age }} <!-- 可以动态变化 -->
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
  setup () {
    const obj = reactive({
      name: '胡子',
      age: 18,
      height: 100
    })
    // 1.通过reactive初始化的对象在下面结构后不能动态变化了,此时可以使用toRefs将reactive初始化的对象转换成一个普通的对象,改普通对象的属性是一个ref,此时就可以成为响应式了
    const obj2 = toRefs(obj)
    setInterval(() => {
      obj.age++
      obj.height++
    }, 1000)
    return {
      ...obj, // 通过结构出来的属性不能响应式变化
      obj,
      ...obj2 // 根据结构语法特点,后面的属性会覆盖前面的属性,因此obj2结构出来的属性是响应式变化的
    }
  }
})
</script>

13.ref获取元素:

ref还有另一个作用,用于获取页面中的元素,对元素操作需要在onMounted钩子后面,并且需要先判断其value值是否存在,如:

<template>
  <div class="home">
    <div class="box" ref="box">box盒子</div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
  setup () {
    // 页面加载前这里还获取不到页面元素,所以括号中可以给null,因为最终存的是html标签,所以可以使用泛型在前面做约束,但是刚开始括号中是null,所以泛型也要使用或加上null做判断取值
    const box = ref<HTMLElement | null>(null)
    onMounted(() => {
      console.log(box) // 此时box就是页面中的元素,但是想要操作元素,前面还要通过判断value是否存在,当存在时在操作,如:
      box.value && box.value.setAttribute('style', 'color: pink') // js原生操作dom的方式部分不支持,具体遇到问题在百度
    })
    return {
      box
    }
  }
})
</script>

14.shallowReactive和shallowRef:

shallowReactive只处理对象的外层响应式,它是一个浅Reactive;

shallowRef只处理value的响应式,不进行对象的reactive处理。

经测试:它们都会深度监测,测试代码:

<template>
  <div class="home">
    <div>ref{
   
   { val1 }}</div>
    <div>reactive{
   
   { val2 }}</div>
    <div>shallowRef{
   
   { val3 }}</div>
    <div>shallowReactive{
   
   { val4 }}</div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, shallowRef, shallowReactive } from 'vue'
export default defineComponent({
  setup () {
    const val1 = ref({
      name: 'ref',
      son: {
        name: 'sonRef'
      }
    })
    const val2 = reactive({
      name: 'reactive',
      son: {
        name: 'sonReactive'
      }
    })
    const val3 = shallowRef({
      name: 'shallowRef',
      son: {
        name: 'sonShallowRef'
      }
    })
    const val4 = shallowReactive({
      name: 'shallowReactive',
      son: {
        name: 'sonShallowReactive'
      }
    })
    setInterval(() => {
      val1.value.name += '*'
      val2.name += '*'
      val3.value.name += '*'
      val4.name += '*'
      val1.value.son.name += '+'
      val2.son.name += '+'
      val3.value.son.name += '+'
      val4.son.name += '+'
    }, 2000)
    return {
      val1,
      val2,
      val3,
      val4
    }
  }
})
</script>

提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:[email protected]联系笔者删除。
笔者:苦海

猜你喜欢

转载自blog.csdn.net/weixin_46758988/article/details/131671400