Vue3快速入门(一)

Vue3快速入门(一)

个人博客:Vue3快速入门(一)

实习进入公司后,简单安装vscode、node、git等必备工具后,导师直接问有没有学过vue3,说了只学过vue2后,就给我分享了两篇文章,以及让我查看文档,快速从vue2加速到vue3。本文将参考到很多文章、文档,部分借用的可能没有备注出来,侵权请联系。(不过就算是参考,例子我很多按自己的理解弄成自己以后看更容易理解的了,虽然也差不了多少)

顺带附上以前的笔记:Vue2

1. 构建项目

开始的第一步,当然就是构建项目啦。这里就有一个重大的区别了,vue3使用web开发构建工具vite,而不是webpack

vite优点:

  • 无需打包,快速的冷服务器启动
  • HMR(热更新)
  • 按需编译

另外,vite开启的服务器端口默认是3000,而不是8080

npm init vite@latest

# 使用yarn
yarn create vite

按需选择

Vue3快速入门(一)_第1张图片

项目启动的方式不再是npm run serve了,而是npm run dev

Vue3快速入门(一)_第2张图片

2. 入口文件变化

Vue2

import Vue from 'vue'
import App from './App.vue'

import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

Vue3

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

Vue3引入的不再是Vue构造函数,而是createApp工厂函数。

3. 组合式API

3.1 set up

setup函数是Composition API(组合API)的入口

在setup函数中定义的变量和方法需要return出去,才能在模板中使用

实际上和vue2类似,只不过在vue2中数据在data函数中,方法在methods节点中,而vue3则是"更有人情味了",和原生js更相似,数据和方法就是普通的变量和方法,只需要return出去,就能在模板中使用。

Vue3

<template>
  <button @click="alertName">alert namebutton>
  <p>
    {{ nickname }}
  p>
template>


<script>
export default {
  name: "App",

  setup() {
    let nickname = "赤蓝紫"

    function alertName() {
      alert(`我是${nickname}`)
    }

    return {
      nickname,
      alertName
    }
  }
}
script>



template里不再必须要一个唯一的根标签了,这个改进个人感觉很舒服。查了一下资料,发现是虽然看似没有根节点,但是只是vue3的根节点是一个虚拟节点,不会映射到一个具体节点。因为一棵树必须有一个根节点,所以使用虚拟节点作为根节点非常有用。

Vue2

<template>
  <div id="app">
    <button @click="alertName">alert nicknamebutton>
    <p>
      {{ nickname }}
    p>
  div>
template>

<script>
export default {
  name: "App",
  data() {
    return {
      nickname: "赤蓝紫"
    }
  },
  methods: {
    alertName() {
      alert(`我是${this.nickname}`)
    }
  }
}
script>

3.2 ref

先看一下,下面这个例子

<template>
  <button @click="changeName">change namebutton>
  <p>
    {{ nickname }}
  p>
template>


<script>
export default {
  name: "App",
  setup() {
    let nickname = "赤蓝紫"

    function changeName() {
      nickname = "clz"
      console.log(nickname)
    }

    return {
      nickname,
      changeName
    }
  }
}
script>

点击后,发现数据改变了,但是p标签中的名字却没变,难道是vue的数据驱动视图失效了。

当然,并不是,只是vue3中多出了响应式数据和普通数据的区别,只有响应式数据才能驱动视图的改变。而上面的nickname只是字符串,不是响应式数据,试图自然也不会发生改变。

而将字符串变成响应式数据也非常简单,只需要引入并使用ref即可

<template>
  <button @click="changeName">change namebutton>
  <p>
    {{ nickname }}
  p>
template>


<script>
import { ref } from "vue"

export default {
  name: "App",
  setup() {
    let nickname = ref("赤蓝紫")

    function changeName() {
      nickname = "clz"
      console.log(nickname)
    }

    return {
      nickname,
      changeName
    }
  }
}
script>

然而,还是不行,这是为什么呢?原因就是ref把nickname变成RefImpl的实例对象了,修改的时候要.value去修改,底层还是用的get和set去操作。

把上面changeName方法中的nickname = "clz"注释掉后,再点击按钮,就能知道变成RefImpl的实例对象了

image-20220219171513259

最终版本:

<template>
  <button @click="changeName">change namebutton>
  <p>
    {{ nickname }}
  p>
template>


<script>
import { ref } from "vue"

export default {
  name: "App",
  setup() {
    let nickname = ref("赤蓝紫")

    function changeName() {
      nickname.value = "clz"
      // console.log(nickname)
    }

    return {
      nickname,
      changeName
    }
  }
}
script>

修改要用.value来修改,为什么显示时不用{{ nickname.value }}来显示呢?这是因为vue3检测到是ref对象后,直接给你nickname.value了(还挺人性化)

3.3 reactive

