Vue3

作者: h2coder | 来源:发表于2023-09-14 00:33 被阅读0次

    Vue3

    • 2020 年 9 月 18 日发布,许多开发者还在观望
    • 2022 年 2 月 7 日称为默认版本,意味着 vue3 是现在也是未来
    库名称 简介
    ant-design-vue PC 端组件库:Ant Design 的 Vue 实现,开发和服务于企业级后台产品
    arco-design-vue PC 端组件库:字节跳动出品的企业级设计系统
    element-plus PC 端组件库:基于 Vue 3,面向设计师和开发者的组件库
    Naive UI PC 端组件库:一个 Vue 3 组件库,比较完整,主题可调,使用 TypeScript,快,有点意思
    vant 移动端组件库:一个轻量、可靠的移动端组件库,于 2017 年开源
    VueUse 基于 composition 组合式 api 的常用函数集合

    相关文档

    Vue3优点和特点

    • 首次渲染更快
    • diff 算法更快
    • 内存占用更少
    • 打包体积更小
    • 更好的 Typescript 支持
    • Composition API 组合 API

    vite 构建工具

    vite(法语意为 "快速的",发音 /vit/,发音同 "veet") 是一种新型前端构建工具,能够显著提升前端开发体验

    • 对比 webpack:
      • 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢
    image.png
    • vite 的原理:
      • 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应
    image.png
    - 注:项目打包的时候最终还是需要打包成静态资源的,打包工具 Rollup
    
    • 问题:
      • 基于 webpack 构建项目,基于 vite 构建项目,谁更快体验更好?vite
      • 基于 webpack 的 vue-cli 可以创建 vue 项目吗?可以,慢一点而已

    vite 创建项目

    • 运行创建项目命令
    # 使用npm
    npm create vite@latest
    
    # 使用yarn
    yarn create vite
    
    # 使用pnpm
    pnpm create vite
    

    代码提示

    • Vue2的提示插件:Vetur
    • Vue3的提示插件:Vue Language Features(Volar)
    • 注意:安装 volar,禁用 vuter,也可以使用工作区模式启用对应插件

    vue3 写法不同

    • 组件一个根节点非必需
    <template>
      <div>节点1</div>
      <div>节点2</div>
    </template>
    
    • 创建应用挂载到根容器(vue3 中是使用 createApp() 管理容器,不是 new Vue())
    import { createApp } from 'vue'
    import App from './App.vue'
    // 根据App组件创建一个应用实例
    const app = createApp(App)
    // app应用挂载(管理)index.html的 #app 容器
    app.mount('#app')
    
    • 入口页面,ESM(ES Module)加载资源
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    

    CompositionAPI

    组合式API的介绍

    Vue3提供两种组织代码逻辑的写法

    • 通过data、methods、watch 等配置选项组织代码逻辑,选项式API写法
    • 所有逻辑在setup函数中,使用 ref、watch 等函数组织代码,组合式API写法

    计数器案例

    选项式API

    <template>
      计数器:{{ count }} <button @click="increment">累加</button>
    </template>
    <script>
    export default {
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        increment() {
          this.count++;
        },
      },
    };
    </script>
    

    组合式API

    <template>
      计数器:{{ count }} <button @click="increment">累加</button>
    </template>
    <script>
    // ref 就是一个组合式API  
    import { ref } from 'vue';
    export default {
      setup () {
        // 计数器
        const count = ref(0)
    
        const increment = () => {
          count.value ++
        }
    
        return { show, toggle, count, increment }
      }
    };
    </script>
    

    总结

    • 在setup中通过vue提供的内置函数组合,实现代码功能,是什么方式?
      • 组合式API写法
    • 组合式API有什么好处?
      • 可复用,可维护
    • ref 是不是一个组合式API?

    setup函数

    setup函数是组合式API的入口函数

    • setup函数是 Vue3特有的选项,作为组合式API的起点
    • 函数中this不是组件实例,是undefined
    • 如果数据或者函数在模板中使用,需要在setup返回
    <script>
    export default {
      // Vue3中,setup函数是所有组合式API的入口函数
      // setup函数中的变量和函数,都必须return出来,否则模板中获取不到
      // Vue2中的this为Vue组件实例,而Vue3中的this为undifined
      setup() {
        const msg = "Hello";
        const sayHello = () => {
          alert("你好,点我干啥?");
        };
        return {
          msg,
          sayHello,
        };
      },
    };
    </script>
    
    <template>
      <h1>{{ msg }}</h1>
      <button @click="sayHello">点我</button>
    </template>
    
    <style lang="scss" scoped>
    </style>
    
    • 总结:在vue3的项目中几乎用不到 this , 所有的东西通过函数获取

    setup语法糖

    • 使用 setup 有几件事必须做:
      • 默认导出配置选项
      • setup函数声明
      • 返回模板需要数据与函数

    每个组件都需要做以上三步,太麻烦,幸好Vue3提供了setup语法糖来帮我们解决这个问题

    • 子组件
    // HelloWorld.vue
    <script setup>
    </script>
    
    <template>
      <h1>Hello World</h1>
    </template>
    
    <style lang="scss" scoped>
    </style>
    
    • 父组件
    <!-- 
      setup语法糖,简写
      1、不需要写默认导出
      2、不需要写setup函数
      3、不需要将变量、函数return
      4、组件不需要注册
     -->
     <script setup>
    // Vue3中,setup函数是所有组合式API的入口函数
    // setup函数中的变量和函数,都必须return出来,否则模板中获取不到
    // Vue2中的this为Vue组件实例,而Vue3中的this为undifined
    
    import HelloWorld from "./components/HelloWorld.vue";
    
    const msg = "Hello";
    const sayHello = () => {
      alert("你好,点我干啥?");
    };
    </script>
     
     <template>
      <HelloWorld></HelloWorld>
      <h1>{{ msg }}</h1>
      <button @click="sayHello">点我</button>
    </template>
     
     <style lang="scss" scoped>
    </style>
    
    • 总结
      • 在 script setup 中声明的变量都可以在模板使用,数据,函数,组件。
      • 不需要export default
      • 不需要return

    reactive函数

    通常使用它定义 对象或数组 类型 响应式数据

    • 疑问:以前在 data 函数中返回对象数据就是响应式的,现在 setup 中返回对象数据是响应式的吗?
    • 不是,需要使用reactive转成响应式数据

    基本使用

    • vue中导出reactive函数
    • 调用reactive函数,传入一个普通对象或数组,返回一个响应式数据对象或数组
    <!-- 
      setup语法糖,简写
      1、不需要写默认导出
      2、不需要写setup函数
      3、不需要将变量、函数return
      4、组件不需要注册
     -->
     <script setup>
    // Vue3中,setup函数是所有组合式API的入口函数
    // setup函数中的变量和函数,都必须return出来,否则模板中获取不到
    // Vue2中的this为Vue组件实例,而Vue3中的this为undifined
    
    // 导入组件
    import HelloWorld from "./components/HelloWorld.vue";
    
    // reactive只能声明复杂数据类型,不能声明基础数据类型
    import { reactive } from "vue";
    
    // reactive声明对象数据
    const person = reactive({
      name: "张三",
      age: 92,
    });
    const growUp = () => {
      person.age++;
    };
    
    // reactive声明数组数据
    const list = reactive([1, 2, 3]);
    
    // 注意:reactive不能声明基础数据类型
    // 控制台警告:value cannot be made reactive: Hello 你好
    const msg = reactive("Hello 你好");
    </script>
     
     <template>
      <HelloWorld></HelloWorld>
      <h1>{{ person.name }}</h1>
      <h1>{{ person.age }}</h1>
      <button @click="growUp">点我长大</button>
    
      <br />
    
      <h2>{{ list }}</h2>
      <button @click="list.push(4)">push 4</button>
    
      <br />
      <h3>{{ msg }}</h3>
      <button @click="msg += '~'">+~</button>
    </template>
     
     <style lang="scss" scoped>
    </style>
    

    总结

    • reactive 函数通常定义:复杂类型的响应式数据
    • 可以转换简单数据吗?不能

    ref函数

    通常使用它定义响应式数据,不限类型

    基本使用

    • vue中导出ref函数
    • 调用ref函数,传入普通数据(简单or复杂),返回一个响应式数据
    • 使用ref创建的数据
      • script标签(也就是JS)中需要 .value
      • template标签中要省略.value
    <script setup>
    // 导入组件
    import HelloWorld from "./components/HelloWorld.vue";
    
    // ref,能声明所有数据类型
    import { ref } from "vue";
    
    // ref声明简单数据类型
    const msg = ref("Hello");
    
    const fn = () => {
      msg.value += "~";
    };
    
    // ref声明复杂数据类型
    const person = ref({
      name: "张三",
      age: 92,
    });
    
    const fn2 = () => {
      // JS中,修改响应式变量的值,都必须要加.value
      person.value.age++;
    };
    </script>
     
     <template>
      <HelloWorld></HelloWorld>
      <h1>{{ msg }}</h1>
      <button @click="msg += '~'">+~</button>
      <button @click="fn">++~</button>
    
      <br />
    
      <h2>{{ person.name }}</h2>
      <h2>{{ person.age }}</h2>
      <!-- 在template模板中,都不需要加.value -->
      <button @click="person.age++">点我长大</button>
      <button @click="fn2">点我长大 + 1</button>
    </template>
     
     <style lang="scss" scoped>
    </style>
    

    总结

    • ref可以把简单数据或者复杂数据转换成响应式数据
    • 注意
      • script标签(也就是JS)中需要 .value
      • template标签中要省略.value

    reactive 与 ref 的选择

    疑问:定义响应式数据使用ref还是reactive呢?

    • 对比
      • reactive可以转换对象成为响应式数据对象,但是不支持简单数据类型。
      • ref可以转换简单数据类型为响应式数据对象,也支持复杂数据类型,但是操作的时候需要 .value
    • 它们各有特点,现在也没有最佳实践,没有明显的界限,所有大家可以自由选择
    • 推荐用法
      • 推荐:使用ref
    <template>
      <div>
        <h1>{{ form.age }}</h1>
        <button @click="form.age++">reactive age++</button>
    
        <h1>{{ form.name }}</h1>
        <button @click="form.name += '~'">reactive name+~</button>
        <hr>
        <h1>{{ data.age }}</h1>
        <button @click="data.age++">ref age++</button>
    
        <h1>{{ data.name }}</h1>
        <button @click="data.name += '~'">ref name+~</button>
      </div>
    </template>
    
    <script setup>
    import { reactive, ref } from 'vue'
    
    let form = reactive({})
    // 重新赋值会导致响应式丢失
    form = {
      name: 'zs',
      age: 18,
    }
    
    
    let data = ref({})
    // 重新赋值不会导致响应式丢失
    data.value = {
      name: 'zs',
      age: 18,
    }
    </script>
    
    <style lang="scss" scoped>
    </style>
    

    总结

    • 尽量使用ref函数支持所有场景,减少心智负担
    • 如确定字段为对象,若使用reactive,可以省去.value

    computed函数

    使用 computed 函数定义计算属性

    • 作用:与vue2一模一样,根据其它数据计算得到一个新的值
    • 语法:computed( () => 返回一个新的值 )

    基本使用

    • vue中导出 computed 函数
    • 调用computed函数,传入一个回调函数
    • 回调函数内计算新的值,并返回结果
    <script setup>
    // 导入computed函数
    import { computed, ref } from "vue";
    const score = ref();
    const scoreList = ref([50, 80, 100, 90, 70, 60]);
    
    // 需求:定义三个计算属性,
    // 不及格 < 60
    const failList = computed(() => {
      return scoreList.value.filter((item) => item < 60);
    });
    
    // commonList 及格 >=60 且 <90
    const commonList = computed(() => {
      return scoreList.value.filter((item) => item >= 60 && item < 90);
    });
    
    // 优秀 > 90
    const betterList = computed(() => {
      return scoreList.value.filter((item) => item > 90);
    });
    
    // 处理点击事件
    const handleClick = () => {
      if (score.value === undefined || score.value === "") {
        alert("请输入内容");
        return;
      }
      // 添加输入框的值到数组中
      scoreList.value.push(score.value);
      // 清空输入框
      score.value = "";
    };
    </script>
    
    <template>
      <div>
        <input
          type="text"
          @keyup.enter="handleClick"
          v-model.number="score"
        /><button @click="handleClick">添加</button>
        <hr />
        <p>不及格: {{ failList }}</p>
        <p>及格: {{ commonList }}</p>
        <p>优秀:{{ betterList }}</p>
      </div>
    </template>
    

    watch函数

    使用watch函数监听数据的变化

    • 功能

      • 使用 watch 监听一个基础数据类型
      • 使用 watch 监听一个复杂数据类型,配置深度监听
      • 使用 watch 监听,配置立即执行
      • 使用 watch 同时监听多个响应式数据
      • 使用 watch 只监听对象数据中的某一个属性(简单)
      • 使用 watch 只监听响应式对象数据中的一个属性(复杂),配置深度监听
    • 语法

      • watch(基本数据,(newValue, oldValue) => { })
      • watch(复杂数据,(newValue, oldValue) => { }, {deep: true, immediate: true})
      • watch( [数据1, 数据2], (newValue, oldValue) => { })
      • watch( () => 复杂数据.属性名, (newValue, oldValue) => { } )

    基本使用

    <script setup>
    // 导入watch函数
    import { ref, watch } from "vue";
    
    const count = ref(0);
    const user = ref({
      name: "jack",
      info: {
        gender: "男",
        age: 18,
      },
    });
    
    // 监听基础数据类型
    watch(count, (newValue, oldValue) => {
      console.log(`newValue => ${newValue}`);
      console.log(`oldValue => ${oldValue}`);
    });
    
    // 监听复杂数据类型
    // watch(
    //   user,
    //   (newValue, oldValue) => {
    //     console.log(`newValue => `, newValue);
    //     console.log(`oldValue => `, oldValue);
    //   },
    //   {
    //     // 深度监听
    //     deep: true,
    //     // 初始化时,就立即执行回调函数
    //     immediate: true,
    //   }
    // );
    
    // 同时监听多个数据的变化
    // 注意:如果其中一个数据是复杂数据类型,还是要加上deep深度监听
    // watch(
    //   [count, user],
    //   (newValue) => {
    //     console.log(`newValue => `, newValue);
    //   },
    //   { deep: true }
    // );
    
    // 深度监听,需要遍历,如果只想其中一个属性,就会性能浪费,Vue3可以单独精准监听对象某个属性的变化
    // 注意:如果精准监听的数据也是复杂数据类型,还是要加上deep深度监听
    watch(
      // 监听哪个属性
      () => user.value.info.age,
      // 属性变化时,回调函数
      (newValue) => {
        console.log(`newValue => `, newValue);
      }
    );
    </script>
    
    <template>
      <div>计数器:{{ count }} <button @click="count++">count++</button></div>
      <div>
        <p>姓名:{{ user.name }}</p>
        <p>性别:{{ user.info.gender }}</p>
        <p>年龄:{{ user.info.age }}</p>
        <button @click="user.name += '~'">对象改名字</button>
        <button @click="user.info.age++">对象改年龄</button>
      </div>
    </template>
    

    总结

    • watch(需要监听的数据,数据改变执行函数,配置对象),来进行数据的侦听
    • 数据
      • 单个数据
      • 多个数据
      • 对象的某个属性,函数返回对象属性
      • 属性为复杂数据类型,需要开启深度监听
    • 配置对象
      • deep:深度监听
      • immediate:侦听器在初始化时,立即执行一次回调函数

    生命周期函数

    vue3的常用生命周期函数

    使用步骤

    • 先从vue中,导入以on打头的生命周期钩子函数

    • 在setup函数中,调用生命周期函数并传入回调函数

    • 生命周期钩子函数,可以调用多次

    • Vue3和vue2的生命周期对比

    选项式API下的生命周期函数使用 组合式API下的生命周期函数使用
    beforeCreate 不需要(直接写到setup函数中)
    created 不需要(直接写到setup函数中)
    beforeMount onBeforeMount
    mounted onMounted
    beforeUpdate onBeforeUpdate
    updated onUpdated
    beforeDestroyed onBeforeUnmount
    destroyed onUnmounted
    activated onActivated
    deactivated onDeactivated

    基本使用

    <script setup>
    // 导入生命周期函数
    import { onBeforeUpdate, onMounted, onUpdated, ref } from "vue";
    
    // 定义响应式变量
    const count = ref(0);
    
    // 注意:生命周期,都加上了on开头
    // Vue2中,发送请求推荐在created中,而Vue3推荐在onMounted中
    onMounted(() => {
      console.log("onMounted,挂载后 -----> ");
      // 挂载后,能获取到DOM元素
      const box = document.querySelector("#app");
      console.log(box);
    });
    
    onBeforeUpdate(() => {
      console.log("更新前 -----> ");
    });
    
    onUpdated(() => {
      console.log("更新后 -----> ");
    });
    </script>
    
    <template>
      <div class="box"></div>
      <span>{{ count }}</span> <button @click="count++">+1</button>
      <div>生命周期函数</div>
    </template>
    

    总结

    • Vue2发请求,推荐生命周期:created
    • Vue3发请求,推荐生命周期: onMounted
      语法:钩子函数(() => {})

    使用ref获取DOM元素

    元素上使用 ref属性关联响应式数据,获取DOM元素

    使用步骤

    • 创建 ref:const xxxRef = ref()
    • 绑定ref属性到标签上:ref="xxxRef"
    • 通过xxxRef.value访问dom

    基本使用

    <script setup>
    // 导入ref函数
    import { ref } from "vue";
    
    // 调用ref,定义响应式变量
    const inputRef = ref();
    
    const fn = () => {
      // 通过响应式变量,获取DOM元素实例,就可以调用DOM元素的实例方法
      inputRef.value.focus();
    };
    </script>
    
    <template>
      <!-- 绑定ref到标签上 -->
      <input type="text" ref="inputRef" />
      <button @click="fn">获取焦点</button>
    </template>
    

    父子组件通信

    ref操作组件-defineExpose

    使用步骤

    • 准备父组件、子组件

    • 父组件内:

      • 创建ref
      • 绑定ref到子组件标签上
      • 通过ref.value访问子组件实例
    • 子组件内, 调用defineExpose()编译器宏函数,无需导入,暴露数据和方法

    • 解释

      • 使用<script setup>的组件是默认关闭的,父组件拿不到子组件的数据和函数
      • 需要配合defineExpose向外暴露数据,暴露的响应式数据会自动解除响应式

    父传子-defineProps函数

    父组件,向子组件传参

    使用步骤

    • 父组件,声明数据
    • 父组件,传递数据给子组件
    • 子组件通过defineProps,接收props
    • 子组件渲染父组件传递的数据

    子传父-defineEmits函数

    子组件,发送自定义事件,传参给父组件

    使用步骤

    • 子组件通过defineEmits,获取emit函数(因为没有this)
    • 子组件通过emit触发事件,并且传递数据
    • 父组件,v-on或者@,监听自定义事件

    语法

    • const emit = defineEmits(["自定义事件名1", "自定义事件名2"])
    • emit("自定义事件名",值)

    综合案例

    子组件

    <script setup>
    // 通过 defineProps 编译器宏函数,定义子组件的参数
    
    // 方式一:数组定义参数列表
    // defineProps(["name", "money", "car"]);
    
    // 方式二:对象定义参数列表和参数类型
    const props = defineProps({
      name: String,
      money: Number,
      car: String,
    });
    
    // 通过 defineProps 函数的返回值,获取参数值
    console.log(`name => ${props.name}`);
    console.log(`money => ${props.money}`);
    console.log(`car => ${props.car}`);
    
    // 通过 defineEmits 编译器宏函数,定义事件
    // 虽然可以不传参数,但推荐传入事件名称,在发送事件时,会有代码提示
    // const emit = defineEmits();
    const emit = defineEmits(["addMoney", "costMoney"]);
    </script>
    
    <template>
      <div>
        <h3>我是子组件</h3>
        <div>
          金钱:{{ money }} <br />
          跑车:{{ car }}
        </div>
        <button @click="emit('addMoney', 10)">吞金兽赚钱啦</button>
        <button @click="emit('costMoney', 50)">吞金兽想花钱</button>
      </div>
    </template>
    

    父组件

    <script setup>
    import { ref } from "vue";
    import Child from "./components/Child.vue";
    
    // 声明响应式变量
    const money = ref(100);
    const car = ref("玛莎拉蒂");
    
    // 事件处理函数
    const fn = (item) => {
      money.value -= item;
    };
    const fn2 = (item) => {
      money.value += item;
    };
    </script>
    
    <template>
      <div>
        <h1>我是父组件</h1>
        <div>金钱:{{ money }}</div>
        <div>车辆:{{ car }}</div>
        <hr />
        <!-- 通过 v-bind 绑定数据给子组件 -->
        <!-- 父组件,监听子组件发出的自定义事件 -->
        <Child
          :money="money"
          :car="car"
          name="zs"
          @costMoney="fn"
          @addMoney="fn2"
        />
      </div>
    </template>
    

    跨组件传参-provide和inject

    通过provide和inject函数可以简便的实现跨级组件通讯

    案例

    祖先组件

    <script setup>
    // 跨组件通信-依赖注入
    // 注意:不是一定只能爷孙关系才能使用provide和inject,而是所有组件都可以
    
    // 导入 provide 函数,用于提供数据
    import { ref, provide } from "vue";
    import ParentCom from "./components/ParentCom.vue";
    
    // 声明响应式变量
    const count = ref(99);
    // 声明修改变量的函数
    const addFn = () => {
      count.value++;
    };
    
    // 提供数据给子孙组件(存数据)
    provide("data", count);
    provide("addFn", addFn);
    </script>
    
    <template>
      <div
        class="app-page"
        style="border: 10px solid #ccc; padding: 50px; width: 600px"
      >
        app 组件 {{ count }}
        <ParentCom />
      </div>
    </template>
    

    父级组件

    <script setup>
    import ChildCom from "./ChildCom.vue";
    </script>
    
    <template>
      <div class="parent-page" style="padding: 50px">
        parent 组件
        <hr />
        <ChildCom />
      </div>
    </template>
    

    子级组件

    <script setup>
    // 导入inject函数,通过它获取数据
    import { inject } from "vue";
    
    // 获取爷级组件提供的数据(取数据)
    const data = inject("data");
    // 获取爷组件提供的函数,通过函数修改爷级组件
    const addFn = inject("addFn");
    
    console.log(addFn);
    </script>
    
    <template>
      <div class="child-page" style="padding: 50px; border: 10px solid #ccc">
        child 组件接收到count: {{ data }} <button @click="addFn">修改count</button>
      </div>
    </template>
    

    总结

    • provideinject是解决跨级组件通讯的方案
      • provide 提供后代组件需要依赖的数据或函数
      • inject 注入(获取)provide提供的数据或函数
    • 官方术语:依赖注入
    • App是后代组件依赖的数据和函数的提供者,Child是注入(获取)了App提供的依赖

    保持响应式-toRefs函数

    在使用reactive,创建的响应式数据,被展开或解构的时候,使用toRefs保持响应式

    步骤

    • 解构响应式数据,踩坑
    • 使用toRefs处理响应式数据,解决问题
    <script setup>
    import { ref, toRefs } from "vue";
    
    // 定义响应式数据
    let user = ref({ name: "tom", age: 18 });
    
    // 默认:解构ref或reactive,会导致响应式丢失
    // 虽然视图能显示第一次的数据,但后续修改数据,数据不会驱动视图
    // const { name, age } = user.value;
    
    // 作用:把对象中的每一个属性,做一次包装,成为响应式数据
    const { name, age } = toRefs(user.value);
    </script>
    
    <template>
      <div>
        <p>姓名:{{ name }}</p>
        <p>年龄:{{ age }} <button @click="age++">一年又一年</button></p>
      </div>
    </template>
    

    总结

    • toRefs 函数的作用,与使用场景
      • 作用:把对象中的每一个属性,做一次包装,成为响应式数据
      • 场景:解构响应式数据的时候使用
    • 一句话总结:当去解构响应式对象,使用toRefs保持响应式

    Vue3中v-model语法糖的变化

    vue3中v-model语法糖原理

    • vue2中,v-model 语法糖 完整写法?
      • 原生标签,:value="count" 和 @input="count=$event.target.value"
      • 组件标签,:value="count" 和 @input="..."
    • vue3中,v-model 语法糖 完整写法?
      • 原生标签,:value="count" 和 @input="count=$event.target.value"
      • 组件标签,:modelValue="count" 和 @update:modelValue="..."

    为什么变了?

    • vue3 中 组件标签上,可以使用多个v-model语法糖,而在Vue2中,v-model只能出现1个,多个时需要使用.sync修饰符的方式来解决

    • 单个v-model

    <com-a v-model="count"></com-a>
    <!-- 等价 -->
    <com-a :modelValue="count" @update:modelValue="count=$event"></com-a>
    
    • 多个v-model
    <com-a v-model="count" v-model:msg="str"></com-a>
    <!-- 等价 -->
    <com-a :modelValue="count" @update:modelValue="..." :msg="str" @update:msg="..." />
    

    案例

    • 父组件中,点击一个按钮,显示弹窗子组件
    • 点击弹窗子组件右上角的X关闭按钮,关闭弹窗

    弹窗子组件

    <!-- 弹窗子组件 -->
    <script setup>
    // 定义子组件的参数
    defineProps({
      modelValue: Boolean,
    });
    
    // 定义事件
    const emit = defineEmits(["update:modelValue"]);
    </script>
    
    <template>
      <div class="box" v-show="modelValue">
        <div class="container">
          <!-- 点关闭按钮时,发送事件,通知父组件关闭弹窗 -->
          <div class="header" @click="emit('update:modelValue', false)">x</div>
          <div class="content">
            <span>我是弹窗内容</span>
          </div>
        </div>
      </div>
    </template>
    
    <style  scoped>
    .box {
      width: 100vw;
      height: 100vh;
      background-color: rgba(0, 0, 0, 0.3);
      position: fixed;
      left: 0;
      top: 0;
    }
    
    .container {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 400px;
      height: 300px;
      background-color: #fff;
      border-radius: 6px;
    }
    
    .header {
      float: right;
      margin-right: 10px;
      color: #000;
    }
    
    .content {
      width: 100%;
      height: 100%;
      color: #000;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    </style>
    

    父组件

    <script setup>
    // v-model在Vue3中的变化
    // 1、在原生HTML标签上,v-model的原理没有变化,v-model会转换为 :value="值" 和 @input="事件处理函数"
    // 2、在组件标签上,v-model有变化,v-model会转换为:modelValue 和 @update:modelValue
    
    /*
      问:v-model的原理?
      答:v-model是语法糖,底层会将v-model转化为2个指令, v-bind:value="值" 和 @input="事件处理函数"
    */
    import { ref } from "vue";
    // 导入弹窗子组件
    import MyDialog from './components/MyDialog.vue'
    
    const msg = ref("Hello");
    
    // 是否显示弹窗
    const visiable = ref(false)
    
    
    const handleCloseDialog = (event) => {
      visiable.value = event
    }
    
    // 打开弹窗
    const open = () => {
      visiable.value = true
    }
    </script>
    
    <template>
      <!-- 使用v-model,实现双向绑定 -->
      <input type="text" v-model="msg" />
    
      <hr />
    
      <!-- 不使用v-model,实现双向绑定 -->
      <input type="text" :value="msg" @input="msg = $event.target.value" />
    
      <br>
      <br>
    
      <!-- <button @click="visiable = true">显示弹窗</button> -->
      <button @click="open">显示弹窗</button>
    
      <!-- 弹窗子组件 -->
      <!-- Vue3中,v-model会转化为,:modelValue 和 @update:modelValue -->
      <!-- <MyDialog v-model="visiable"></MyDialog> -->
      <!-- <MyDialog :model-value="visiable" @update:model-value="visiable = $event"/> -->
      <MyDialog :model-value="visiable" @update:model-value="handleCloseDialog"/>
    </template>
    

    总结

    • vue3 中v-model语法糖,相当于哪2个指令?
      • 答::modelValue="count" 和 @update:modelValue="count=$event"
    • vue3中,如果要实现xxx属性的双向绑定,使用v-model来实现,它的底层会映射为?
      • 答::xxx="count" 和 @update:xxx="count=$event"
    • 总结:vue3中只需要v-model指令,就可以支持多个数据在父子组件同步

    相关文章

      网友评论

          本文标题:Vue3

          本文链接:https://www.haomeiwen.com/subject/zlcmvdtx.html