Vue3 学习笔记 —— (一)深入理解组合式 API

目录


image.png

[toc](Vue3 学习笔记梳理)
Author:Gorit
Date:2021/4/24
Refer:网易云课堂
2021年发表博文: 17/50

Vue3 学习

学习文档
如果是第一次接触 Vue3,可以看这个 Vue3 初体验

零、Vue3.0 与 Vue2.x 的性能对比

  1. 框架内部做了大量的性能优化,包括虚拟 DOM,编译模板、Proxy 的新数据监听,更小的打包文件等
  2. 新的组合式 API (composition-api),更适合大型项目的编写方式
  3. 对 TypeScript 支持更好,去除繁琐的 this 操作,更强大的类型推导

一、搭建环境

  1. node 8.9.0 以上
  2. 安装好 npm
  3. 安装 vue
  4. 安装 Vue Cli4 脚手架

二、创建项目

  1. vue create vue3-demo
image
  1. 其他的选择默认配置即可
  2. 安装 yarn :cnpm i yarn -g (提升安装速度)
  3. 测试:yarn --versin

项目结构介绍

  • package.json 项目全局管理
  • node_modules 项目依赖包,占用大量空间
  • public 入口文件
  • src:main.js 为入口文件,项目代码在这里编写

三、Vue3 Composition API

Vue3 是向下兼容 Vue2 API 的,但是 Vue3 中提供了一种全新的 <font color="red">Composition API</font>

3.1 ref() or setup() ? reactive()

setup() 作为 Vue3.0 的入口函数

reactive() 作声明式渲染,用来响应数据

ref() 显示响应式数据,配合 reactve()

3.1.1 非响应式数据显示 (reactive)

直接返回数据

<template>
    <div>直接返回 state【非响应式的数据】:{{count}}</div>
</template>

<script>
    // 如果是 Vue2 项目,想要用 Vue3 的语法,需要安装 @vue/composition-api   
    // import { xxx } from '@vue/composition-api'
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        // Vue3.0 的入口函数,编写 Vue3 代码,beforeCreate之前进行触发, 需要 return
        setup() { 
             const state = reactive({
                count: 0
             });
            // 不具备数据响应式的效果,1s 后数据没有任何变化
             setTimeout(()=> {
                state.count++;
             },1000)
             return state;
        }
    }
</script>

3.1.2 响应式数据显示 (reactive)

通过对象的形式

<template>
    <div>返回 state 对象 【响应式数据】{{state.count}}</div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        // Vue3.0 的入口函数,编写 Vue3 代码,beforeCreate之前进行触发, 需要 return
        setup() { 
            // 具备数据响应式, 返回对象
            const state = reactive({
                count: 0
            });
            setTimeout(()=> {
                state.count++;
            },1000)
            return { state };
            
            // 对返回的结果 解构,这样数据就可以只显示 {{ count }} 就可以了
             // return {
            //  count: state.count
            //}
        }
    }
</script>

3.1.3 响应式数据展示(整合 ref() )

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>ref 实现响应式对象 {{ count }}</div>
</template>

<script>
    import {
        ref
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // 使用 ref() 实现响应式数据
            const count = ref(0); // 这样 count 就实现了响应式的效果
            setInterval(()=> {
                count.value++;
            },1000)
            return {
                count
            };
        }
    }
</script>

因此,我们既要响应式,又要展示数据,折中的方案是直接使用 ref

3.1.4 小案例:实现一个计数器

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div class="my-app">
        <!-- Vue3 无法直接拿到值 -->
        <h3>{{counter}}</h3>
        <button @click="increment(1)">increment</button>
        <button @click="decrement(1)">decrement</button>
    </div>
</template>

<script>
    import { ref } from 'vue'
    /**
     * Options API Vue2 Class
     * Composition API Vue3 Function
     */
    export default {
        name: 'App',
        setup() {
            const counter = ref(1000)
            // console.log(counter.value)
            const increment = () => {
                counter.value += 1;
            }
            
            const decrement = () => {
                counter.value -= 1;
            }
            // 必须 return,外部才能拿到值
            return { counter, increment,decrement }
        }
    }
</script>

3.2 toRefs() ? toRef()

在上面的代码中,我们使用 ref() 和 reactive() 分别可以实现响应式的数据,我们是否可以两者一起使用呢?

3.2.1 ref() 和 reactive() 连用

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>ref() 和 reactive() 实现响应式对象 {{ count }}</div>
</template>

<script>
    // 如果是 Vue2 项目,想要用 Vue3 的语法,需要安装 @vue/composition-api   
    // import { xxx } from '@vue/composition-api'
    import {
        ref,
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // ---------------------------- ref 和 reactive 连用
            const count = ref(0);
            const state = reactive({
                count
            })
            setInterval(()=> {
                state.count++;
            },1000)
            return {
                count
            };
        }
    }
</script>

3.2.2 使用 toRefs() 和 reactive()

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>实现响应式对象 {{ count }}</div>
</template>

<script>
    import {
        reactive,
        toRefs
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // ---------------------------- //使用 toRefs() 将 reactive 转换为 Ref
            const state = reactive({
                count: 0
            });
            
            const { count } = toRefs(state); // toRefs() 作用:将普通类型数据,转换为 Ref 响应式数据
            setInterval(()=> {
                state.count++;
            },1000)
            return {
                count
            };
        }
    }
</script>

3.2.3 使用 toRef() 和 reactive()

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>实现响应式对象 {{ count }}</div>
</template>

<script>
    import {
        reactive,
        toRefs
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // ---------------------------- //使用 toRefs() 将 reactive 转换为 Ref
            const state = reactive({
                count: 0
            });
            
            const  count  = toRef(state, 'count'); // toRef() 作用:将普通类型数据,转换为 Ref 响应式数据,指定单个数据转换
            setInterval(()=> {
                state.count++;
            },1000)
            return {
                count
            };
            // toRefs === {}
        }
    }