<template>
  <button @click="changeName">change namebutton>
  <p>
    name: {{ people.name }}
  p>
  <p>
    age: {{ people.age }}
  p>
template>


<script>
import { ref } from "vue"

export default {
  name: "App",
  setup() {
    let people = ref({
      name: "赤蓝紫",
      age: 21
    })

    function changeName() {
      people.value.name = "clz"
    }

    return {
      people,
      changeName
    }
  }
}
script>

乍一看和上面的ref中一样,但是实际上如果是将对象类型转换成响应式数据是应该使用函数reactive的,只是如果ref中是对象的话,会自动调用reactive而已。打印people.value可以发现不再是RefImpl对象了,而是Proxy对象。

image-20220219173758962

  • 基本数据类型:根据Object.defineProperty里的getset进行数据劫持来实现响应式
  • 对象类型:通过Proxy来实现响应式
<template>
  <button @click="changeName">change namebutton>
  <p>
    name: {{ people.name }}
  p>
  <p>
    age: {{ people.age }}
  p>
  <p>hobby: {{hobbys[0]}}, {{hobbys[1]}}p>
template>


<script>
import { reactive } from "vue"

export default {
  name: "App",
  setup() {
    let people = reactive({
      name: "赤蓝紫",
      age: 21
    })
    let hobbys = reactive(["音乐", "动漫"])
    // let hobbys = ["音乐", "动漫"]

    function changeName() {
      people.name = "clz"
      hobbys[0] = "学习"
    }

    return {
      people,
      changeName,
      hobbys
    }
  }
}
script>

上面有个问题:数组形式的不通过reactive()转换成响应式也是响应式数据,暂不知道原因

也可以按vue2中data的形式来写

<template>
  <button @click="changeName">change namebutton>
  <p>
    name: {{ data.people.name }}
  p>
  <p>
    age: {{ data.people.age }}
  p>
  <p>hobby: {{data.hobbys[0]}}, {{data.hobbys[1]}}p>
template>


<script>
import { reactive } from "vue"

export default {
  name: "App",
  setup() {
    let data = reactive({
      people: {
        name: "赤蓝紫",
        age: 21
      },
      hobbys: ["音乐", "动漫"]
    })

    function changeName() {
      data.people.name = "clz"
      data.hobbys[0] = "学习"
    }

    return {
      data,
      changeName
    }
  }
}
script>

ref和reactive的区别

ref reactive
定义基本类型数据 定义对象或数组类型数据
通过Object.defineProperty()getset来实现响应式(数据劫持) 通过Proxy实现响应式(数据劫持),通过Reflect操作源代码内部数据
操作数据需要.value,读取不需要 操作个读取数据都不需要.value

3.3 computed

计算属性,和vue2差不多

<template>
  r: <input type="text" v-model.number="color.r"><br>
  g: <input type="text" v-model.number="color.g"><br>
  b: <input type="text" v-model.number="color.b"><br>
  rgb: <input type="text" v-model="color.rgb" :style="{color: color.rgb}">
template>


<script>
import { reactive, computed } from "vue"

export default {
  name: "App",
  setup() {
    let color = reactive({
      r: 255,
      g: 0,
      b: 0
    })

    color.rgb = computed(() => {
      return `rgb(${color.r}, ${color.g}, ${color.b})`
    })

    return {
      color
    }
  }
}
script>

如果修改计算出来的东西,会报警告,因为计算属性是只读属性

image-20220219200708560

实现可修改:

<template>
  r: <input type="text" v-model="color.r"><br>
  g: <input type="text" v-model="color.g"><br>
  b: <input type="text" v-model="color.b"><br>
  rgb: <input type="text" v-model="color.rgb" :style="{color: color.rgb}">
template>


<script>
import { reactive, computed } from "vue"

export default {
  name: "App",
  setup() {
    let color = reactive({
      r: 255,
      g: 0,
      b: 0
    })

    color.rgb = computed({
      get() {
        console.log(color.r)
        return `rgb(${color.r},${color.g},${color.b})`
      },
      set(value) {
        let rgbList = value.split(",")
        color.r = rgbList[0].slice(4)
        color.g = rgbList[1]
        color.b = rgbList[2].slice(0, -1)
      }
    })

    return {
      color
    }
  }
}
script>

实现计算属性可修改的关键:computed()参数为一个对象,对象中有一个get方法用来获取值,set方法用来修改值

3.4 watch

监听器

<template>
  <div class="home">
    <h1>当前数字为:{{num}}h1>
    <button @click="num++">+1button>
  div>
template>

<script>
import { ref, watch } from "vue"

