入坑 Vue3

最近更新于 2024-05-05 23:48

前言

2024.4.24

月中的时候大概看了下 Spring Boot,感觉干不了啥,接着过了下 Node.js 基础,对 js 的基础语法了解了点,再准备过渡到前端,了解一下 Vue(Vue 的依赖框架创建都是基于 Node.js 来完成)。

Vue 的官方文档挺友好的,有多国语言,包含中文文档:https://cn.vuejs.org/guide/quick-start.html

.vue 文件其实是 html + css + js 的混合,三者可以写到同一个文件中。script 标签内写 js,template 标签内写 html,style 标签内写 css。


2024.4.30

前面 Node.js 搞了点基础,就转 Vue 玩了一下,然后又回去深挖 Node.js,已经实践了一下 ejs 模板引擎使用(服务器渲染前端)、数据库连接、RESTful API,以及 cookie 和 session id 的设置(简单模拟登录保持)。用接口测试工具研究太麻烦,要是用 ejs 渲染前端更显得麻烦,还是得回来学习专用的前端框架,组合后端接口一起玩。
我是实践了一下模板语法、条件渲染、列表渲染再回去挖 Node.js 的,在 Node.js 中实践 ejs 模板引擎部分就很容易联想到 Vue 的模板语法和渲染这套,非常相似,在 Vue 中只是用,通过 ejs 使用就大概能理解 Vue 这套模板语法和渲染的实现原理了,一下通透了。不过用 ejs 很多东西要亲自写,而 Vue 则已经包装好了,可以节省不少功夫。

环境

Node.js 20.12.2
Vue 3.4.21

开始

创建项目

npm create vue@latest

模板语法

file

main.js

import { createApp } from 'vue'
import App from './App.vue' // 导入名称可以随意命名,比如写 import MyApp,后面就用 MyApp 就行

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

文本插值

相当于取变量中的值,使用双大括号

App.vue

<template>
  <h1>模板语法</h1>
  <p>msg: {{ msg }}</p>
  <p>number: {{ number * 2 }}</p>
  <p>ok: {{ ok ? 'yes' : 'no' }}</p>
</template>

<script setup>
const msg = 'hello world'
const number = 10
const ok = true
</script>

file

原始 HTML

相当于在变量中保存 html 代码,并且取出来按照 html 解析,使用 v-html 指令

App.vue

<template>
  <span v-html="homePage"></span>
  <p v-html="blog"></p>
</template>

<script setup>
const homePage = '<a href="https://iyatt.com">主页</a>'
const blog = '<a href="https://blog.iyatt.com">博客</a>'
</script>

file

属性绑定

相当于属性可以通过变量指定,使用 v-bind 指令。

App.vue

<template>
  <div v-bind:id="dynamicID1"></div>
  <div :id="dynamicID2"></div> <!-- 简写 -->

  <!-- 3.4 版本开始支持
      如果属性名称与绑定的变量名称相同可以简写 -->
  <div :id></div>
  <div v-bind:id></div>
</template>

<script setup>
const dynamicID1 = 'testID1';
const dynamicID2 = 'testID2';
const id = 'testID3';
</script>

file

布尔型属性

属性值为空或未定义时会自动取消属性
App.vue

<template>
  <div :attribute1 :attribute2 :attribute3 :attribute4 :attribute5>1</div>
</template>

<script setup>
const attribute1 = 'test';
const attribute2 = null;
const attribute3 = undefined;
const attribute4 = true;
const attribute5 = 10;
</script>

file

一次绑定多个属性

<template>
  <div v-bind="attributes">测试</div>
</template>

<script setup>
const attributes = {
    class: 'testClass',
    id: 'testID'
}
</script>

file

条件渲染

v-if v-else

App.vue

<template>
  <button @click="status = !status">切换</button>

  <h1 v-if="status">现在是打开状态</h1>
  <h1 v-else>Oh no 😢</h1>
</template>

<script setup>
import { ref } from 'vue';

const status = ref(true); // 使用 ref 可以动态响应
</script>

file

file

v-else-if

App.vue

<template>
  <div v-if="value === 'A'">AAA</div>
  <div v-else-if="value === 'B'">BBB</div>
  <div v-else-if="value === 'C'">CCC</div>
  <div v-else>DDD</div>
</template>

<script setup>
const value = "C";
</script>

file

template 多元素条件

将多个元素放进一个包装器元素里,一起接收条件控制
App.vue

<template>
  <button @click="status = !status">切换</button>

  <template v-if="status">
      <p> on </p>
      <p> 你好 </p>
  </template>
  <template v-else>
      <p> off </p>    
      <p> 🐕 </p>
      <p> 🐱 </p>
  </template>
</template>

<script setup>
import { ref } from 'vue';

const status = ref(true);
</script>

