vue3教程

目的

本篇教程励志用简单的例子来教会你理解并使用常用vue3的新特性,适合于从vue2转vue3的码友,本文请按顺序阅读,否则可能会出现需要查找解释的情况。

响应性api

在vue3.x中如果想让数据在改变时,视图层也相应发生变化就需要使用到响应性api。

想创建一个响应式数据,归根结底就两种方法 refreactive

ref

接受一个内部值并返回一个响应式且可变的 ref 对象,该对象仅有一个value属性指向内部值, 可以用来读取和更改。
    const num = ref(0);
    console.log(num.value) // 0
    num.value++; // 1

这样就可以创建一个具有响应性的数据,当然ref也可以接收数组、对象、其他基础类型的值,但我们通常不建议在ref中传递对象,因为响应式转换是深层的,它会影响所有它嵌套的属性,而我们应该尽量避免依赖原始对象。

reactive

返回一个对象的响应式副本。
    const introduce = reactive({
        name: '喵十一',
        age: 19,
        hobby: ['编程', '游戏', '运动']
    })
    console.log(introduce.name) // 喵十一
    introduce.name = "十一"
    console.log(introduce.name) // 十一

这样会返回一个具有相应式的对象副本,但我们不会在reactive中传递基础类型数据,原因请见下方。

ref 与 reactive的对比

可传递类型

ref
可传入基本类型和引用数据类型, 但不推荐传入对象
reactive
可传入对象/数组/json, 但不可以传入基本类型数据, 如果传入则不会返回proxy对象。
由于 reactive是基于proxy实现的响应式,所以如果返回的不是一个proxy对象则该数据不会具有响应式。
即修改数据也不会使得视图层发生变化。

修改方式

ref
.value的方式进行修改
reactive
直接修改

reactive注意事项

如果reactive传入的对象中某一属性是用ref创建的响应式数据,那它将被自动解包。
本条极其重要。
    const introduce = reactive({
        name: '喵十一',
        age: ref(19),
        hobby: ['编程', '游戏', '运动']
    })
    console.log(introduce.age) // 19
    console.log(introduce.age.value) // undefined
    console.log(introduce.age.value = 1) // error
个人对解包的理解是变为了一个普通的属性,然后由于reactive的缘故又使整个对象具有了响应性

ref 和 reative的总结

通常情况下ref用于为基本类型创建响应性,reactive为引用类型创建响应性。

组合式api

在解释这个名词之前,让我们先来了解一下vue2的选项式api的缺点

  1. 业务逻辑分散在各个选项中(methods、watch、data等),要修改某一功能时可能需要连续去好几个选项中修改,不便于维护与管理
  2. 代码冗长时不方便快速定位某一业务
  3. 会使得我们不断地在选项之间来回“跳转”
组合式api,顾名思义,就是将不同的业务逻辑拆分成一块块的像搭积木般构建起我们的业务。

这样就近乎完美的避开了vue2中选项式api的缺点

例子

我们使用一个极其简单的数量框的例子来介绍组合式api

 <template>
    <div class="combinationApi">
        <div class="num">
            <button @click="changeNum('-')">-</button>
            <button>{{num}}</button>
            <button @click="changeNum('+')">+</button>
        </div>
        <button @click="resetNum">重置</button>
    </div>
</template>
<script>
import changeButton from "../composables/changeButton";
import resetButton from "../composables/resetButton";
import {ref} from "vue";
export default {
    name: "combinationApi",
    props: {
        name: {
            default: "123",
            type: String
        }
    },
    setup(props) {
        // 创建一个响应式的数据
        let num = ref(0);
        // 使用组合式api
        let { changeNum } = changeButton(num);
        let { resetNum } = resetButton(num);
        // 返回需要的内容
        return {
            num,
            changeNum,
            resetNum
        }
    }
}
</script>

可以看到我们在script中引入了两个js文件,changeButtonresetButton并在setup中使用了它们。

changeButton的功能是将数量更改。

resetButton的功能是将数量重置。

我们先来看一下这两个文件

changeButton

    import { ref, reactive } from "vue";
    export default function changeButton(num) {
        // 改变数量事件
        let changeNum = (type)=>{
            type == "+" ? num.value++ : type == "-" && num.value > 0 ? num.value-- : '';
        }
        return {
            changeNum
        }        
    }

resetButton

    export default function changeButton(num) {
        // 重置按钮数量
        let resetNum = ()=>{
            num.value = 0;
        }
        return {
            resetNum
        }        
    }

功能非常的简单,他们都接收了一个参数 num,都返回了一个对 num 进行操作的方法。
这就是一个简易的组合式api,整个页面的逻辑就像搭积木一样一块一块的构建了起来,方便以后修改逻辑,找起来也不会麻烦。

setup

setup函数会接受两个参数,propscontext

props

props是具有响应性的,当有新的prop传入时,它将会被更新。
正因为它具有响应性所以请不要使用结构的方式使用它,这样会失去响应性。

context

context是一个普通的js对象,这里面接收了剩余我们可能在setup中使用的值,因为是一个普通的js对象,咱们是可以放心使用结构的方式使用它

