• Vue3新特性--学习笔记


    Vue3新特性

    主要内容

    • Vue3介绍
    • setup函数
    • Vue组件数据
    • 计算属性和监听器
    • Vue组件之间通信

    一、Vue3介绍

    Vue3优点:

    • 最火框架,它是国内最火的前端框架之一,官方文档 (opens new window)中文文档(opens new window)
    • 性能提升,运行速度事vue2.x的1.5倍左右
    • 体积更小,按需编译体积比vue2.x要更小
    • ==类型推断,更好的支持Ts(typescript)==这个也是趋势
    • 高级给予,暴露了更底层的API和提供更先进的内置组件
    • ★组合API (composition api) ,能够更好的组织逻辑,封装逻辑,复用逻辑

    vite基本使用

    vite是什么:官方文档(opens new window)

    • 它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具。
    • 相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置。
    • 所以: 在单纯学习vue3语法会使用它,后面做项目的时候我们还是使用vue-cli
    • 什么是vite?—— 新一代前端构建工具。
    • 优势如下:
      • 开发环境中,无需打包操作,可快速的冷启动。
      • 轻量快速的热重载(HMR)。
      • 真正的按需编译,不再等待整个应用编译完成。

    vite基本使用:

    • 创建项目 npm init vite-app 项目名称 或者 yarn create vite-app 项目名称

    • 安装依赖 npm i 或者 yarn

    • 启动项目 npm run dev 或者 yarn dev

      npm init vite@latest
      ## 创建工程
      npm init vite-app <project-name>
      ## 进入工程目录
      cd <project-name>
      ## 安装依赖
      npm install
      ## 运行
      npm run dev
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    创建vue项目

    1. 安装最新版本的Vue-cli工具,最新版本工具已经提供Vue3-preview
    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
    
    • 1
    • 2
    • 3

    根实例初始化

    在2.x中,通过new Vue()的方法来初始化

    import Vue from 'vue'
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在3.x中Vue不再是一个构造函数,通过createApp方法初始化

    createApp(App).use(store).use(router).mount('#app')
    
    • 1

    组合式 API 征求意见稿

    选项API和组合API

    在这里插入图片描述

    • 代码风格:一个功能逻辑的代码组织在一起(包含数据,函数…)
    • 优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
    • 缺点:需要有良好的代码组织能力和拆分逻辑能力
    • 补充:为了能让大家较好的过渡到vue3.0的版本来,也支持vue2.x选项API写法

    更好的逻辑复用与代码组织

    我们都因 Vue 简单易学而爱不释手,它让构建中小型应用程序变得轻而易举。但是随着 Vue 的影响力日益扩大,许多用户也开始使用 Vue 构建更大型的项目。这些项目通常是由多个开发人员组成团队,在很长一段时间内不断迭代和维护的。多年来,我们目睹了其中一些项目遇到了 Vue 当前 API 所带来的编程模型的限制。这些问题可归纳为两类:

    1. 随着功能的增长,复杂组件的代码变得越来越难以阅读和理解。这种情况在开发人员阅读他人编写的代码时尤为常见。根本原因是 Vue 现有的 API 迫使我们通过选项组织代码,但是有的时候通过逻辑关系组织代码更有意义。
    2. 目前缺少一种简洁且低成本的机制来提取和重用多个组件之间的逻辑。 RFC 中提出的 API 为组件代码的组织提供了更大的灵活性。现在我们不需要总是通过选项来组织代码,而是可以将代码组织为处理特定功能的函数。这些 API 还使得在组件之间甚至组件之外逻辑的提取和重用变得更加简单。我们会在设计细节这一节展示达成的效果。

    更好的类型推导

    二、setup函数

    2.1 setup函数介绍

    • setup 是一个新的组件选项,作为组件中使用组合API的起点
    • 从组件生命周期来看,它的执行在组件实例创建之前vue2.x的beforeCreate执行。
    • 这就意味着在setup函数中 this 还不是组件实例,this 此时是 undefined
    • 在模版中需要使用的数据和函数,需要在 setup 返回。
    <template>
      <div class="container">
        <h1 @click="say()">{{msg}}h1>
      div>
    template>
    <script>
    export default {
      setup () {
        console.log('setup执行了')
        console.log(this)
        // 定义数据和函数
        const msg = 'hi vue3'
        const say = () => {
          console.log(msg)
        }
    
        return { msg , say}
      },
      beforeCreate() {
        console.log('beforeCreate执行了')
        console.log(this)
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    总结: setup 组件初始化之前执行,它返回的数据和函数可在模版使用。

    2.2 组合API-生命周期

    回顾vue2.x生命周期钩子函数:

    • beforeCreate
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • beforeDestroy
    • destroyed

    认识vue3.0生命周期钩子函数(7)

    • setup 创建实例前
    • onBeforeMount 挂载DOM前
    • onMounted 挂载DOM后
    • onBeforeUpdate 更新组件前
    • onUpdated 更新组件后
    • onBeforeUnmount 卸载销毁前
    • onUnmounted 卸载销毁后

    代码演示

    <template>
      <div class="container">
        container
      div>
    template>
    <script>
    import { onBeforeMount, onMounted } from 'vue'
    export default {
      setup () {
        onBeforeMount(()=>{
          console.log('DOM渲染前',document.querySelector('.container'))
        })
        onMounted(()=>{
          console.log('DOM渲染后1',document.querySelector('.container'))
        })
        onMounted(()=>{
          console.log('DOM渲染后2',document.querySelector('.container'))
        })
      },
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    注意:

    组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同。

    生命周期可以写多遍,可以把两个功能写在两个不同的生命周期里。去实现不同的逻辑

    三、Vue组件数据

    介绍

    ref或者reactive替代data中的变量

    在2.x中通过组件data的方法来定义一些当前组件的数据

    data() {
      return {
        name: 'iwen',
        list: [],
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在3.x中通过ref或者reactive创建响应式对象

    import { ref,reactive } from "vue"
    export default {
      name: 'HelloWorld',
      setup(){
          const name = ref("iwen")
          const state = reactive({
              list:[]
          })
    
        return{
            name,
            list
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    定义响应式数据:
    • reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。 {}, []
    • 通常是用来定义响应式对象数据
    <template>
        <div>
            <p>{{obj.name}}p>
            <p><button @click="updateName">修改名字button>p>
        div>
    template>
    
    <script>
    import { reactive } from 'vue'
    export default {
        setup () {
            // 普通数据
            // const obj={
            //     name:'zs',
            //     age:18
            // }
    
            const obj=reactive({
                name:'zs,我是响应式的',
                age:18
            })
    
            const updateName=()=>{
                console.log('updateName....');
                obj.name='lisi'
            }
    
            return {obj,updateName}
        }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    toRef函数

    toRef是函数,转换响应式对象某个属性为单独响应式数据,并且跟原来是数据的值是关联的

    如上例中,只想访问响应式对象obj中name属性,而不需要age.

    通过对象解构出来的数据,不是响应式的。需要使用toRef

    <template>
      <div>
        <p>{{ name }}p>
        <p><button @click="updateName">修改button>p>
      div>
    template>
    
    <script>
    import { reactive, toRef } from "@vue/reactivity";
    export default {
      setup() {
        // 定义响应式数据
        const obj = reactive({
          name: "zs",
          age: 18,
        });
        // 模块中只使用name,
        // 注意:从响应式数据中解构出来的数据不再是响应式的!!!
        // let {name}=obj;
        let name = toRef(obj, "name");
    
        const updateName = () => {
          console.log("updateName、、、、");
          // toRef转换响应式数据包装成对象,value存放值的位置
          name.value = "我是修改之后的lisi 啊";
        };
    
        return { name, updateName };
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据。

    toRefs是函数

    • toRefs是函数,转换响应式对象所有属性为单独响应式数据,对象成为普通对象,并且值是关联的

      <template>
        <div>
          <p>{{ name }}p>
          <p>{{ age }}p>
          <p><button @click="updateName">修改button>p>
        div>
      template>
      
      <script>
      import { reactive, toRef, toRefs } from "@vue/reactivity";
      export default {
        setup() {
          // 定义响应式数据
          const obj = reactive({
            name: "zs",
            age: 18,
          });
          const obj2={...obj};
          console.log(obj2);
          const obj3=toRefs(obj)
          console.log(obj3);
        
          const updateName = () => {
            console.log("updateName、、、、");
            obj3.name.value='改了哦、、、'
            // 也可以改原来的数据,因为【值是关联的】
            // obj.name='也可以改原来的数据'
          };
      
          return { ...obj3, updateName };
        },
      };
      script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33

    在这里插入图片描述

    使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据。

    可以理解的是,用户会纠结用 ref 还是 reactive。而首先你要知道的是,这两者你都必须要了解,才能够高效地使用组合式 API。只用其中一个很可能会使你的工作无谓地复杂化,或反复地造轮子。 使用 ref 和 reactive 的区别,可以通过如何撰写标准的 JavaScript 逻辑来比较

    // 风格 1: 将变量分离
    let x = 0
    let y = 0
    
    function updatePosition(e) {
      x = e.pageX
      y = e.pageY
    }
    
    // --- 与下面的相比较 ---
    
    // 风格 2: 单个对象
    const pos = {
      x: 0,
      y: 0,
    }
    
    function updatePosition(e) {
      pos.x = e.pageX
      pos.y = e.pageY
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 如果使用 ref,我们实际上就是将风格 (1) 转换为使用 ref (为了让基础类型值具有响应性) 的更细致的写法。
    • 使用 reactive 和风格 (2) 一致。我们只需要通过 reactive 创建这个对象。

    总结一下,一共有两种变量风格:

    1. 就像你在普通 JavaScript 中区别声明基础类型变量与对象变量时一样区别使用 ref 和 reactive。我们推荐你在此风格下结合 IDE 使用类型系统。
    2. 所有的地方都用 reactive,然后记得在组合函数返回响应式对象时使用 toRefs。这降低了一些关于 ref 的心智负担,但并不意味着你不需要熟悉这个概念。

    组合API-ref函数

    定义响应式数据:

    • ref函数,常用于简单数据类型定义为响应式数据
      • 再修改值,获取值的时候,需要.value
      • 在模板中使用ref申明的响应式数据,可以省略.value

    代码演示

    <template>
      <div class="container">
        <div>{{name}}div>
        <div>{{age}}div>
        <button @click="updateName">修改数据button>
      div>
    template>
    <script>
    import { ref } from 'vue'
    export default {
      name: 'App',
      setup () {
        // 1. name数据
        const name = ref('ls')
        console.log(name)
        const updateName = () => {
          name.value = 'zs'
        }
        // 2. age数据
        const age = ref(10)
    
        // ref常用定义简单数据类型的响应式数据
        // 其实也可以定义复杂数据类型的响应式数据
        // 对于数据未之的情况下 ref 是最适用的
        // const data = ref(null)
        // setTimeout(()=>{
        //   data.value = res.data
        // },1000)
    
        return {name, age, updateName}
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    模板在解析的时候,会判断是不是ref形式创建的数据,直接取出value。所以在模板中使用的时候,省略 .value

    在这里插入图片描述

    使用场景:

    • 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
    • 其他情况使用ref

    3.2 组合API-reactive函数

    定义响应式数据:

    • reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。

    演示代码:

    <template>
      <div class="container">
        <div>{{obj.name}}div>
        <div>{{obj.age}}div>
        <button @click="updateName">修改数据button>
      div>
    template>
    <script>
    import { reactive } from 'vue'
    export default {
      name: 'App',
      setup () {
        // 普通数据
        // const obj = {
        //   name: 'ls',
        //   age: 18
        // }
        const obj = reactive({
          name: 'ls',
          age: 18
        })
    
        // 修改名字
        const updateName = () => {
          console.log('updateName')
          obj.name = 'zs'
        }
    
        return { obj ,updateName}
      }
    }
    script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.3 知识运用案例

    基本步骤:

    • 记录鼠标坐标
      • 定义一个响应式数据对象,包含x和y属性。 ==>响应式数据对象
      • 在组件渲染完毕后,监听document的鼠标移动事件 =>生命周期,挂载之后
      • 指定move函数为事件对应方法,在函数中修改坐标
      • 在setup返回数据,模版中使用
    • 累加1功能
      • 定义一个简单数据类型的响应式数据 ===》ref(0)
      • 定义一个修改数字的方法
      • 在setup返回数据和函数,模板中使用

    落的代码:

    <template>
      <div class="container">
        <div>坐标div>
        <div>x: {{x}}div>
        <div>y: {{y}}div>
        <hr>
        <div>{{count}} <button @click="add">累加1button>div>
      div>
    template>
    <script>
    import { onMounted, onUnmounted, reactive , ref, toRefs} from 'vue'
    const useMouse = () => {
        // 1. 记录鼠标坐标
        // 1.1 申明一个响应式数据,他是一个对象,包含x y
        const mouse = reactive({
          x: 0,
          y: 0
        })
        // 1.3 修改响应式数据
        const move = (e) => {
          mouse.x = e.pageX
          mouse.y = e.pageY
        }
        // 1.2 等dom渲染完毕。去监听事件
        onMounted(()=>{
          document.addEventListener('mousemove', move)
        })
        // 1.4 组件销毁,删除事件
        onUnmounted(()=>{
          document.removeEventListener('mousemove', move)
        })
    
        return mouse
    }
    export default {
      name: 'App',
      setup () {
    	const mouse = useMouse()
        
        // 2. 数字累加
        const count = ref(0) 
        const add = () => {
          count.value ++   //一定记得.value
        }
        //单纯 ...mouse 解构是不可以的,需要 toRefs,再解构
        return { ...toRefs(mouse), count, add }
      }
    }
    script>
    <style scoped lang="less">style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    封装:
    const useMouse = () => {
      // 声明响应式数据
      const mouse = reactive({
        x: 0,
        y: 0,
      });
      // 修改响应式数据
      const move = (e) => {
        // console.log(e.pageX);
        // console.log(e.pageY);
        mouse.x = e.pageX;
        mouse.y = e.pageY;
      };
      // 等dom渲染完毕。去监听事件
      onMounted(() => {
        document.addEventListener("mousemove", move);
      });
      // 组件卸载,删除事件
      onUnmounted(() => {
        document.removeEventListener("mousemove", move);
      });
      // 一定要return 出去!!!!
      return mouse;
    };
    
    export default {
      setup() {
        const mouse = useMouse();
    
        // 定义响应式数据
        let count = ref(0);
        const add = () => {
          count.value++; //一定记得.value
        };
    
        // return { mouse };
        //单纯 ...mouse 解构是不可以的,需要 toRefs,再解构
        return { ...toRefs(mouse), count, add };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    补充:ref属性:

    Vue2 :

    ref: 获取DOM元素;获取组件事件

    Vue3: ref

    在这里插入图片描述

    回顾:

    1. Vue3 新特性:

      • Vue2 选项API (data,methods,computd,。。。。。)

      • Vue3 组合API(方法)

      • 创建根节点:Vue 2: new Vue({}) ;Vue3 :createApp(App).use(router).use(vuex)

      • Vue3 Setup():起点 beforeCreate() created() ===>this

      • Vue 2 : data,computd 定义的数据都是响应式

      • Vue3 定义响应式数据: ref():基本数据类型 reactive() :复杂数据类型

        • const res= ref(null)
        • toRef是函数,转换响应式对象某个属性为单独响应式数据,并且跟原来是数据的值是关联的
        • toRefs是函数,转换响应式对象所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
      • ref :获取DOM 元素,组件

        • 属性:

          <h2 ref='myH2'>
              
          h2>
          
          • 1
          • 2
          • 3
          const myH2=ref(null);
          
          onMounted(()=>{
              console.log(myH2)
          })
          
          reutrn{myH2}
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
      • Vue2 生命周期全部都支持

      • Vue3 on onBeforeMount onUnmounted Vue3的生命周期比Vue2都要早

      • 数据代理方式:

      • Vue2: Object.defineproperty(obj,‘name’) ==> 会对 data, computed所有属性进行遍历代理

        getter 依赖收集 [] ,setter

      • Vue3 Proxy 13种 ===》MDN

    四、计算属性和监听器

    4.1 计算属性

    在2.x中

    computed: {
        storeData () {
          return this.$store.state.storeData
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在3.x中

    import {computed} from 'vue'
    setup(){
      const storeData = computed(() => store.state.storeData)
    
      return {
          storeData
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    高级点的用法:
    const newMoney = computed({
          // 访问,
          get() {
            // 该函数的返回值,就是计算属性的值
            return money.value * 6;
          },
          //   修改它的依赖的值
          set(val) {
            money.value = val / 6;
          }
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.2 watch监听器

    在2.x版本的时候代码如下

    export default {
      data() {
        return {
          counter: 0
        }
      },
      watch: {
        counter(newValue, oldValue) {
          console.log('The new counter value is: ' + this.counter)
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在3.x版本的时候代码如下

    import { ref, watch } from 'vue'
    
    const counter = ref(0)
    watch(counter, (newValue, oldValue) => {
      console.log('The new counter value is: ' + counter.value)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    watch函数

    定义计算属性:

    watch函数,是用来定义侦听器的

    监听ref定义的响应式数据

    监听多个响应式数据数据

    监听reactive定义的响应式数据

    监听reactive定义的响应式数据,某一个属性

    深度监听

    默认执行

    <template>
      <div class="container">
        <div>
          <p>count的值:{{count}}p>
          <button @click="add">改数据button>
        div>
        <hr>
        <div>
          <p>{{obj.name}}p>
          <p>{{obj.age}}p>
          <p>{{obj.brand.name}}p>
          <button @click="updateName">改名字button>
          <button @click="updateBrandName">改品牌名字button>
        div>
      div>
    template>
    <script>
    import { reactive, ref, watch } from 'vue'
    export default {
      name: 'App',
      setup () {
        const count = ref(0)
        const add = () => {
          count.value++
        }
        // 当你需要监听数据的变化就可以使用watch
        // 1. 监听一个ref数据
        // watch(count, (newVal,oldVal)=>{
        //   console.log(newVal,oldVal)
        // })
    
        const obj = reactive({
          name: 'ls',
          age: 10,
          brand: {
            id: 1,
            name: '宝马'
          }
        })
        const updateName = () => {
          obj.name = 'zs'
        }
        const updateBrandName = () => {
          obj.brand.name = '奔驰'
        }
        // 2. 监听一个reactive数据
        watch(obj, ()=>{
          console.log('数据改变了')
        })
    
        watch(()=>obj.brand, ()=>{
          console.log('brand数据改变了')
        },{
          deep: true,
          immediate: true
        })
    
        return {count, add, obj, updateName, updateBrandName}
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    五、Vue组件之间通信

    5.1 组合API-父子通讯

    父传子:

    <template>
      <div class="container">
        <h1>父组件h1>
        <p>{{money}}p>
        <hr>
        <Son :money="money" />
      div>
    template>
    <script>
    import { ref } from 'vue'
    import Son from './Son.vue'
    export default {
      name: 'App',
      components: {
        Son
      },
      // 父组件的数据传递给子组件
      setup () {
        const money = ref(100)
        return { money }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    <template>
      <div class="container">
        <h1>子组件h1>
        <p>{{money}}p>
      div>
    template>
    <script>
    import { onMounted } from 'vue'
    export default {
      name: 'Son',
      // 子组件接收父组件数据使用props即可
      props: {
        money: {
          type: Number,
          default: 0
        }
      },
      setup (props) {
        // 获取父组件数据money
        console.log(props.money)
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    子传父

    <template>
      <div class="container">
        <h1>父组件h1>
        <p>{{money}}p>
        <hr>
    +    <Son :money="money" @change-money="updateMoney" />
      div>
    template>
    <script>
    import { ref } from 'vue'
    import Son from './Son.vue'
    export default {
      name: 'App',
      components: {
        Son
      },
      // 父组件的数据传递给子组件
      setup () {
        const money = ref(100)
    +    const updateMoney = (newMoney) => {
    +      money.value = newMoney
    +    }
    +    return { money , updateMoney}
      }
    }
    script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    <template>
      <div class="container">
        <h1>子组件h1>
        <p>{{money}}p>
    +    <button @click="changeMoney">花50元button>
      div>
    template>
    <script>
    import { onMounted } from 'vue'
    export default {
      name: 'Son',
      // 子组件接收父组件数据使用props即可
      props: {
        money: {
          type: Number,
          default: 0
        }
      },
      // props 父组件数据
      // emit 从上下文context中解构出emit,触发自定义事件的函数
    +  setup (props, {emit}) {
        // 获取父组件数据money
        console.log(props.money)
        // 向父组件传值
    +    const changeMoney = () => {
    +      emit('change-money', 50)
    +    }
    +    return {changeMoney}
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    5.2 Provide / Inject

    这里要值得一说的是Provide / Inject的应用,他们在3.x中得到了增强

    • provide() 和 inject() 可以实现嵌套组件之间的数据传递。 ==》深层传递
    • 这两个函数只能在 setup() 函数中使用。
    • 父级组件中使用 provide() 函数向下传递数据。
    • 子级组件中使用 inject() 获取上层传递过来的数据。
    • 不限层级 父组件:
    <template>
      <div>
        <provideAndInject />
      div>
    template>
     
    <script>
    import { provide } from "@vue/composition-api";  // 父组件引入 provide
    import provideAndInject from "./components/provideAndInject";  // 引入子组件
     
    export default {
      name: "app",
      components: {
        provideAndInject
      },
      setup() {
        // provide('数据名称', 要传递的数据)
        provide("customVal", "我是父组件向子组件传递的值"); 
      }
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    子组件

    <template>
      <div>
        <h3>{{ customVal }}h3>
      div>
    template>
     
    <script>
    // 子组件导入 inject
    import { inject } from "@vue/composition-api";
     
    export default {
      setup() {
        //调用 inject 函数,通过指定的数据名称,获取到父级共享的数据
        const customVal = inject("customVal");
     
        return {
          customVal
        };
      }
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5.3 Vuex引用

    在2.x版本对store对象的引用是通过this对象

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      state: {
        count: 0
      },
      mutations: {
        increment (state) {
          state.count++
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在3.x版本对store引用变为createStore方法

    import { createApp } from 'vue'
    import { createStore } from 'vuex'
    
    // Create a new store instance.
    const store = createStore({
      state () {
        return {
          count: 0
        }
      },
      mutations: {
        increment (state) {
          state.count++
        }
      }
    })
    
    const app = createApp({ /* your root component */ })
    
    // Install the store instance as a plugin
    app.use(store)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在组件中引用store对象

    import { useStore } from "vuex"
    
    setup(){
        const store = useStore();
        const storeData = computed(() =>store.state.counter)
    
        return{
            storeData
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    值得注意的是,我们在使用vuex大多会引用扩展运算符,如果使用扩展运算符根据目前官网vuex文档4.x版本 (opens new window),仍然需要在methods中进行处理

    代码组织能力

    在2.x版本中,data和methods代码过多的时候,会不容易维护,过多的属性和过多的方法无论是阅读还是维护都给开发者造成了很大的困扰。 在3.x版本中,我们可以更好组织代码,例子如下:

    我们分离一个数据处理文件helloworld.js

    import { ref,reactive } from "vue"
    export function article(){  
        const currentArt = ref("测试数据");
        const artLists = reactive({
            name:"标题",
            content:"内容"
        })
    
        function changeArt(){
            currentArt.value = "新测试数据"
        }
    
        return{
            currentArt,
            artLists,
            changeArt
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们可以在分离一个网络请求文件处理NetWork.js

    import { ref,reactive } from "vue"
    import axios from "axios"
    export function getBanner(){
        const netState = reactive({
            banner:{}
        })
    
        function http(url){
            axios.get(url).then(res =>{
                netState.banner = res.data
            })
        }
    
        return {
            netState,
            http
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们可以在主业务文件中如下引入:

    import { article } from "./HelloWorld";
    import { getBanner } from "./NetWork";
    
    export default {
      setup(props, ctx) {
    
        const { netState, http } = getBanner();
    
        onMounted(() => {
          http("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php")
        });
    
        const { currentArt, artLists, changeArt } = article();
    
        return {
          currentArt,
          artLists,
          changeArt,
          netState
        };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    状态管理

    Vuex是一个很棒的状态管理库,它简单易懂,且与Vue集成良好。为什么不直接使用Vuex?因为即将发布的Vue3公开了底层的响应式系统,并引入了构建应用程序的新方法。新的响应式系统功能强大,可用于共享状态管理。

    你需要一个共享状态吗

    在某些情况下,多个组件之间数据共享困难,以至于需要集中的状态管理。情况包括:

    • 多个组件使用相同数据
    • 多个根模块需要单独接入数据
    • 深层嵌套的组件需要与其它组件进行数据通讯

    如果上述情况都不存在,那么很容易做决定:你不需要它。 如果存在上述情况呢?最直接的答案就是使用Vuex,这是一个经过实战检验的解决方案,而且效果不错。 如果不想添加另一个依赖项或者不想做过多的配置?那么现在就可以与Composition API一起,用新的Vue3内置的方法解决这些问题。

    新的解决方案

    共享状态必须符合两个标准:

    • 响应式:当状态改变时,使用它们的组件也相应更新
    • 可用性:可以在任何组件中访问

    响应式

    Vue3对外暴露了其响应式系统的众多函数,你可以使用reactive函数创建一个reactive变量(也可以使用ref函数)

    import { reactive } from 'vue';
    
    export const state = reactive({ counter: 0 });
    
    • 1
    • 2
    • 3

    从响应式函数返回的对象是一个Proxy对象,它可以监听其属性的更改。当在组件模板中使用时,每当响应值发生变化时,组件就会重新渲染:

    <template>
      <div>{{ state.counter }}div>
      <button type="button" @click="state.counter++">Incrementbutton>
    template>
    
    <script>
      import { reactive } from 'vue';
      export default {
        setup() {
          const state = reactive({ counter: 0 });
          return { state };
        }
      };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可用性

    上面的示例非常适合单个组件,但多组件共享状态就不适用了。为了克服这个问题,你可以使用Vue3的provideinject

    import { reactive, provide, inject } from 'vue';
    
    export const stateSymbol = Symbol('state');
    export const createState = reactive({ counter: 0 });
    
    export const setCounter = (num) =>{
        createState.counter = num
    }
    
    export const useState = () => inject(stateSymbol);
    export const provideState = () => provide(
      stateSymbol, 
      createState
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    当你将Symbol作为键值传递给provide时,该值将通过inject方法使任何组件可用。键在提供和检索时使用相同的Symbol名称。
    在这里插入图片描述

    如上图,如果你在最上层的组件上提供值,那么它将在所有组件中都可用,或者你也可以在应用的主文件上调用provide

    import { createApp } from 'vue';
    import App from './App.vue';
    import { stateSymbol, createState } from './store';
    
    createApp(App).provide(stateSymbol, createState).mount('#app');
    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <p>{{ state.counter }}</p>
        <button @click="changeCounter">修改</button>
      </div>
    </template>
    <script>
    
    import { useState,setCounter } from "../store"
    export default{
      setup(){
    
        function changeCounter(){
          setCounter(100)
        }
    
        return{
          state:useState(),
          changeCounter
        }
      }
    }
    
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    通过使用Vue3的响应式系统和依赖注入机制,我们已经从局部状态管理变为全局状态管理,本方案可以在小型应用中取代Vuex。 但是如果大型项目或者有使用Vuex的特性的情况下,这种方案就不合适了,例如:你不知道谁做的状态更改,因为状态可以直接更改,不受任何限制。 所以基于此,大型项目仍然需要使用Vuex。

    5.5 无法使用EventBus

    在2.x中通过EventBus的方法来实现组件通信

    全局事件总线机制:基于自定义事件

    var EventBus = new Vue()
    Vue.prototype.$EventBus = EventBus
    ...
    this.$EventBus.$on()  
    this.$EventBus.$emit()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在3.x中移除了$on, $off等方法(参考rfc),而是推荐使用mitt方案来代替

    import mitt from 'mitt'
    const emitter = mitt()
    // listen to an event
    emitter.on('foo', e => console.log('foo', e) )
    // fire an event
    emitter.emit('foo', { a: 'b' })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 安装:

      npm i mitt -S
      
      • 1
    2. utils/mitter.js

      mitt库默认导出一个函数,充当事件总线对象==》EventBus

      import mitt from 'mitt'
      const emitter = mitt()
      export default emitter
      
      • 1
      • 2
      • 3
    3. 需要的组件导入

     import emitter from 'utils/mitter.js'
     
     //触发
     emitter.emit('事件名','参数')
    
    • 1
    • 2
    • 3
    • 4
    1. 监听的组件

      import emitter from 'utils/mitter.js'
      
      //监听
      emitter.on('事件名',(val)=>{
          // val
      })
      
      // 第二种写法: 第一个参数写成  *,监听所有的事件触发
          emmiter.on('*',(eventType,msg)=>{
            console.log(`监听到的事件类型:${eventType},参数:${msg}`);
          })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
  • 相关阅读:
    Visual Studio 2017多工程开发
    计算机基础(五)——Linux的bash.shell,help,man命令
    angular框架怎么从0到1进行开发
    vue3+vite项目在按需导入vant后, 打包构建时报错
    kubeadm系列-03-静态Pod的创建
    C++ partial_sort()排序函数用法详解(深入了解,一文学会)
    Java并发编程学习笔记6——共享模型之不可变
    浅谈tarjan算法
    SpringCloud-4-基础工程-创建服务提供者
    住宅IP与普通IP的区别
  • 原文地址:https://blog.csdn.net/smartboy_01/article/details/126911966