file

file

v-show

v-show 仅切换 display 属性

App.vue

<template>
  <button @click="status = !status">切换</button>
  <h1 v-show="status">Hello</h1>
</template>

<script setup>
import { ref } from 'vue';

const status = ref(true)
</script>

file

file

v-if 和 v-show 比较:https://cn.vuejs.org/guide/essentials/conditional.html#v-if-vs-v-show

列表渲染 v-for

数组

App.vue

<template>
  <li v-for="item in items">
      {{ item.msg }}
  </li>

  <hr>

  <!-- 解构 -->
  <li v-for="{ msg } in items">
      {{ msg }}
  </li>

  <hr>

  <!-- 添加索引 -->
  <li v-for="({ msg }, index) in items">
      {{ msg }} {{ index }}
  </li>

</template>

<!-- 组合式 API -->
<script setup>
import { ref } from 'vue';

const items = ref([
          { msg: "第一条消息"},
          { msg: "第二条消息"},
          { msg: "第三条消息"}
      ]);
</script>

file

遍历时用的 in 可以换为 of,一样的效果

对象

<template>
  <ul>
      <li v-for="value in object">
          {{ value }}
      </li>

      <hr>

      <li v-for="(value, key) in object">
          {{ key }}: {{ value }}
      </li>

      <hr>

      <li v-for="(value, key, index) in object">
          {{ index }} {{ key }}: {{ value }}
      </li>
  </ul>
</template>

<script setup>
const object = {
          title: "How  to do lists in vue",
          author: "Jane Doe",
          publishedAt: "2016-04-10"
      };
</script>

file

响应式数据

直接定义的数据在页面中被使用,如果中间修改了数据值,页面上使用数据部分不会更新为最新的值。响应式数据则支持使用的页面自动更新为最新的值,定义响应式数据使用 Vue 中的 ref 和 reactive,后者不支持基本数据类型,前者本身只支持基本类型,但又能间接在内部使用 reactive 来支持对象类型。一般建议是直接使用 ref,除非表单多层级深的才直接用 reactive。

ref 对象

App.vue

<script setup lang="ts">
import { ref } from 'vue';

const value1 = ref('hello')
const value2 = 'hello'

console.log(value1)
console.log(value2)
</script>

可以看到控制台打印的信息,value1 是一个 RefImpl 的对象,其中 value 属性保存着值
file

ref 使用

ref 对象的 value 属性中存储着变量值,在模板语法中是可以直接使用变量名,Vue 会自动取 .value,但是在 js 部分使用还是需要使用 .value 来操作。

<script setup lang="ts">
import { ref } from 'vue';

let num1 = ref(0)
let num2 = 0

// 在方法中,需要手动使用 .value
function fun1() {
    num1.value++
    console.log('num1: ', num1.value)
}

function fun2() {
    num2++
    console.log('num2: ', num2)
}
</script>

<template>
    <div>
        <!-- 模板语法中可以自动取 .value -->
        {{ num1 }} 
        <button @click="fun1">num1++</button>
    </div>

    <div>
        {{ num2 }}
        <button @click="fun2">num2++</button>
    </div>
</template>

两个按扭各自点击了三次,num1 是响应式数据,页面引用值也更新了。num2 则是普通变量,页面上没有更新,但是从控制台打印的值可以看到实际是修改了的。
file

reactive 对象

App.vue

<script setup lang="ts">
import { reactive } from 'vue';

let value1 = reactive({
    name: '小强',
    age: 18
});

let value2 = reactive([
    { name: '小明', age: 18 },
    { name: '小红', age: 19 },
    { name: '小强', age: 20 }
])

console.log(value1);
console.log(value2);
</script>

从控制台打印的信息可以看到返回的是一个 Proxy 对象(JS 内置),其中的 Target 存储着数据。
file

reactive 使用

App.vue

<script setup lang="ts">
import { reactive } from 'vue';

let value1 = reactive({
    commodity: '汽车',
    price: 10000
});

function add() {
    value1.price += 1000;
}
</script>

<template>
<div>
    {{ value1.commodity }} - {{ value1.price }}
    <button @click="add">加价</button>
</div>
</template>

file

file

ref 定义非基本数据类型

使用 ref 定义的响应式数据在 js 中都需要通过 .value 来操作,而在模板语法中是可以直接使用的
App.vue

<script setup lang="ts">
import { ref } from 'vue';

let value1 = ref({
    commodity: '汽车',
    price: 10000
});

console.log(value1);

function add() {
    value1.value.price += 1000;
}
</script>

<template>
<div>
    {{ value1.commodity }} - {{ value1.price }}
    <button @click="add">加价 1000</button>
</div>
</template>

从打印的控制台输出可以看到,ref 定义的对象值也是存储在 value 属性中,而且 value 属性值是一个 Proxy 对象,也就是说实际实现也是使用了 reactive 来完成
file

