Vue3 基础知识

2024-04-06  本文已影响0人  我没叫阿
npm create vue@latest

OptionsAPI 和 compositionAPI

setup

注意

  1. setup 函数中 this 指向 undefined。
  2. 如果定义的数据不修改,可以直接定义成普通数据。如果定义的数据需要修改,那么必须定义成响应式,否则数据虽然修改了,但是页面不会改变。
  3. vue3 模板中可以使用 data 中的数据和 methods 中的方法,也可以使用 setup 中的数据和方法(不推荐)。
  4. data 中可以通过 this.name 读取到 setup 中的数据.反之 setup 中不能读取 data 中的数据(不推荐)。
<script lang="ts">
  export default {
    name: 'Person',
    setup() {
      // 定义普通数据
      let name = "张三";
      let tel = "13888888888";

      // 定义方法
      function changeName() {
        name.value = "李四";
      }
      function showTel() {
        alert(tel);
      }

      // 返回数据
      return {
        name,
        changeName,
        showTel,
    };
    }
  }
</script>

注意

  1. 语法糖写法:直接在标签中写入 setup,并且标签内的数据会自动 return
  2. 需要要注意的是如果给组件重新命名需要单独写个 script 标签,并指定 name
  3. 也可以通过安装插件来解决(详细配置请百度)npm i vite-plugin-vue-setup-extend -D
  4. 工作中组件名称和文件名称一般保持一致,所以可以省略 name 属性。
<script setup lang="ts" name="Person">
  ...
</script>

ref 和 reactive

<script setup lang="ts" name="Person">
  import { ref , reactive } from "vue";

  // 用ref定义【基本类型】响应式数据
  let name = ref("孙悟空");

  // 用reactive定义【对象类型】响应式数据(只能定义对象类型)
  let user = reactive({
      name: "猪八戒",
      age: 18,
  })

  // 用ref定义【对象类型】响应式数据
  let car = ref({
      name:'宝马',
      price:'40W'
  })

  // 定义普通数据
  let tel = "13888888888";

  // 定义方法
  function changeName() {
    // 当用ref包裹数据后,name就成了一个ref对象,所以需要使用name.value来读取值
    name.value = "李四";
  }

  function showTel() {
    alert(tel);
  }

  function changeUserName() {
    user.name = "沙和尚";
  }

  function changeCarName() {
    car.value.name = "奔驰";
  }


</script>

注意

  1. ref 内部是基于 reactive 实现的。
  2. ref 定义的基本类型数据,在模版中必须使用 .value 的方式读取和修改数据。reactive不需要。
  3. reactive重新分配一个新对象会失去响应式,可以使用 Object.assign()来解决。
let obj = reactive({ name: "张三", age: 19 });

function changeObj() {
  obj = { name: "李四", age: 20 }; // 无法修改(页面不更新)
  obj = Object.assign(obj, { name: "李四", age: 20 }); // 可以修改
}

使用原则

  1. 若需要一个基本类型的响应式数据,必须使用 ref。
  2. 若需要一个响应式对象,层级不深,ref、reactive 都可以。
  3. 若需要一个响应式对象,且层级较深,推荐使用 reactive。

toRefs 和 toRef

import { reactive, toRefs } from "vue";

let user = reactive({
  name: "张三",
  age: 10,
});

// toRefs的作用就是把一个reactive所定义的对象,变成一个由n个ref所定义的对象
let { name, age } = toRefs(user);
import { reactive, toRef } from "vue";

let user = reactive({
  name: "张三",
  age: 10,
});

let name = toRef(user, "name");

computed

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

  let fname = ref("张");
  let lname = ref("三");

  // 这样定义的fullName是一个计算属性,但是是一个只读的计算属性
  let fullName = computed(() => {
    return fname.value + lname.value;
  })

  // 这样定义的fullName是一个可读可写的计算属性
  let fullName = computed({
    get() {
      return fname.value + lname.value;
    },
    set(value) {
      let arr = value.split(" ");
      fname.value = arr[0];
      lname.value = arr[1];
    }
  })

  // 计算属性返回的也是一个ref类型的数据
  // 所以需要经过 .value 才能获取到值
  function changeFullName(){
    fullName.value = "李-四";
  }
  </script>