export default {
  name: "Home",
  setup() {
    let num = ref(0)
    watch(
      num, // 如果想监听多个数据,则第一个参数是要监听的数据数组,其中newValue、oldValue也是数组,第一个元素就是监听的第一个参数
      (newValue, oldValue) => {
        console.log(`数字增加了,现在的值${newValue}, 原值${oldValue}`)
      }
      // {
      //   immediate: true,
      //   deep: true
      // } // 第三个参数就是选项设置,可设置立即监听、深度监听
    )

    return {
      num
    }
  }
}
script>
<template>
  <div class="home">
    <h1>age:{{people.age}}h1>
    <button @click="people.age++">年龄加1button>
    <h1>salary:{{people.job.salary}}h1>
    <button @click="people.job.salary+=100">薪水加100button>
  div>
template>

<script>
import { reactive, watch } from "vue"
export default {
  name: "Home",
  setup() {
    let people = reactive({
      name: "赤蓝紫",
      age: 21,
      job: {
        salary: -10
      }
    })
    watch(
      people,
      (newValue, oldValue) => {
        console.log(`信息改变了`, newValue, oldValue) // 直接监听reactive所定义的一个响应式数据,会出现oldValue也是更新后的值,且默认开启深度监听,还无法关闭
      },
      {
        deep: false
      }
    )

    // 监听reactive所定义的一个响应式数据中的某个属性
    // 如果是对象还需要开启深度监听,才能监听到变化。
    // 或者变为监听people.job.salary(直接手动直接指出最终的目标)
    // watch(
    //   () => people.job,
    //   (newValue, oldValue) => {
    //     console.log(`工作信息改变了`, newValue, oldValue)
    //   },
    //   {
    //     deep: true
    //   }
    // )

    return {
      people
    }
  }
}
script>

Vue3快速入门(一)_第3张图片

如果监听器用来监听reactive定义的响应式数据,那么无法获取到旧数据,而且默认开启深度监听,无法关闭深度监听

watchEffect

  • 默认开启了立即更新(immediate: true)
  • 用到谁就监听谁
<template>
  <div class="home">
    <h1>age:{{people.age}}h1>
    <button @click="people.age++">年龄加1button>
    <h1>salary:{{people.job.salary}}h1>
    <button @click="people.job.salary+=100">薪水加100button>
  div>
template>

<script>
import { reactive, watchEffect } from "vue"
export default {
  name: "Home",
  setup() {
    let people = reactive({
      name: "赤蓝紫",
      age: 21,
      job: {
        salary: -10
      }
    })
    watchEffect(() => {
      const salary = people.job.salary
      console.log("工资变更")
    })

    return {
      people
    }
  }
}
script>

3.5 生命周期钩子

Vue3快速入门(一)_第4张图片

vue3中,beforeDestroy改为beforeUnmountdestroyed改为unmounte

beforeCreatd和created没有API,因为setup实际上就相当于这两个生命周期函数

使用示例:

<template>
  <h2>{{num}}h2>
  <button @click="num++">+1button>
template>

<script>
import { onMounted, onUpdated } from "vue"
import { ref } from "vue"

export default {
  name: "Home",
  setup() {
    onMounted(() => {
      console.log("onMounted")
    }),
      onUpdated(() => {
        console.log("数据更新啦")
      })

    let num = ref(0)

    return {
      num
    }
  }
}
script>

3.6 toRef和toRefs

toRef就是把数据变成ref类型的数据,toRefs就是将多个数转换成响应式数据

先引用一下之前的例子:

<template>
  <div class="home">
    <h1>age:{{people.age}}h1>
    <button @click="people.age++">年龄加1button>
    <h1>salary:{{people.job.salary}}h1>
    <button @click="people.job.salary+=100">薪水加100button>
  div>
template>

<script>
import { reactive, watchEffect } from "vue"
export default {
  name: "Home",
  setup() {
    let people = reactive({
      name: "赤蓝紫",
      age: 21,
      job: {
        salary: -10
      }
    })
    watchEffect(() => {
      const salary = people.job.salary
      console.log("工资变更")
    })

    return {
      people
    }
  }
}
script>

仔细观察,可以发现template中,使用了很多次people.,于是想偷一下懒,return的时候耍点小聪明

return {
  age: people.age,
  salary: people.job.salary
}

哦豁,点击按钮不再能改变数据了,原因就是因为return出去的数据不是响应式,而是number,自然不能改变。验证也很简单,只要在watchEffect()中顺便打印出people.age就行了。

通过toRef就可以实现自动修改people里的数据,不要忘记引入toRef

return {
  age: toRef(people, 'age'),
  salary: toRef(people.job, 'salary')
}

这种时候,有可能会想到使用ref就可以了,即以下形式

return {
  age: ref(people.age),		// ref()不会改变people里的数据,而toRef可以修改
  salary: ref(people.job.salary)
}

这样子,乍一看,效果确实一样,但是,实际上的数据并没有发现改变,通过监听器就可以发现

