vue3.0基础

131 阅读6分钟

vue3基础


优点: 最火框架,国内最火前端框架之一

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

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

类型判断, 更好的支持Ts, 这个也是趋势

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

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

vite基本使用

是一个更加轻量(热更新速度快,打包构建速度快) 的vue项目脚手架工具

相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置

使用:

创建项目npm init vite-app 项目名称 或者 yarn create vite-app 项目名称

安装依赖 npm i 或者 yarn

启动项目 npm run dev 或者 yarn dev

使用vite创建项目学习vue3语法,使用vue-cli创建项目正式开发

创建应用

  • 在main.js中导入craeateApp函数
  • 定义App.vue组件, 导入到main.js
  • 使用craeteApp函数基于app.vue组件创建应用实例
  • 挂载至Index.html的#app容器

总结: 如何创建vue应用实例?

  • 通过createApp创建应用实例--->扩展功能将来都是在app上进行。

选项API和组合API

选项API(options API)

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

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

  • 优点: 易于学习和使用,写代码的位置已经约定好
  • 缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读。
  • 补充:虽然提供mixins用来封装逻辑,但是出现数据函数覆盖的概率很大,不好维护。

组合API(composition API)

vue3.x使用组合API写法

  • 一个功能逻辑的代码组织在一起(包括数据,函数)
  • 优点: 功能逻辑复杂繁多的情况下,各个功能逻辑代码组织在一起,便于阅读和维护
  • 缺点: 需要有良好的代码组织能力和拆分逻辑能力
  • 也支持vue2.x选项API写法

组合API-- setup函数

setup是一个新的组件选项,作为组件中使用组合PAI的起点

从组件生命周期来看,它的执行在组件实例创建之前,vue2.x的beforeCreate执行

意味着在setup函数中this还不是组件实例,此时是undefined

模板中需要使用的数据,函数,需要在setup中返回

<template>
  <div class="container">
    <h1 @click="say()">{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  setup() {
    console.log('setup执行了')
    console.log(this)
​
    // 定义数据和函数
    const msg = '和平'
    const say = () => {
      console.log(msg)
    }
​
    return {
      msg,
      say,
    }
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  },
}
</script>
<style scoped></style>

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

组合API-- 生命周期钩子

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() {
    onBeforeMount(() => {
      console.log('DOM渲染前', document.querySelector('.container'))
    })
​
    onMounted(() => {
      console.log('DOM渲染后1', document.querySelector('.container'))
    })
    onMounted(() => {
      console.log('DOM渲染后2', document.querySelector('.container'))
    })
  },
}
</script>
<style scoped></style>

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

组合API-- reactive函数

定义响应式数据:

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

<template>
  <div class="container">
    <div>{{ obj.name }}</div>
    <div>{{ obj.age }}</div>
​
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    //  普通数据
    // const obj = {
    //   name: '大猫',
    //   age: 8
    // },
​
    const obj = reactive({
      name: '大猫',
      age: 8,
    })
​
    const updateName = () => {
      console.log('updateName')
      obj.name = '雪豹'
    }
​
    return { obj, updateName }
  },
}
</script>
<style scoped></style>

总结: reactive函数通常是用来定义响应式对象数据

组合API-- toRef函数

定义响应式数据:

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

<template>
  <div class="container">
    {{ name }}
​
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
  name: 'App',
  setup() {
    // 响应式数据对象
    const obj = reactive({
      name: '大猫',
      age: 8,
    })
    console.log(obj)
    //模板中只需要使用name数据
    // 注意: 从响应式对象中解构出的属性数据,不在是响应式数据
    // let {name} = obj 不能直接解构,出来的是一个普通数据
    const name = toRef(obj, 'name')
    console.log(name)
​
    const updateName = () => {
      console.log('updateName')
      // toRef转换响应式数据包装成对象,value存储值的位置
      name.value = '雪豹'
    }
​
    return { name, updateName }
  },
}
</script>
<style scoped></style>

使用场景: 有一个响应式数据,但是模板中只需要使用其中一项数据

组合API-- toRefs

定义响应式数据:

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