watch

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

  let sum = ref(0);

  // 参数:监听对象 回调函数(newValue, oldValue)
  // watch的返回值是一个函数
  const stopWatch = watch(sum, (newValue, oldValue) => {
    console.log(newValue, oldValue);
    if (newValue >= 10) {
      // 停止监听
      stopWatch();
    }
  });
  </script>
  <script lang="ts" setup name="Person">
  import { ref, watch } from "vue";

  let user = ref({
    name: "张三",
    age: 18,
  });

  // 参数:监听对象 回调函数(newValue, oldValue) 配置对象
  // 监听对象时,所监听的是对象的地址,如果需要监听对象内部某个值的改变需要开启第三个参数
  // deep 深度监视
  // immediate 立即执行(一进来先执行一下)
  watch(
    person,
    (newVal, oldVal) => {
      console.log(newVal, oldVal);
    },
    { deep: true, immediate: true }
  );
  </script>
  <script lang="ts" setup name="Person">
  import { reactive, watch } from "vue";

  let user = reactive({
    name: "张三",
    age: 18,
  });

  // 参数:监听对象 回调函数(newValue, oldValue)
  // 默认开启深度监视,且无法关闭
  watch(
    person,
    (newVal, oldVal) => {
      console.log(newVal, oldVal);
    },
    { deep: true, immediate: true }
  );
  </script>
  <script lang="ts" setup name="Person">
  import { ref, reactive, watch } from "vue";

  let user = reactive({
    name: "张三",
    age: 18,
    car: {
      c1: "奔驰",
      c2: "宝马",
    },
  });

  // 监听reactive定义的对象中的某个属性,watch的第一个参数需要换成一个函数,这个函数返回一个值,这个值就是要监听的属性,如果监听的这个值是一个对象形式,可以手动开启深度监听deep:true
  watch(
    () => user.name,
    (newVal, oldVal) => {
      console.log("newVal", newVal);
      console.log("oldVal", oldVal);
    }
  );
  watch(
    () => user.car,
    (newVal, oldVal) => {
      console.log("newVal", newVal);
      console.log("oldVal", oldVal);
    },
    { deep: true }
  );

  // 监听多个属性
  watch(
    [() => user.name, () => user.car.c1]
    (newVal, oldVal) => {
      console.log("newVal", newVal);
      console.log("oldVal", oldVal);
    },
    { deep: true }
  );
  </script>

watchEffect

import { ref, watchEffect } from "vue";

let width = ref(0);
let height = ref(0);

watchEffect(() => {
  if (width.value > 100 || height.value > 100) {
    console.log("干点啥...");
  }
});

标签的 ref 属性

<h1>标签的ref属性</h1>
import { ref, defineExpose } from "vue";

let title = ref();

function getDom() {
  console.log(title.value);
}

v-ifv-show的区别

  1. v-if是动态地创建或移除 DOM 元素。
  2. v-show是通过设置 CSS 属性 display 来显示或隐藏元素。

生命周期

  1. 创建
  1. 挂载
import { onBeforeMount } from "vue";

onBeforeMount(() => {
  console.log("挂载前");
});
import { onMounted } from "vue";

onMounted(() => {
  console.log("挂载完毕");
});
  1. 更新
import { onBeforeUpdate } from "vue";

onBeforeUpdate(() => {
  console.log("更新前");
});
import { onUpdated } from "vue";

onUpdated(() => {
  console.log("更新完毕");
});
  1. 卸载
import { onBeforeUnmount } from "vue";

onBeforeUnmount(() => {
  console.log("卸载前");
});
import { onUnmounted } from "vue";

onUnmounted(() => {
  console.log("卸载完毕");
});

自定义 hooks

  1. 什么是自定义 hooks?
  1. 为什么使用自定义 hooks?

    • 可以将重复的逻辑或者同一个模块的功能进行封装,使代码更加模块化,便于维护。
  2. 如何创建自定义 hooks?

import { ref, reactive } from "vue";

export default function useUser() {
  let userId = ref("001");

  async function getUserInfo() {
    let res = await fetch("http://localhost:3000/data");
  }

  return { userId, getUserInfo };
}
  1. 如何使用?
