Vue3 基础知识

  • 创建项目
npm create vue@latest
  • 目录结构

    ├── public/ # 静态资源目录,其中的文件会被复制到 dist 目录下
    │ └── favicon.ico # 示例:网站图标

    ├── src/ # 主要开发目录
    │ ├── assets/ # 静态资源,如图片、字体等
    │ ├── components/ # Vue 组件目录(如果你使用 Vue.js 的话)
    │ ├── App.vue # Vue 应用的根组件(如果是 Vue 项目)
    │ ├── main.js # 应用程序的入口脚本
    │ └── ...

    ├── .gitignore # Git 忽略文件
    ├── index.html # Vite 服务启动时的基本 HTML 模板
    ├── package.json # npm 包配置文件,包含项目依赖和脚本任务
    ├── vite.config.js # Vite 配置文件,用于自定义 Vite 的行为
    └── README.md # 项目说明文件(通常是 README 或其他文档)

OptionsAPI 和 compositionAPI

  • OptionsAPI(Vue2 是选项式 API)

    • OptionsAPI 的数据、方法、计算属性是分散在 data、methods、computed 等选项中的,若想新增或者修改一个需求,就需要分别修改 data、methods、computed,不便于维护和复用。
  • compositionAPI(Vue3 是组合式 API)

    • 可以用函数的方式组织代码,让相关功能的代码更加有序的组织在一起,让代码更加清晰易懂。

setup

  • setup 函数是 Vue3 中一个新的配置项,值是一个函数。

注意

  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

  • 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

  • toRefs 可以将 reactive 定义的对象,转换为普通对象,该普通对象的每个属性都是 ref 类型。
import { reactive, toRefs } from "vue";

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

// toRefs的作用就是把一个reactive所定义的对象,变成一个由n个ref所定义的对象
let { name, age } = toRefs(user);
  • toRef 可以将 reactive 定义的对象中的某个属性,转换为 ref 类型。
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

  • 监听数据的变化。

  • Vue3 中的 watch 只能监视以下四种数据

    1. ref 所定义的数据。
    2. reactive 所定义的数据。
    3. 函数返回一个值(getter函数)。
    4. 一个包含上述内容的数组。
  • 场景 1:监听 ref 定义的基本类型的数据。

  <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>
  • 场景 2:监听 ref 定义的对象类型的数据。
  <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>
  • 场景 3:监听 reactive 定义的对象类型的数据。
  <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>
  • 场景 4:监听 ref 或 reactive 定义的对象类型的数据的某个属性。
  <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

  • 当有数据变化时,会自动监听变化,并执行回调函数
    watchwatchEffect 两者区别:
    1. watch 需要手动指定监听的数据。
    2. 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 属性

  • 给标签添加 ref 属性,可以获取到标签的 DOM 元素
  • 给组件标签添加 ref 属性,可以获取到组件实例对象
    注意
    1. 在父组件中获取子组件的实例对象,需要在子组件中通过defineExpose({}) 暴露出来。
<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. 创建
  • setup 函数
    setup 函数代替了 vue2 中的 beforeCreatecreated
  1. 挂载
  • onBeforeMount
import { onBeforeMount } from "vue";

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

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

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

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

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

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

自定义 hooks

  1. 什么是自定义 hooks?
  • hook 本质就是一个函数,一般放在 src 下的 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 开头,后面是功能的描述。

路由

  • vue3 中的路由使用
  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>

命名路由

  • 就是在路由配置时,给路由添加个 name 的属性
    注意:其他使用方法与 vue2 一致
// 创建并暴露路由器

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 模式

  • 只需要在跳转的时候加上replace
<RouterLink replace to="/about">关于我们</RouterLink>

编程式路由

  • 编程式路由就是通过 js 代码实现路由跳转
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

  • 父组件向子组件传值

    1. 父组件通过标签的属性将需要传递的数据传递给子数据。
  • 子组件向父组件传值

    1. 父组件先向子组件传递一个方法
    2. 子组件通过调用该方法将参数传递给父组件
    3. 父组件就可以在这个方法的参数中获取到子组件传递过来的数据
<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

  • 适用于父组件向孙子组件传递数据
  • 只需要在儿子组件中使用 v-bind="$attrs" 接收父组件传递过来的数据并传递下去
<Grandson v-bind="$attrs" />

refs 和parent

  • $refs 适用于父传子
  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>
  • $parent
  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

  • 用来保护数据,使其变为只读的,避免被修改
  • readonly常用,因为shallowReadonly只能处理浅层次的只读
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容