<template>
  <div class="container">
    {{ name }}
    {{ age }}
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive, toRef, toRefs } from 'vue'
export default {
  name: 'App',
  setup() {
    // 响应式数据对象
    const obj = reactive({
      name: '大猫',
      age: 8,
    })
    console.log(obj)
    //  解构或者展开响应式数据对象
    // const {name, age} = obj
    // console.log(name,age);
    // const obj1 = {...obj}
    // console.log(obj1);
    // 以上方式导致数据就不是响应式数据了
    const obj2 = toRefs(obj)
    console.log(obj2)
​
    const updateName = () => {
      console.log('updateName')
      // obj2.name.value = '翼龙'
      obj.name = '翼龙'
    }
​
    return { ...obj2, updateName }
  },
}
</script>
<style scoped></style>

使用场景: 剥离响应式对象(解构或展开),想使用响应式对象中的多个或者所有属性做为响应式数据

组合API-- ref函数

定义响应式数据:

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

在修改值,获取值的时候需要.value

在模板中使用ref申明的响应式数据,可以省略.value

<template>
  <div class="container">
    {{ name }}
    {{ age }}
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    //  name数据
    const name = ref('东北虎')
    console.log(name)
​
    // age数据
    const age = ref(20)
    console.log(age)
    const updateName = () => {
      console.log('updateName')
      name.value = '非洲狮'
    }
​
    // ref常用定义简单数据类型的响应式数据
    // 其实也可以定义复杂数据类型的响应式数据
    // 对于数据未之的情况下 ref 是最适用的
    // const data = ref(null)
    // setTimeout(()=>{
    //   data.value = res.data
    // },1000)
    return { name, age, updateName }
  },
}
</script>
<style scoped></style>

使用场景:

  • 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
  • 其他情况使用ref

基础案例: 记录鼠标坐标 &&累加1功能

  • 记录鼠标坐标

    • 定义一个响应式数据对象,包含x和y属性。
    • 在组件渲染完毕后,监听document的鼠标移动事件
    • 指定move函数为事件对应方法,在函数中修改坐标
    • 在setup返回数据,模版中使用
  • 累加1功能

    • 定义一个简单数据类型的响应式数据
    • 定义一个修改数字的方法
    • 在setup返回数据和函数,模板中使用
<template>
  <div class="container">
    <div>坐标</div>
    <div>x:{{ x }}</div>
    <div>y:{{ y }}</div>
    <div>{{ count }}<button @click="add">累加1</button></div>
  </div>
</template>
<script>
import { onMounted, onUnmounted, reactive, toRefs, ref } from 'vue'const useMouse = () => {
  // 1. 记录鼠标坐标
  // 1.1 申明一个响应式数据,他是一个对象,包含x y
  const mouse = reactive({
    x: 0,
    y: 0,
  })
​
  // 1.3 修改响应式数据
  const move = e => {
    ;(mouse.x = e.pageX), (mouse.y = e.pageY)
  }
​
  // 1.2 等dom渲染完毕。去监听事件
  onMounted(() => {
    document.addEventListener('mousemove', move)
  })
​
  // 1.4 组件消耗,删除事件
  onUnmounted(() => {
    document.removeEventListener('mousemove', move)
  })
​
  return mouse
}
export default {
  name: 'App',
  setup() {
    const mouse = useMouse()
​
    // 数字累加
    const count = ref(0)
    const add = () => {
      count.value++
    }
​
    return { ...toRefs(mouse), count, add }
  },
}
</script>
<style scoped></style>

组合API-- computed函数

定义计算属性:

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

<!-- 计算属性基本使用 -->
<template>
  <div class="container">
    <div>今年:{{ age }}</div>
    <div>后年:{{ newAge }}</div>
  </div>
</template><script>
import { computed, ref } from 'vue'
export default {
  name: 'App',
  setup() {
    // 计算属性:当你需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据
    const age = ref(16)
    const newAge = computed(() => {
      return age.value + 2
    })
​
    return { age, newAge }
  },
}
</script><style scoped></style>
<!-- 计算属性高级用法 -->
<template>
  <div class="container">
    <div>今年:{{ age }}</div>
    <div>后年:{{ newAge }}</div>
    <input type="text" v-model="newAge" />
  </div>