为什么呢?实际上使用ref的话,有类似于new出来一个对象,new出来的对象自然和原来的数据没有什么实质上的联系

使用toRefs就可以稍微偷一下懒

return {
  ...toRefs(people), // 解构,只能解构一层,所以深层的还是要写
  ...toRefs(people.job)
}

4. 其他改变

  • 移除keyCode作为v-on的修饰符

    <input v-on:keyup.13="submit" />
    <input v-on:keyup.enter="submit" />
    
    
    
  • 移除native作为v-on的修饰符

  • 移除filter过滤器

Vue 3 迁移策略笔记

4.1 路由

app.vue

<template>
  <router-view>router-view>
template>
<script>
import { useRoute, useRouter } from "vue-router"

export default {
  setup() {
    const route = useRoute()
    const router = useRouter()

    console.log(route.path)
    router.push("/home")
  }
}
script>

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

router \ index.js

import { createRouter, createWebHashHistory } from "vue-router"


const routes = [
  {
    path: "/home",
    name: "home",
    component: () => import("../components/home.vue")
  },
  {
    path: "/login",
    name: "login",
    component: () => import("../components/login.vue")
  }
]


export default createRouter({
  history: createWebHashHistory(),
  routes
})

注意:

  • 在模板中仍然可以访问 $router$route,所以不需要在 setup 中返回 routerroute
  • vue-router中引入的useRoute,useRouter相当于vue2的 this.$routethis.$router
  • 引入组件时,必须加上.vue后缀

编程式导航传参

<template>
  <router-view>router-view>
template>
<script>
import { useRoute, useRouter } from "vue-router"

export default {
  setup() {
    const route = useRoute()
    const router = useRouter()

    // query编程式导航传参
    // router.push({
    //   path: "/home",
    //   query: {
    //     id: 666
    //   }
    // })

    // params编程式导航传参
    router.push({
      name: "login",		// 需要使用命名路由
      params: {
        // 路由规则那里也要配成path: "/login/:id",
        id: 666
      }
    })
  }
}
script>

4.2 导航守卫

4.2.1 局部导航守卫

home.vue

<template>
  home
template>

<script>
import { onBeforeRouteLeave } from "vue-router"

export default {
  setup() {
    onBeforeRouteLeave((to, from) => {		// 默认可前往,可通过return false禁止前往
      console.log("去", to)
      console.log("来自", from)
    })
  }
}
script>

4.2.2 全局导航守卫

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

router.beforeEach((to, from) => { // next是可选参数,可写可不写,return false是取消导航,否则意味着通过验证
  console.log('去', to)
  console.log('来自', from)
  // return false
})

createApp(App)
  .use(router)
  .mount('#app')

4.3 $refs

<template>
  <button @click="getValue" ref="btn">点击button>
template>

<script>
import { getCurrentInstance, nextTick, reactive, ref } from "vue"

export default {
  setup() {
    const ci = getCurrentInstance()
    const { proxy } = getCurrentInstance()
    // console.log(getCurrentInstance())    //只有在setup()这一直接块才可以,如果在函数中,则会得到null

    function getValue() {
      console.log(ci.refs.btn)
      console.log(proxy.$refs.btn) // 也可以通过proxy.$refs来获取
    }

    return {
      getValue
    }
  }
}
script>

4.4 nextTick

nextTick():在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

<template>
  <button @click="change" ref="btn">{{msg}}button>
template>

<script>
import { getCurrentInstance, nextTick, ref } from "vue"

export default {
  setup() {
    let { proxy } = getCurrentInstance()

    let msg = ref("Hi")
    function change() {
      const btn = proxy.$refs.btn
      msg.value = "Hello"

      console.log("直接打印:", btn.innerText)

      nextTick(() => {
        console.log("nextTick:", btn.innerText)
      })
    }

    return {
      msg,
      change
    }
  }
}
script>

4.5 teleport

使用,可以通过to将teleport下的html传送到指定位置(如传送到body中)。

<template>
  <div class="one">
    <div class="two">
      <teleport to="body">
        <div class="three">
          <div class="four">div>
        div>
      teleport>
    div>
  div>
template>

<script>
export default {
  name: "App"
}
script>

Vue3快速入门(一)_第5张图片

5. 法宝(setup语法糖)

Vue3.0通过setup()函数,需要把数据和方法return出去才能使用,但是Vue3.2中,只需要在srcipt标签上加上setup属性,这样子就无需return,template就可以直接使用了

<template>
  <button @click="alertName">alert namebutton>
  <p>{{ nickname }}p>
template>


<script setup>
  let nickname = "赤蓝紫"

  function alertName() {
    alert(`我是${nickname}`)
  }
script>

参考:

vue3保姆级教程

官方文档

你可能感兴趣的:(Vue,vue.js,vscode,前端)