<h1>{{ userId }}</h1>
<button @click="getUserInfo">获取用户信息</button>
import useUser from "./hooks/useUser";

const { userId, getUserInfo } = useUser();

注意

  1. hooks 的命名一般以 use 开头,后面是功能的描述。

路由

  1. 安装
npm i vue-router
  1. main.ts中引入
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

const app = createApp(App);
app.use(router);
app.mount("#app");
  1. 创建路由文件
import { createRouter, createWebHistory } from "vue-router";
import Home from "../pages/Home.vue";
import News from "../pages/News.vue";
import About from "../pages/About.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/home",
      component: Home,
    },
    {
      path: "/news",
      component: News,
    },
    {
      path: "/about",
      component: About,
    },
  ],
});
export default router;
  1. 在页面中使用
<RouterLink to="/home">首页</RouterLink>

<RouterView></RouterView>

注意
在 vue3 中,原本 vue2 中的<router-link/><router-view>变成了<RouterLink><RouterView>

命名路由

// 创建并暴露路由器

import { createRouter, createWebHistory } from "vue-router";
import Home from "../pages/Home.vue";
import News from "../pages/News.vue";
import About from "../pages/About.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      name:'home',
      path: "/home",
      component: Home,
    },
    ...
  ],
});

export default router;

嵌套路由

import { createRouter, createWebHistory } from "vue-router";
import Home from "../pages/Home.vue";
import News from "../pages/News.vue";
import Detail from "../pages/Detail.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      name: "home",
      path: "/home",
      component: Home,
    },
    {
      name: "news",
      path: "/news",
      component: News,
      children: [
        {
          name: "detail",
          path: "detail",
          component: Detail,
        },
      ],
    },
  ],
});

export default router;

路由传参

  1. query 传参
    优点:不需要修改路由文件
    缺点:html 代码稍微复杂
<RouterLink
  :to="{
          // 如果是name,可以利用命名路由时定义的名字来简写
          name: 'detail',
          // 如果写path,需要将路由写完整
          path: '/news/detail',
          query: { title:news.title,content:news.content},
        }"
>
  新闻详情
</RouterLink>
<script lang="ts" setup name="Detail">
  import {useRoute} from "vue-router"; const route = useRoute();
  console.log(route.query.title) console.log(route.query.content)
</script>
  1. params 传参
    优点:html 代码风格简洁
    缺点:需要更改路由文件
    注意 1:路由跳转必须使用命名路由时起的 name
    注意 2:在路由匹配规则后面增加问号,表示该参数可传可不传
// 创建并暴露路由器

import { createRouter, createWebHistory } from "vue-router";
import News from "../pages/News.vue";
import Detail from "../pages/Detail.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      name: "news",
      path: "/news",
      component: News,
      children: [
        {
          name: "detail",
          path: "detail/:title/:content?",
          component: Detail,
        },
      ],
    },
  ],
});

export default router;
<RouterLink :to="`/news/detail/${news.title}/${news.content}`">
  新闻详情
</RouterLink>

<RouterLink
  :to="{
          name: 'detail',
          params: { title:news.title,content:news.content},
        }"
>
  新闻详情
</RouterLink>
<script lang="ts" setup name="Detail">
  import {useRoute} from "vue-router"; const route = useRoute();
  console.log(route.params.title) console.log(route.params.content)
</script>
  1. 路由的 props 配置
    注意:需要在路由文件中配置 props 属性,不同传参方式配置也不相同,但是接收参数方式都是用 defineProps
<template>
  <h1>{{ title }}</h1>
  <p>{{ content }}</p>
</template>

<script lang="ts" setup name="Detail">
  // 利用props传参 + defineProps收参,实现优雅写法
  defineProps(["title", "content"]);
</script>
import { createRouter, createWebHistory } from "vue-router";
import News from "../pages/News.vue";
import Detail from "../pages/Detail.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      name: "news",
      path: "/news",
      component: News,
      children: [
        {
          name: "detail",
          path: "detail",
          component: Detail,
          // 写法1:这种自定义写法适合query传参
          props(route) {
            return route.query;
          },
          // 写法2:这种写法适合params传参
          props: true,
        },
      ],
    },
  ],
});