</template>

<script>
import { computed, ref } from 'vue'
export default {
  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>

<style scoped></style>

目的: 让计算属性支持双向数据绑定

总结: 计算属性两种用法

  • 给computed传入函数,返回值就是计算属性的值
  • 给computed传入对象,get获取计算属性的值,set监听计算属性改变

组合API-- watch函数

定义计算属性:

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

监听ref定义的响应式数据

监听多个响应式数据

监听reactive定义的响应式数据

监听reactive定义的响应式数据,某一个属性

深度监听

默认执行

<!-- watch侦听器 -->
<template>
  <div class="container">
    <div>
      <p>count的值:{{ countNumber }}</p>
      <button @click="add">改数据</button>
    </div>
    <hr />
    <div>
      <p>{{ obj.age }}</p>
      <p>{{ obj.name }}</p>
      <p>{{ obj.brand.name }}</p>
      <button @click="updateName">改名字</button>
      <button @click="updateBrandName">改品牌名字</button>
    </div>
  </div>
</template>

<script>
import { reactive, ref, watch } from 'vue'
export default {
  name: 'App',
  setup() {
    const countNumber = ref(0)
    const add = () => {
      countNumber.value++
    }

    // 当你需要监听数据的变化就可以使用watch
    // 第一个参数是需要监听的目标,第二个参数是改变后触发的函数
    // watch(countNumber, (newVal, oldVal) => {
    //   console.log(newVal, oldVal)
    // })

    const obj = reactive({
      age: 20,
      name: '天日',
      brand: {
        id: 2,
        name: '别克',
      },
    })

    const updateName = () => {
      obj.name = '玄机'
    }
    const updateBrandName = () => {
      obj.brand.name = '东风'
    }
    // 监听一个reactive数据
    watch(obj, () => {
      console.log('数据改变了')
    })

    //  深度监听
    watch(
      () => obj.brand,
      () => {
        console.log('brand数据改变了')
      },
      {
        // 深度监听
        deep: true,

        // 默认触发
        immediate: true,
      }
    )

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

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

<style scoped></style>

组合API-- ref属性

获取DOM或者组件实例可以使用ref属性,写法与vue2.0要区别开

获取单个DOM或者组件

<!--  -->
<template>
  <div class="container">
    <!-- vue2.0 获取单个元素 -->
    <!-- 1. 通过ref属性绑定该元素 -->
    <!-- 2. 通过this.$refs.box获取元素 -->
    <!-- <div ref="box">我是box</div> -->
    <!-- vue2.0 获取v-for遍历的多个元素 -->
    <!-- 1. 通过ref属性绑定被遍历元素 -->
    <!-- 2. 通过this.$refs.li 获取所有遍历元素  -->
    <!-- <ul>
      <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    </ul> -->

    <!-- 单个元素 -->
    <div ref="dom">我是box</div>

    <!-- 被遍历的元素 -->
    <ul>
      <li v-for="i in 4" :key="i" :ref="setDom">第{{ i }}Li</li>
    </ul>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
  setup() {
    // 获取单个元素
    // 先定一个空的响应式数据ref定义的
    // setup中返回该数据, 你想获取那个dom元素,在该元素上使用ref属性绑定该数据即可
    const dom = ref(null)

    // 获取v-for遍历的元素
    // 先定义一个空数组,接收所有的Li
    // 定义一个函数,往空数组中push DOM
    const domList = []
    const setDom = el => {
      domList.push(el)
    }
    onMounted(() => {
      console.log(dom.value)
      console.log(domList)
    })

    return { dom, setDom }
  },
}
</script>

<style scoped></style>

总结:

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

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

  • 有一个边界问题:组件更新的时候会重复的设置dom元素给数组

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

组合API-- 父子通讯

  • 在vue2.x的时候 .sync 除去v-model实现双向数据绑定的另一种方式
<!-- <Son :money='money' @update:money="fn"  /> -->
<Son :money.sync='money'  />
  • 在vue3.0的时候,使用 v-model:money="money" 即可
   <!-- <Son :money="money" @update:money="updateMoney" /> -->
    <Son v-model:money="money" />

总结;

  • 父传子: 在setup中使用props数据setup(props){ // props就是父组件数据 }
  • 子传父: 触发自定义事件的emit来自 setup(props,{emit}){ // emit 就是触发事件函数 }
  • 在vue3.0中v-model和.sync已经合并成v-model指令

组合API-- 依赖注入

使用场景: 有一个父组件,里头有子组件,孙组件,有很多后代组件,共享父组件数据

父组件

<!--  -->
<template>
  <div>
    <h1>父组件:{{ money }} <button @click="money = 1000">发钱</button></h1>
    <hr />
    <Son />
  </div>
</template>

<script>
import { ref, provide } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son,
  },
  setup() {
    const money = ref(100)

    const changeMoney = salMoney => {
      console.log('changeMoney', salMoney)
      money.value = money.value - salMoney
    }
    // 将数据提供给后代组件
    provide('money', money)

    // 将函数提供给后代组件
    provide('changeMoney', changeMoney)
    return { money }
  },
}
</script>

<style scoped></style>

子组件

<!--  -->
<template>
  <div class="container">
    <h2>子组件{{ money }}</h2>
    <hr />
    <GrandSon />
  </div>
</template>

<script>
import { inject } from 'vue'
import GrandSon from './grandSon.vue'
export default {
  name: 'Son',
  components: {
    GrandSon,
  },
  setup() {
    // 接收祖先组件提供的数据
    const money = inject('money')
    return { money }
  },
}
</script>

<style scoped></style>

孙组件

<!--  -->
<template>
  <div>
    <h3>孙组件{{ money }} <button @click="fn">消费20元</button></h3>
  </div>
</template>

<script>
import { inject } from 'vue'

export default {
  name: 'grandSon',
  setup() {
    const money = inject('money')
    // 孙组件消费20,通知父组件App.vue修改
    // 孙组件不能自己修改,本着遵循单向数据流原则,也就是谁定义谁修改、
    const changeMoney = inject('changeMoney')
    const fn = () => {
      changeMoney(20)
    }
    return { money, fn }
  },
}
</script>

<style scoped></style>

总结: provide函数提供数据和函数给后代组件使用

inject函数给当前组件注入provide提供的数据和函数

v-model语法糖

在vue2.0中v-mode语法糖简写的代码 <Son :value="msg" @input="msg=$event" />

在vue3.0中v-model语法糖有所调整:<Son :modelValue="msg" @update:modelValue="msg=$event" />

<!--  -->
<template>
  <div>
    <!-- 如果你想获取原生事件事件对象 -->
    <!-- 如果绑定事函数 fn fn(e){ // e 就是事件对象 } -->
    <!-- 如果绑定的是js表达式  此时提供一个默认的变量 $event -->
    <h1 @click="$event.target.style.color = 'red'">父组件{{ count }}</h1>
    <hr />
    <!-- 如果你想获取自定义事件  -->
    <!-- 如果绑定事函数 fn fn(data){ // data 触发自定义事件的传参 } -->
    <!-- 如果绑定的是js表达式  此时 $event代表触发自定义事件的传参 -->
    <!-- <Son :modelValue="count" @update:modelValue="count=$event" /> -->
    <Son v-model="count" />
  </div>
</template>

<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son,
  },
  setup() {
    const count = ref(10)
    return { count }
  },
}
</script>

<style scoped></style>
<!--  -->
<template>
  <div class="container">
    <h2>子组件{{ modelValue }} <button @click="fn">改变数据</button></h2>
  </div>
</template>

<script>
export default {
  name: 'Son',
  props: {
    modelValue: {
      type: Number,
      default: 0,
    },
  },
  setup(props, { emit }) {
    const fn = () => {
      // 改变数据
      emit('update:modelValue', 100)
    }
    return { fn }
  },
}
</script>

<style scoped></style>

总结: vue3.0封装组件支持v-model的时候,父传子: modelValue 子传父 @update:modelValue

补充: vue2.0的 xxx.sync 语法糖解析 父传子 :xxx 子传父 @update:xxx 在vue3.0 使用 v-model:xxx 代替。