</script>

ref 和 reactive 分别是两种响应式数据的变量风格,具体看个人情况使用

3.3 computed 计算属性

3.3.1 配合 ref() 使用 实现响应式

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>
        {{count}} , {{double}}
    </div>
</template>

<script>
    import {
        ref,
        computed
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // 使用计算属性
            const count = ref(1)
            const double = computed(()=>count.value * 2)
            setTimeout(()=>{
                count.value++
            },1000)
            return {
                count,double
            }
        }
    }
</script>

3.3.2 配合 toRefs() 和 reactive() 实现响应式

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>
        {{count}} , {{double}}
    </div>
</template>

<script>
    import {
        reactive,
         toRefs,
        computed
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            const state = reactive({
                count: 1,
                double: computed(()=>state.count * 2)
            })
            setTimeout(()=>{
                state.count++
            },1000)
            return toRefs(state)
        }
    }
</script>

3.4 定义函数

3.4.1 函数与 watch

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <div>
        事件:{{count}}
    </div>
    <button @click="add">添加</button>
</template>

<script>
    import {
        ref,
         watch
    } from 'vue'
    export default {
        name: 'App',
        setup() { 
            // =============== 事件
            const count = ref(1)
            const add = () => { // 事件方法
                count.value++;
            }
            // 配合侦听器
            watch(count ,(count, prevCount) => {
                console.log(count, prevCount)
            })
            return {count, add}
        }
    }
</script>

3.4.2 Vue3.0 函数与生命周期函数

文档

新的生命周期函数钩子要在 setup() 中使用

  1. onMounted
  2. onUpdated
  3. onUnmounted
export default {
  setup() {
    // mounted
    onMounted(() => {
      console.log('Component is mounted!')
    })
  }
}

四、Vue3 组件化(拆分+传值+注册)

这里主要是回顾 组件化编程

拆分的方式同 Vue2,注册 + 引入
组件拆分的案例我们沿用上面的计数器来实现 (参考 3.1.4 小节的内容)

4.1 组件拆分

预览效果:


在这里插入图片描述

编码如下:

  1. 定义一个新的 vue 文件,命名为 CounterView.vue 文件
  2. 使用 props 接受父组件 App.vue 穿过来的值
<template>
  <div>
      我是子组件 CounterView
      {{counter}}
  </div>
</template>

<script>
export default {
    name: 'CounterView',
    // props: ['counter']  vue2 一般都是这么写的
    // 下面的这种写法会更加规范
    props: {
        counter: {
            type: Number,
            required: true,
            default: 500
        }
    }
}
</script>
  1. 修改父组件 App.vue,改为引用 CounterView.vue ,并注册为组件
<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png" />  
    <!-- 属性绑定 -->
    <counter-view :counter="counter"/>
    <button @click="increament(1)">增加</button>
    <button @click="increament(-1)">减少</button>
  </div>
</template>

<script>
import { ref } from "vue";
import CounterView from '@/components/CounterView.vue'
export default {
  name: "App",
  components: {
    CounterView
  },
  setup() {
    const counter = ref(1000);
    console.log(counter.value);
    // 定义函数
    const increament = (num) => {
      counter.value += num;
    };
    return { counter,increament };
  },
};
</script>

<style>
</style>

4.2 事件拆分

这里我们在上面的基础上,将 setup() 中定义的事件,拆分至另一个新的 vue 文件
首先我们需要补充一些前置概念:

  1. 在 setup() 中是没有 this 关键字的
  2. setup() 是可以接受两个参数的 (props, context),然后我们打印接受到的值如下


    在这里插入图片描述
  3. cotext 中,可以看到 emit 关键字,是不是很熟悉,vue2 中我们要子组件传事件给父组件,用的是 this.$emit("事件名称", '值"), 在 Vue3 中也会用到类似的,后面会有具体的演示
  4. 编码如下:

在子组件完成事件注册

<template>
  <div>
    <button @click="increament(1)">增加</button>
    <button @click="increament(-1)">减少</button>
  </div>
</template>

<script>
export default {
    name: 'CounterController',
    setup(props,ctx) { 
       console.log(props, ctx)      // 这里打印的就是我们刚刚上面看到的
       const increament = (num) => {
            // ctx.emit 等价于 vue2 中 this.$emit("xxx",xxx)。 在 vue3 中 setup() 函数是没有 this 的概念的
            ctx.emit("onIncreament",num) // 完成事件注册,将操作的逻辑交给父组件来完成
        }
        return {increament}
    },
}
</script>

在父组件完成事件调用

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png" />  
    <!-- 属性绑定 -->
    <counter-view :counter="counter"/>
    <!-- <button @click="increament(1)">增加</button>
    <button @click="increament(-1)">减少</button> -->
    <!-- 子组件事件注册完毕后,交给父组件进行触发, 处理的函数需要传 $event 就可以实现和上面一样的效果了 -->
    <counter-controller @onIncreament="increament($event)" />
  </div>
</template>

<script>
import { ref } from "vue";
import CounterView from '@/components/CounterView.vue'
import CounterController from '@/components/CounterController.vue'
export default {
  name: "App",
  components: {
    CounterView,
    CounterController
  },
  setup() {
    const counter = ref(1000);
    // 定义函数
    const increament = (num) => {
      counter.value += num;
    };
    return { counter,increament };
  },
};
</script>

五、总结

我们来回顾一下所学内容

  1. setup(props, context), setup() 是代码的入口
  2. 响应式 API:reactive() 、ref()、toRef()、toRefs()
  3. 计算属性 computed 和 watch 侦听器的使用
  4. 组件化思想
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容