解构

App.vue

<script setup lang="ts">
import { reactive, toRef, toRefs } from 'vue';

let value1 = reactive({
    commodity: '汽车',
    price: 10000
});

let { commodity, price }= toRefs(value1); // 解构多个属性
let price1 = toRef(value1, 'price'); // 解构单个属性

function add() {
    price1.value += 1000;
}
</script>

<template>
<div>
    {{ value1.price }} {{ price }} {{ price1 }}
    <button @click="add">加价 1000</button>
</div>
</template>

如果直接解构,得到的新变量其实是一份拷贝的数据,相当于是深拷贝的。对解构的变量修改,不会修改原对象。而使用 toRefs 或 toRef 进行解构,相当于浅拷贝,拿到的是原对象的属性地址,对解构变量修改,原对象的属性同步修改。
file

file

计算属性

App.vue

<script setup lang="ts">
import { computed, ref } from 'vue';

let value1 = ref(1)

let result1 = computed(() => {
    return value1.value ** 2
})

function addValue1() {
    ++value1.value
}
</script>

<template>
    <input v-model="value1" type="number" />
    <button @click="addValue1">++value1</button>
    {{ result1 }}
</template>

计算属性像是一个函数,可以执行一些指定操作,但是与函数不同。计算属性依赖的变量发生变化的时候,会自动调用执行,而且有缓存,即使多次访问计算属性,只要依赖的变量没有修改就不会重新执行,直接沿用前面的计算结果。
file

watch 监视

watch 是监视地址的变化,变量引用地址变化的才可以监视到。

对应文档中侦听器部分:https://cn.vuejs.org/guide/essentials/watchers.html

ref 定义的基本类型

App.vue

<script setup lang="ts">
import { ref, watch } from 'vue';

let value = ref(0)

function add() {
    ++value.value
}

// 监视 value
// 可以获取到新值和旧值
const stopWatch = watch(value, (newValue, oldValue) => {
    if (newValue > 5) {
        stopWatch() // watch 的返回值是一个函数,调用它可以停止监视
    }
    console.log(`newValue: ${newValue}, oldValue: ${oldValue}`)
})
</script>

<template>
    {{ value }}
    <button @click="add">add</button>
</template>

在值大于 5 后停止监视了,控制台也不再打印了
file

ref 定义的对象

<script setup lang="ts">
import { ref, watch } from 'vue';

let person = ref({
    name: '张三',
    age: 18
})

function changeName() {
    person.value.name += '*'
}

function changeAge() {
    ++person.value.age
}

function changePerson() {
    person.value = {
        name: '李四',
        age: 20
    }
}

// // 情况一
// // 只监听 person 对象地址是否修改
watch(person, (newVal, oldVal) => {
    console.log(newVal, ' ', oldVal)
})

// // 情况二
// // 深度监听
// // 可以监听到 person 对象内部属性的变化
// // 但是在内部属性变化的时候会出现新旧值相同的情况
// // 这是因为新旧值变化实际只是修改了值,并没有修改地址
// // 监视发现变化后是去新旧值的地址去取值,新旧值是同一个地址,实际就是都是新值
// watch(person, (newVal, oldVal) => {
//     console.log(newVal, ' ', oldVal)
// }, {
//     deep: true
// })

// // 情况三
// // 深度监听 + 立即执行
// // 同情况二
// // 只是立即执行,在首次赋值时就会执行一次
// watch(person, (newVal, oldVal) => {
//     console.log(newVal, ' ', oldVal)
// }, {
//     deep: true,
//     immediate: true
// })
</script>

<template>
    <div>
        <p>姓名:{{ person.name }}</p>
        <p>年龄:{{ person.age }}</p>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改整个对象</button>
    </div>
</template>

情况一
file
file

情况二
file

情况三
file

reactive 定义的对象

App.vue

<script setup lang="ts">
import { reactive, watch } from 'vue';

let person = reactive({
    name: '张三',
    age: 18
})

function changeName() {
    person.name += '*'
}

function changeAge() {
    ++person.age
}

function changePerson() {
    person = Object.assign(person, {
        name: '李四',
        age: 20
    })
}

// // 情况一
// // 默认深度监视,person 内属性变化都能监测到,且无法关闭深度监视
// // 同样存在新旧值相同的情况,因为变化只是值变化,新旧值实际是同一块地址
// watch(person, (newVal, oldVal) => {
//     console.log(newVal, ' ', oldVal)
// })

// 情况二
// 深度监听 + 立即执行
// 同情况一
// 只是立即执行,在首次赋值时就会执行一次
watch(person, (newVal, oldVal) => {
    console.log(newVal, ' ', oldVal)
}, {
    immediate: true
})
</script>