export default router;

replace 模式

<RouterLink replace to="/about">关于我们</RouterLink>

编程式路由

import { onMounted } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();

onMounted(() => {
  setTimeout(() => {
    router.push({
      name: "about",
    });
  }, 5000);
});

重定向

import { createRouter, createWebHistory } from "vue-router";
import Home from "../pages/Home.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/",
      redirect: "/home",
    },
    {
      name: "home",
      path: "/home",
      component: Home,
    },
  ],
});

export default router;

Pinia

  1. 安装
npm i pinia
  1. 在 main.ts 中引入
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.use(router);
app.mount("#app");
  1. 在 src 下建立 store 文件夹并在其下创建与页面对应的文件
    例如:我有一个 User.vue 的组件,就创建一个 user.ts 的文件。需要注意的是不需要每个.vue 文件都在 store 文件夹下创建文件,只是哪些组件需要状态管理,就新建对应的 ts 文件。

3.1 user.ts 文件

import { defineStore } from "pinia";
import axios from "axios";
import { pinyin } from "pinyin-pro";

export const useUserStore = defineStore("user", {
  // 存储数据的地方
  state() {
    return {
      name: "刘洪涛",
      age: 9,
      tel: "1388888888",
      data: [
        {
          id: "d001",
          content: "一句话",
        },
      ],
    };
  },
  // actions里存储动作函数(修改数据的方法)
  // 注意:actions里的this指的是state
  actions: {
    updateTel(tel: string) {
      console.log("updateTelupdateTel", tel);
      this.tel = tel;
    },
    async getSentence() {
      const res = await axios.get(
        "https://api.uomg.com/api/rand.qinghua?format=json"
      );
      console.log(res.data.content);
      this.data.unshift({
        id: new Date().getTime() + "",
        content: res.data.content,
      });
    },
  },
  getters: {
    pinyinName(state) {
      return pinyin(state.name);
    },
  },
});

3.2 User.vue 文件

<template>
  <div class="count">
    <h2>用户信息</h2>
    <p>姓名:{{ name }} / {{ pinyinName }}</p>
    <p>年龄:{{ age }}</p>
    <p>手机号:{{ tel }}</p>
    <p>
      <button @click="increase">加一岁</button>
      <button @click="changeName">改名</button>
      <button @click="changeUserInfo">修改全部信息</button>
      <button @click="changeTel">修改手机号</button>
    </p>
    <div class="centenes">
      <ul>
        <li v-for="d in data" :key="d.id">{{ d.content }}</li>
      </ul>
      <button @click="getSentence">获取一句话</button>
    </div>
  </div>
</template>

<script lang="ts" setup name="CountPage">
import { storeToRefs } from "pinia";
import { useUserStore } from "@/store/user";
const userStore = useUserStore();
const { name, age, tel, data, pinyinName } = storeToRefs(userStore);

// 修改数据的第一种方式
function changeName() {
  name.value = "红桃六";
}
function increase() {
  age.value++;
}

// 修改数据的第二种方式(批量修改)
function changeUserInfo() {
  userStore.$patch({
    name: "孙悟空",
    age: 500,
    tel: "12345678910",
  });
}

// 修改数据的第三种方式(利用actions)
function changeTel() {
  userStore.updateTel("010-67689999");
}

// 通过actions里的方法请求接口,获取数据
function getSentence() {
  userStore.getSentence();
}

// $subscribe 监听pinia里的数据修改
userStore.$subscribe((mutate, state) => {
  console.log(mutate, state);
});
</script>

组件通信

props

<template>
  <Person :personList="personList" :sendToy="sendToy" />
</template>
// 利用父组件的传递方法来接收子组件传递的数据
sendToy(){
  console.log(data);
}
  1. 子组件通过defineProps来接收。
<template>
  <button @click="sendToy('奥特曼')">把玩具传给父组件</button>
</template>
import { defineProps, withDefaults } from "vue";

// 方式一:只接收
defineProps(["personList", "sendToy"]);

// 方式二:只接收 + 限制类型
defineProps<{ personList: Person[] }>();

// 方式三:只接收 + 限制类型 + 设置默认值 + 设置可选
withDefaults(defineProps<{ personList?: Person[] }>(), {
  personList: () => [[{ id: "9", name: "孙悟空", age: 500 }]],
});

