Bootstrap

Vue3学习笔记

Vue3

Vue3现状

  • vue-next 2020年9月18日,正式发布了vue3.0版本
  • 主流的组件库已经发布了支持vue3.0的版本
    • element-plus基于Vue3.0的桌面端组件库
    • vant vant3.0版本移动端组件库
    • ant-design-vue

Vue3优点

  • 最火框架,国内最火的框架之一

    https://cn.vuejs.org/guide/introduction.html

  • 性能提升,运行速度是vue2.x的1.5倍左右

  • 体积更小 ,按需编译体积比vue2.x 要更小

  • 类型推断,更好的支持TS (typescript)

  • 高级给予 暴露了更底层的API和提供更先进的内置组件

  • **组合API(composition API)**能够更好的组织逻辑,封装逻辑,复用逻辑

为什么学习Vue3

适应市场学习流行的技术提高提升自己竞争力,加薪

vite基本使用

vite是什么

  • 是一个更加轻量(热更新速度快,打包构建速度快)的vue项目的脚手架工具
  • 相对于vue-cli 它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置

vite使用

  • 创建项目 npm init vite-app 项目名称 或者 yarn create vite-app 项目名称
  • 安装依赖 npm i 或者 yarn
  • 启动项目 npm run dev 或者 yarn dev

Vue3应用

  • main.js中导入createApp函数
  • 定义App.vue组件,导入main.js
  • 使用createApp函数App.vue组件创建应用实例
  • 挂载到index.html的#app容器
// 导入createApp函数
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

// 使用createApp(App) 函数基于App.vue组件创建应用实例
// 挂载到index.html的#app容器
//  扩展功能都是在app上进行
const app =createApp(App)
app.mount('#app')

选项API和组合API

什么是选项API写法Options Api

我们在vue2.x项目中使用的就是选项API写法

风格:data选项写数据,methods选项写函数…一个功能逻辑的代码分散

优点:易于学习和使用,写代码的位置已经约定好

缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑代码多了不好阅读和管理

<template>
  <div class="container">
    <div>鼠标位置</div>
    <div>X轴:{{ x }}</div>
    <div>Y轴{{ y }}</div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      x: 0,
      y: 0,
    }
  },
  mounted() {
    document.addEventListener('mousemove', this.move)
  },
  methods: {
    move(e) {
      this.x = e.pageX
      this.y = e.pageY
    },
  },
  destroyed() {
    document.removeEventListener('mousemove', this.move)
  },
}
</script>

组合API Composition API

在vue3项目中使用组合API写法

代码风格: 一个功能逻辑的代码组织在一起(包括数据,函数…)

优点:功能逻辑复杂的情况下,各个功能逻辑代码组织在一起,便于阅读和维护

缺点: 需要良好的代码组织能力和拆分逻辑能力

**补充:**为了能让大家较好的过渡到vue3.0,在vue3中也支持vue2.x的选项Api的写法

<template>
  <div class="container">
    <div>鼠标位置</div>
    <div>X轴:{{ x }}</div>
    <div>Y轴{{ y }}</div>
  </div>
</template>

<script>
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
export default {
  setup() {
    // 定义逻辑数据
    const mouse = reactive({
      x: 0,
      y: 0,
    })
    const move = (e) => {
      mouse.x = e.pageX
      mouse.y = e.pageY
    }
    onMounted(() => {
      document.addEventListener('mousemove', move)
    })
    onUnmounted(() => {
      document.removeEventListener('mousemove', move)
    })

    return {
      ...toRefs(mouse),
    }
  },
}
</script>

组合API-setup函数

  • setup是一个新的组件选项,作为组件中使用组合API的起点
  • 从组件生命周期来看,它(setup)的执行在组件实例创建之前beforeCreate执行
  • setup函数中this还不是组件实例,this是undefined
  • 在模板(视图)中需要使用的数据和函数,需要在setup中返回