<template>
    <div>
        <p>姓名:{{ person.name }}</p>
        <p>年龄:{{ person.age }}</p>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改整个对象</button>
    </div>
</template>

情况一
file

情况二
file

对象的属性

监视的一个对象的属性,如果这个属性是基本类型,就必须用函数形式,如果属性是一个对象,那么可以直接监视也能用函数形式,建议是用函数形式,用函数和不哟个函数形式,在监听属性的属性和属性对象整体上的现象有区别。

App.vue

<script setup lang="ts">
import { reactive, watch } from 'vue';

let person = reactive({
    name: '张三',
    cars: {
        car1: '奔驰',
        car2: '宝马'
    }
})

function changeName() {
    person.name = '李四'
}

function changeCar1() {
    person.cars.car1 = '奥迪'
}

function changeCars() {
    person.cars =  {
        car1: '特斯拉',
        car2: '保时捷'
    }
}

watch(() => person.name, (newVal, oldVal) => {
    console.log(newVal, ' ' , oldVal)
})

watch(() => person.cars, (newVal, oldVal) => {
    console.log(newVal, ' ' , oldVal)
}, {
    deep: true // 加上这个才能监视到属性对象内的变化。只有直接监视 reactive 对象本身的时候才能默认深度监视。
})
</script>

<template>
    <button @click="changeName">修改name</button>
    <button @click="changeCar1">修改car1</button>
    <button @click="changeCars">修改cars</button>
</template>

file

监视多个数据

App.vue

<script setup lang="ts">
import { reactive, watch } from 'vue';

let person = reactive({
    name: '张三',
    cars: {
        car1: '奔驰',
        car2: '宝马'
    }
})

function changeName() {
    person.name = '李四'
}

function changeCar1() {
    person.cars.car1 = '奥迪'
}

function changeCars() {
    person.cars =  {
        car1: '特斯拉',
        car2: '保时捷'
    }
}

// 通过数组可以一次性监视多个
watch([
    () => person.name,
    () => person.cars.car1,
    () => person.cars
], (newVal, oldVal) => {
    console.log(newVal, ' ' , oldVal)
})
</script>

<template>
    <button @click="changeName">修改name</button>
    <button @click="changeCar1">修改car1</button>
    <button @click="changeCars">修改cars</button>
</template>

file

watchEffect 监视

前面的 watch 监视需要指定监视变量,使用 watchEffect 则自动监视变量,只有在回调函数中被使用的变量才会被监视

App.vue

<script setup lang="ts">
import { ref, watchEffect } from 'vue';

let value1  = ref(0);
let value2 = ref({
    a: 0
})

function increment1() {
    value1.value++;
}

function increment2() {
    value2.value.a++;
}

watchEffect(() => {
    if (value1.value === 1 || value2.value.a === 1)
    {
        value1.value = 10000;
        value2.value.a = 10000;
    }
})
</script>

<template>
    {{ value1 }}
    {{ value2.a }}
    <button @click="increment1">+1</button>
    <button @click="increment2">+a</button>
</template>

两个变量任意一个自增到 1 就会都改为 10000
file

TypeScript

2024.5.5
这段时间接触 Node.js 和 Vue3 的过程中,稍微了解了下 TypeScrit。最直观的映像就是在 JavaScript 基础上加了类型,实际执行的时候会把 ts 先编译为 js。区别就在类型上,对于 js 这种动态类型的语言来说,开发时 IDE 的语法提示就比较烂。在用 Python 的时候也有这个问题,基本类型一般没啥问题,要是来点类实例和列表混合,要索引指定下标的时候,代码补全就会摆烂,不过 Python 可以支持类型声明,这样语法提示也能很好的工作。ts 感觉就是弥补这一点,可以添加类型声明,这样 IDE 可以根据声明的类型来推断如何进行代码补全。

下面是使用示例
文件结构

src
|
|---types
|    |
|    |--- index.ts
|
|---App.vue

一般将自定义的类型放在 types 目录下,如果模块名是 index.ts,导入的时候只需要指定所在文件夹名就行

index.ts

// 定义一个接口
export interface Person {
    name: string,
    age: number,
}

// 一个数组类型
export type Persons = Person[]

App.vue

<script setup lang="ts">
// 导入类型需要加关键词 type
// @ 符号表示路径 /src
import type { Person, Persons } from '@/types';

// 通过冒号声明类型

// Person 对象
const p1: Person = {
    name: 'John',
    age: 30,
}

// Person 对象数组
const p2: Persons = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 35}
]

// 数字类型
const value: number = 1
</script>

生命周期

图片来源于官方文档:https://cn.vuejs.org/guide/essentials/lifecycle.html
file

一些属性

ref 属性

prop 属性

入坑 Vue3
Scroll to top