// 方式四:defineProps有返回值,值为所有传递过来的数据
const props = defineProps(["personList"]);

自定义事件

注意 1:在 vue 中给方法传递事件对象 要用 $event
注意 2:当自定义事件由多个单词组成 推荐使用 kebab-case 肉串命名

  1. 在父组件中定义一个自定义事件
<template>
  <Child @send-toy="getChildSendData" />
</template>
function getChildSendData(data) {
  console.log("拿到子组件传递过来的数据", data);
}
  1. 在子组件中拿到自定义事件并调用
<button @click="emit('send-toy','奥特曼')">把玩具传给父组件</button>
import {(onMounted, defineEmits)} from "vue";

// 声明一个事件
const emit = defineEmits(["send"]);

mitt

  1. 安装
npm install mitt
  1. 在 src 下新建一个 utils 文件夹,在 utils 文件夹下新建一个 emitter.ts 文件
import mitt from "mitt";
// 调用mitt得到emitter,emitter能绑定和触发事件
const emitter = mitt();
export default emitter;
  1. 在 main.ts 中引入
import emitter from "@/utils/emitter";
  1. 在传递数据的组件中
<template>
  <h1>about内容</h1>
  <button @click="sendToy">向其他组件传递数据</button>
</template>

<script setup lang="ts" name="About">
import emitter from "@/utils/emitter";

function sendToy() {
  // 给emitter绑定自定义事件
  emitter.emit("send-toy", "小黄鸭");
}
</script>
  1. 在接收数据的组件中
<template>
  <div class="title">我有一个玩具是 {{ toy }}</div>
</template>

<script setup lang="ts" name="Header">
import emitter from "@/utils/emitter";
import { ref, onUnmounted } from "vue";

let toy = ref("奥特曼");

emitter.on("send-toy", (value: any) => {
  toy.value = value;
});

// 建议:组件卸载时解绑自定义事件
onUnmounted(() => {
  emitter.off("send-toy");
});
</script>

$attrs

<Grandson v-bind="$attrs" />

refs 和parent

  1. 父组件
<template>
  <button @click="getAllSon($refs)">获取素所有子组件实例</button>
  <Son1 ref="son1" />
  <Son2 ref="son2" />
</template>

<script lang="ts" name="Father" setup>
import Son1 from "./Son1.vue";
import Son2 from "./Son2.vue";

function getAllSon(refs) {
  // refs 就是拿到的所有子组件的实力对象,可以用来修改子组件的数据
  // 注意在使用refs获取子组件实例对象的时候,必须要给子组件增加ref属性
  console.log(refs);
}
</script>
  1. 子组件
<template>
  <div class="son1">...</div>
</template>

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

let toy = ref("嘎嘎");
let books = ref(3);

// 一定要将需要修改的数据通过 defineExpose 暴露出去
defineExpose({ toy, books });
</script>
  1. 子组件
<template>
  <button @click="getFather($parent)">获取父组件的数据</button>
</template>

<script lang="ts" name="Son1" setup>
function getFather(parent) {
  // 可以拿到父组件的数据和方法
  console.log(parent);
}
</script>
  1. 父组件
<script lang="ts" name="Father" setup>
// 父组件也需要将需要修改的数据通过 defineExpose 暴露出去
defineExpose({ house });
</script>

provide 和 inject

  1. 父组件
<script lang="ts" name="Father" setup>
import { ref, reactive, provide } from "vue";

let car = reactive({ name: "奔驰", price: "40W" });
let name = ref("张老三");

// 调用 provide 函数,向后代提供数据
provide("name", name.value);
provide("car", car);
</script>
  1. 后代组件
<script lang="ts" name="Son1" setup>
import { ref, inject } from "vue";

const fatherName = inject("name");
</script>

总结:组件间数据通信有很多种方式,实际开发中根据实际情况来选择用哪种方式实现。利用 v-model 也可以父子组件之间传递数据,但实际开发中用到的地方不多。

其他 API

shallowRef 和 shallowReactive

readonly 和 shallowReadonly

上一篇 下一篇

猜你喜欢

热点阅读