<template>
  <div class="container">
    <button @click="say">{{ msg }}</button>
  </div>
</template>

<script>
export default {
  beforeCreate() {
    console.log(2)
  },
  // setup在组件创建实例之前执行
  setup() {
    console.log(1, this) //this  undefined
    // 定义数据和函数
    const msg = 'hello'
    const say = () => {
      console.log(msg)
    }

    return { msg, say }
  },
}
</script>

总结:setup组件初始化之前执行,它返回的数据和函数可以在模板中使用

组合API-生命周期

回顾vue2生命周期钩子函数
beforeCreate 创建前
created      创建完成
beforeMount  挂载前
mounted      挂载完成
beforeUpdate 更新前
updated      更新完成
beforeDestroy 销毁前
destroyed    销毁完成
vue3.0生命周期钩子函数
  • setup 创建实例前
  • onBeforeMount 挂载DOM前
  • onMounted挂载DOM后
  • onBeforeUpdate 更新组件前
  • onUpdated 更新组件后
  • onBeforeUnmount 卸载销毁前
  • onUnmounted 卸载销毁后
<template>
  <div class="container">container</div>
</template>

<script>
// 按需引入
import { onBeforeMount, onMounted } from 'vue'
export default {
  setup() {
    // 挂载DOM前
    onBeforeMount(() => {
      console.log('DOM渲染前', document.querySelector('.container')) 
      //DOM渲染前 null
    })
    // 挂载DOM后
    onMounted(() => {
      console.log('DOM渲染后', document.querySelector('.container')) 
      //DOM渲染后 <div class="container">container</div>
    })

    //可以多次使用同一个钩子,执行顺序和书写顺序相同
    onMounted(() => {
      console.log('DOM渲染后2', document.querySelector('.container'))
    })
  },
}
</script>

总结:组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同

组合API-reactive

reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据