setup中可访问property

  1. props
  2. attrs
  3. slots
  4. emit
  5. expose

setup中可返回的类型

对象

    <template>
        <div class="setup">
            {{num}}
        </div>
    </template>
    <script>
        export default {
            props: {
                name: {
                    default: "123",
                    type: String
                }
            },
            setup(props, context) {
                let num = ref(0); // 该数据将在暴漏出去时自动解包
                // 暴漏给template
                return {
                    num                
                }
            }
        }
    </script>
该返回的对象中的property 和 props中的property,都可在模板中直接使用,

其中的refs将会被自动解包, 所以在模板中访问时不需要在使用.value了

渲染函数

    <script>
        // 解构出创建渲染函数的h方法
        import {h} from "vue";
        export default {
            props: {

            },
            setup(props, context) {
                // 一个小姐姐的图片链接
                let img = "https://img1.baidu.com/it/u=3149018235,1913956841&fm=253&fmt=auto&app=138&f=JPEG?w=231&h=500"            
                // 暴漏给template 一个能创建img的渲染函数
                return ()=>h('img', {src: img});
            }
        }
    </script>

本照片为百度网络资源,仅作展示:

vue3教程_第1张图片

这里返回的渲染函数将自动渲染在页面上。

查看更多渲染函数相关知识请点击这里

setup中的this

由于setup会在其他组件选项之前解析,这使得setup中的this和其他组件选择中的this完全不同,

因此在setup中使用this,因为不仅访问不到其他选项式api,还会造成和它们之间的混肴。

setup语法糖

想使用setup语法糖非常简单,只需要在script标签上加上setup

    <script setup>
    ....
    </script>

接下来我们来讲一下setup的语法糖的好处

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 Typescript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

顶层的绑定会被暴露给模板

不需要再将数据抛出, 在此语法糖中创建的变量可以直接在模板中访问, ref创建的数据依然会被解包。

    <template>
        <div>{{name}}</div>
    </template>
    <script setup>
        import {ref} from vue;
        let name = ref('喵十一')
    </script>

使用组件

引入的组件可以直接使用,不需要再次注册, 且引入的名称可以直接当自定义标签来使用。

    <template>
        <button></button>
    </template>
    <script setup>
        import button from '@/component/button';
    </script>

当然从单文件件中引入多组件

<template>
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>
<script setup>
import * as Form from './form-components'
</script>

自定义指令

全局自定义指令可以直接使用,但如果在本地自定义指令则需要使用vNameDirective的方式来命名。

例如:

    <template>
      <div v-msy-directive>This is a Heading</div>
    </template>
    <script setup>
    const vMsyDirective = {
        // 代码
    }
    </script>

defineProps 和 defineEmits

如果在setup语法糖中想使用像普通的 script 标签中的 props 和 emits, 则需要使用 defineProps 和 defineEmits来声明

    <template>
      
    </template>
    <script setup>
        let props = defineProps({
            name: {
                default: "喵十一",
                type: String
            }
        });
        let emits = defineEmits(['change']);
    </script>

definePropsdefineEmits 仅在 setup 语法中使用且不需导入

defineExpose

用来在 setup 语法糖中确认要暴漏出去的属性,和前两个方法一样不需要导入。

useSlots 和 useAttrs

useSlotsuseAttrs 是真实的运行时函数,它会返回与 setupContext.slotssetupContext.attrs 等价的值,同样也能在普通的组合式 API 中使用。

顶层await

需要和Suspense组合使用,但Suspense还在测试阶段,因为本文不做更多介绍,想要了解更多的可以去查询一下

仅 ts 的功能

仅限类型的 props/emit 声明

props 和 emits 都可以使用传递字面量类型的纯类型语法做为参数给 definePropsdefineEmits 来声明:

const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
  • definePropsdefineEmits 只能是要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。

  • 使用类型声明的时候,静态分析会自动生成等效的运行时声明,以消除双重声明的需要并仍然确保正确的运行时行为。

    • 在开发环境下,编译器会试着从类型来推断对应的运行时验证。例如这里从 foo: string 类型中推断出 foo: String。如果类型是对导入类型的引用,这里的推断结果会是 foo: null (与 any 类型相等),因为编译器没有外部文件的信息。
    • 在生产模式下,编译器会生成数组格式的声明来减少打包体积 (这里的 props 会被编译成 ['foo', 'bar'])。
    • 生成的代码仍然是有着类型的 Typescript 代码,它会在后续的流程中被其它工具处理。
  • 截至目前,类型声明参数必须是以下内容之一,以确保正确的静态分析:

    • 类型字面量
    • 在同一文件中的接口或类型字面量的引用

    现在还不支持复杂的类型和从其它文件进行类型导入。理论上来说,将来是可能实现类型导入的。

使用类型声明时的默认 props 值

仅限类型的 defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。为了解决这个问题,提供了 withDefaults 编译器宏:

interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

上面代码会被编译为等价的运行时 props 的 default 选项。此外,withDefaults 辅助函数提供了对默认值的类型检查,并确保返回的 props 的类型删除了已声明默认值的属性的可选标志。

限制

由于

你可能感兴趣的:(教程,vue,javascript,前端,vue3,前端教程)