<template>
  <div class="container">
    <h2>reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据</h2>
    <div>{{ obj.name }}</div>
    <div>{{ obj.age }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>

<script>
// 按需引入
import { reactive } from 'vue'
export default {
  setup() {
    // 普通数据---不是响应式的
    // const obj = {
    //   name: 'zs',
    //   age: 18,
    // }

    // 响应式数据
    const obj = reactive({
      name: 'zs',
      age: 18,
    })
    // 修改名字
    const updateName = () => {
      console.log('updateName')
      obj.name = 'ls'
    }

    return { obj, updateName }
  },
}
</script>

**总结:**reactive用来定义响应式对象数据

组合API-toRef函数

定义响应式数据

toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且值是关联的

<template>
  <div class="container">
    <h2>
      toRef是函数,转换**响应式对象**中某个属性为单独响应式数据,并且**值是关联的**
    </h2>
    <div>{{ name }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>

<script>
// 按需引入
import { reactive, toRef } from 'vue'
export default {
  setup() {
    // 1 定义响应式数据对象
    const obj = reactive({
      name: 'ls',
      age: 19,
    })
    console.log(obj)

    // 2模板中只需要使用name数据
    // 从响应式数据对象中解构出属性数据,不再是响应式数据
    // let { name } = obj //不能直接解构,出来的是一个普通数据

    // const updateName = () => {
    //   console.log('updateName')
    //   obj.name = 'zs'
    // }

    // -----解决办法
    const name = toRef(obj, 'name')
    console.log(name, 'name')

    const updateName = () => {
      console.log('updateName')
      // toRef 转换响应式数据包装成对象  value存放值的位置
      name.value = 'zss'
    }

    return { name, updateName }
  },
}

总结:有一个响应式对象数据,但是模板中只需要使用其中一项数据

组合API-toRefs函数

定义响应式数据

toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的

<template>
  <div class="container">
    <h2>
      toRefs是函数,转换**响应式对象**中所有属性为单独响应式数据,对象成为普通对象,并且**值是关联的**
    </h2>
    <div>{{ name }}</div>
    <div>{{ age }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>

<script>
// 按需引入
import { reactive, toRef, toRefs } from 'vue'
export default {
  setup() {
    // 1 定义响应式数据
    const obj = reactive({
      name: 'ls',
      age: 10,
    })

    // 解构 或者展开响应式数据对象---普通数据
    // const { name, age } = obj
    // const obj1 = { ...obj }

    // toRefs
    const obj2 = toRefs(obj)
    const updateName = () => {
      obj.name = 'zs'
      obj.age++
    }

    // return { ...obj1, updateName }
    return { ...obj2, updateName }
  },
}
</script>

总结:把响应式对象中的多个或者所有属性做为响应式数据

组合API-ref函数

定义响应式数据

ref函数,常用于简单数据类型定义为响应式数据

  • 在修改值,获取值的时候,需要加 .value
  • 在模板中使用ref声明的响应式,可以省略 .value
<template>
  <div class="container">
    <h2>ref函数,常用于**简单数据类型**定义为响应式数据</h2>
    <div>{{ name }}</div>
    <div>{{ data }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>

<script>
// 按需引入
import { ref } from 'vue'
export default {
  setup() {
    // 1 name数据
    const name = ref('ls')

    console.log(name.value)
    const updateName = () => {
      name.value = 'zs'
    }

    // ref常用于定义简单数据类型的响应式数据
    // 其实也可以定义复杂数据类型的响应式数据
    // 对于数据未知的情况下,ref是最适用的
    //  初始值
    const data = ref(null)

    setTimeout(() => {
      // 发送请求 拿到数据   {name:'zs'}
      //  data.value = res.data
      data.value = { name: 'zs' }
    }, 1000)

    return { name, updateName, data }
  },
}
</script>

总结:

当你明确知道需要的是一个响应式数据对象,就使用用reactive

其它情况使用ref

组合API-computed

vue2语法

computed:{
    属性名(){
        return xxx
    },
    属性名:{
        set(v){
            
        },
        get(){
            return xxx
        }
    }
}

vue3语法

  • 基本用法

computed函数,用来定义计算属性的,计算属性不能修改

<template>
  <div>今年{{ age }}岁</div>
  <div>后年{{ newAge }}岁</div>
</template>

<script>
import { ref, computed } from 'vue'
export default {
  name: 'App',
  setup() {
    const age = ref(16)
    // 计算属性  当你需要依赖现有的响应式数据,根据一定的逻辑得到一个新的数据
    // 得到后年的年龄
    const newAge = computed(() => {
      // 函数的返回值就是计算属性的值
      return age.value + 2
    })
    return { age, newAge }
  },
}
</script>
  • 高级用法

支持双向数据绑定

<template>
  <div>今年{{ age }}岁</div>
  <div>后年{{ newAge }}岁</div>
  <!-- 使用v-model绑定计算属性 -->
  <input type="text" v-model="newAge" />
</template>
<script>
import { ref, computed } from 'vue'
export default {
  name: 'App',
  setup() {
    const age = ref(16)
    // 计算属性  当你需要依赖现有的响应式数据,根据一定的逻辑得到一个新的数据
    // 计算属性的高级用法  传入对象
    const newAge = computed({
      // get函数,获取计算属性的值
      get() {
        return age.value + 2
      },
      // set函数  当你给计算属性设置值的时候触发
      set(value) {
        age.value = value - 2
      },
    })
    return { age, newAge }
  },
}
</script>

总结

给computed传入函数,返回值就是计算属性的值

给computed传入对象,get获取计算属性的值,set监听计算属性的改变

组合API-watch函数

vue2

// 监听基本数据类型
watch:{
    监听的变量名(newv,oldv){
       //代码
    }
}
// 监听复杂数据类型
watch:{
    监听的变量名:{
        deep:true,
        handler(v){
            
        }
    }
}

vue3

watch函数,用来定义侦听器的

  • 监听ref定义的响应式数据
  • 监听多个响应式数据
  • 监听reactive定义的响应式数据
  • 监听reactive定义的响应式数据,某一个属性
  • 深度监听
  • 默认执行
<template>
  <div>count的值{{ count }}</div>
  <button @click="add">改数据</button>
  <hr />
  <div>{{ obj.name }}</div>
  <div>{{ obj.age }}</div>
  <div>{{ obj.brand.name }}</div>
  <button @click="updateName">改名字</button>
  <button @click="updateBrandName">改品牌名字</button>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
  name: 'App',
  setup() {
    const count = ref(0)
    const add = () => {
      count.value++
    }
    /*
      监听数据的变化使用watch
      1:监听ref数据
      第一个参数  监听的目标值
      第二个参数  改变后触发的函数
      watch(count, (newV, oldV) => {
      console.log(newV, oldV, 9)
    })
    */

    /*
     2监听reactive定义的响应式数据
   */

    const obj = reactive({
      name: 'zs',
      age: 10,
      brand: {
        id: 1,
        name: '宝马',
      },
    })

    const updateName = () => {
      obj.name = 'ls'
    }
    const updateBrandName = () => {
      obj.brand.name = '奔驰'
    }
    // 监听reactive定义的响应式数据
    watch(obj, () => {
      console.log('数据改变了')
    })

    watch(
      () => obj.brand,
      () => {
        console.log('brand数据改变了')
      },
      {
        // 需要深度监听
        deep: true,
        // 默认执行
        immediate: true,
      }
    )

    // 监听多个数据
    watch([count, obj], () => {
      console.log('监听多个数据变化')
    })

    // 监听对象中某一个属性的变化  obj.name
    // 需要写成函数返回该属性的方式才能监听到
    watch(
      () => obj.name,
      () => {
        console.log('obj.name改变了')
      }
    )

    return { count, add, obj, updateBrandName, updateName }
  },
}
</script>

组合API-ref属性

获取DOM或者组件实例使用ref属性,写法何vue2区分开

获取单个元素或者组件

<template>
  <div ref="dom">我是box</div>
</template>
<script>
/* 

vue2 获取单个元素
1通过ref属性绑定元素
2通过this.$refs.box获取元素

*/
import { onMounted, reactive, ref, watch } from 'vue'
export default {
  name: 'App',
  setup() {
    /* 
   获取单个元素
   1:先定义个空的响应式数据 ref定义
   2:setup中返回该数据,你想获取那个dom元素,在该元素上面绑定使用ref属性绑定该数据
   */
    const dom = ref(null)
    onMounted(() => {
      console.log(dom.value)
    })

    return { dom }
  },
}
</script>

<style scoped></style>

获取v-for遍历的dom或者组件

<template>
  <ul>
    <li v-for="i in 4" :key="i" :ref="setDom">第{{ i }}个li</li>
  </ul>
</template>
<script>
import { onMounted, reactive, ref, watch } from 'vue'
export default {
  name: 'App',
  setup() {
    /* 
  定义一个空数组 ,接收所有的li
  定义个函数,往空数组pushDom
  给遍历的元素添加  :ref=“函数”
  */

    const domList = []
    const setDom = (el) => {
      domList.push(el)
    }

    onMounted(() => {
      console.log(domList)
    })
    return { setDom }
  },
}
</script>

总结

单个元素: 先声明ref响应式数据,返回给模板使用,通过ref绑定数据

遍历的元素:先定义一个空数组,定义一个函数获取元素,返回给模板使用,通过ref绑定这个函数

注意:组件更新的时候会重复的设置dom元素个给数组

最好在下面添加,更新结束后设置为空列表

onBeforeUpdate(()=>{
    domList = []
})

组件通信

父传子

父组件App.vue

<template>
  <div>父组件</div>
  <p>{{ money }}</p>
  <hr />
  <!-- 父组件的数据传递给子组件 -->
  <son :money="money"></son>
</template>
<script>
import { onMounted, reactive, ref, watch } from 'vue'
import Son from './components/Son.vue'
export default {
  components: { Son },
  name: 'App',
  setup() {
    const money = ref(100)
    return { money }
  },
}
</script>

子组件 Son.vue

<template>
  <div>子组件</div>
  <div>{{ money }}</div>
</template>

<script>
export default {
  // 子组件通过props接收父组件的数据
  props: {
    money: {
      type: Number,
      default: 0,
    },
  },
  setup(props) {
    // 获取父组件数据money
    console.log(props.money)
  },
}
</script>

总结:父传子: 在setup中使用props数据

setup(props){ //props就是父组件数组}

子传父

父组件App.vue

<template>
  <div>父组件</div>
  <p>{{ money }}</p>
  <hr />
  <!-- 父组件的数据传递给子组件 -->
  <son :money="money" @change-money="updateMoney"></son>
</template>
<script>
import { onMounted, reactive, ref, watch } from 'vue'
import Son from './components/Son.vue'
export default {
  components: { Son },
  name: 'App',
  setup() {
    const money = ref(100)

    const updateMoney = (newMoney) => {
      money.value -= newMoney
    }
    return { money, updateMoney }
  },
}
</script>

<style scoped></style>

子组件Son.vue

<template>
  <div>子组件</div>
  <div>{{ money }}</div>
  <button @click="changeMoney">花50元</button>
</template>

<script>
export default {
  // 子组件通过props接收父组件的数据
  props: {
    money: {
      type: Number,
      default: 0,
    },
  },
  setup(props, { emit }) {
    // context : attrs  emit触发自定义事件函数  slots
    // console.log(context, 'context')
    // props 父组件数组
    // 获取父组件数据money
    // console.log(props.money)
    const changeMoney = () => {
      // 消费50元
      // 通知父组件,money需要减50
      // context.emit('change-money', 50)
      emit('change-money', 50)
    }

    return { changeMoney }
  },
}
</script>

<style scoped></style>

总结: 子传父 触发自定义事件的时候emit

setup(props,{emit}{ //emit就是触发事件的函数})

v-model语法糖

在vue2.x的时候,.sync除去v-model实现双向数据绑定

<Son :money.sync="money"></Son>

在vue3.x,使用v-model="money"即可

<Son v-model:money="money"></Son>

mixins

混入(mixin)来分发vue组件中的可复用的功能。

一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被 “混入” 进入该组件本身的选项

全局混入

所有组件混入这些逻辑代码

组件可以直接使用

main.js

// 全局混入 全局mixin
// vue2.0 Vue.mixin({})
app.mixin({
  data() {
    return {
      name: 'zs',
    }
  },
  methods: {
    say() {
      this.name = 'ls'
      console.log(this.$el, '在mounted中调用哦个say函数')
    },
  },
  mounted() {
    this.say()
  },
})

Child.vue

<template>
  <div>child</div>
</template>

<script>
export default {}
</script>

<style scoped></style>

Son.vue

<template>
  <div>我是son组件{{ name }}</div>
</template>

<script>
export default {}
</script>

<style scoped></style>

局部混入

通过mixins选项进行混入

导入注册后可以使用

src/mixin.js

// 配置对象
export const followMixin = {
  data() {
    return {
      loading: false,
    }
  },
  methods: {
    followFn() {
      this.loading = true
      // 模拟请求
      setTimeout(() => {
        this.loading = false
      }, 2000)
    },
  },
}

Child.vue

<template>
  <div>child</div>
  <button @click="followFn">{{ loading ? '请求中' : '关注' }}</button>
</template>

<script>
import { followMixin } from '../mixins'
export default {
  mixins: [followMixin],
}
</script>

总结: 在vue2.0中一些复用的逻辑可以使用mixins来封装,需要考虑逻辑代码冲突的问题

vue3.0的组合API 很好的解决了这个问题,vue3中不推荐使用了

;