vue3基础知识剖析

目录
  • 声明
  • vue3.0有哪些新特性
  • vue3.0的优缺点
  • 如何解锁vue3.0
    • 体验vue3.0的4中姿势
    • 核心的composition API
    • setup
    • setup语法糖
    • ref、reactive
    • watch跟watchEffect
    • computed(计算属性)
    • 组件通信
    • props
    • emit
    • 插槽
    • vue2中的使用
    • vue3中的使用
    • 生命周期
    • vue-router 4.0
    • Composition API
    • 路由守卫
    • keep-alive 和 transition 必须用在 router-view 内部
    • style新特性
    • 深度选择器
    • 全局选择器
    • 通过module自定义注入名称
    • 与组合式 API 一同使用
    • Typescript基础&项目中如何使用Typescript
    • 基本的数据类型
    • 枚举
    • 元祖
    • 任意值 Any
    • 空值 void
    • interface
    • 函数参数类型与返回值类型
    • 泛型
    • 交叉类型
    • 联合类型
    • 函数重载
    • vue3项目中如何集成TS
    • 状态管理Pinia

前言

前段时间,由新东方出品直播带货品牌东方甄选火爆全网,其中最受大家关注的主播董宇辉,用网友的调侃来说“长着一个颗粒无收的脸,却拥有五谷丰登的灵魂”。他在直播中推荐五常大米时说:“厨房里充满了饭香,就是人间浪漫。”,介绍水蜜桃:“这个水蜜桃,美好的像穿越大峡谷的风,像仲夏夜的梦”。卖牛排,告诉观众这是“Original Cutting”。让网友赞叹不绝,大家都说这买的不是吃的,买的是知识付费,买的是灵魂洗礼。最重要的是,他尽然还是一个英语老师。很多人感叹,好好读书太重要了,因为知识能给人带来力量,带来高贵的灵魂。相比那些快节奏、声嘶力竭、充满商业诱导的的直播模式,简直就是降维打击

从笔者的角度来看,董宇辉的成功并非偶然,能够饱读诗书,一定源于自己多年不断的思考跟总结,不断的追求学习的本质才能让自己在无意之间沉淀的像个诗人,像个哲学家。这背后的付出,常人肯定无法想象。作家周岭说过“所谓的学习,不是努力,努力,在努力。而是反馈,反馈,再反馈。光靠一味的输入,而不输出,这种学习大概率是低效率的”。就像咱们前端技术圈一样,框架层出不穷,版本迭代快的让人无法喘息。很多小伙伴都焦虑的呐喊,学不动了。笔者认为,真正高效的学习一定是需要在输入的同时,要有很好的输出,让自己积累更多的正向反馈,就像我们平时学习某一种技术栈一样,光是一味的学习不行,还要做出高质量的实践跟输出才行!

笔者这篇文章会从vue3基础的知识点开始剖析,特别是在将composition API的时候,在代码示例中不会一上来就使用setup语法糖,而是用早期的setup函数,这样方便于初学的小伙伴们理解跟学习。文章篇幅较大,接下来,请您花个10分钟耐心的看完,或许会有不一样的收货。

声明

  • 本文中下边所有的示例代码都可以直接访问这个网站点击这里查看效果需要源代码的小伙伴可以在评论区下留言,或者私信我。
  • 本文章的讲解的所有实例面向对vue3的初学者,如有讲解不到位,或者有偏差的地方,欢迎大家留言指出。

vue3.0有哪些新特性

  • Composition Api (最重要的新特性)
  • 组件通信
  • 生命周期
  • 自定义Hook
  • 插槽
  • v-model的更改
  • 更加纯粹的Tree-shaking
  • 配合状态管理的Pinia
  • 配合升级的vue-router 4.x
  • 配合升级的打包工具vite
  • 配合TS在项目中自由使用

vue3.0的优缺点

优点

  • 使用vue3最大的优势个人认为倒不是它的Api,而是配合使用的vite打包工具,特别是大型项目本地启动要比当前的webpack5要快至少2倍以上(项目中测试过)
  • 比起vue 2.xComposition Api的优势要明显的多,如果习惯了setup语法糖的写法,你会发现爽的飞起,很多之前在vue 2.x中大量重复逻辑不存在了
  • 底层通过Proxy来实现双向绑定,性能上提升了很多
  • TypeScript支持度更好,可以很愉快的在项目中使用TypeScript

缺点

  • 如果还有IE情节的公司,那vue3确实不太适合,因为vue3已经抛弃了对IE11的支持,再说了 微软人家自己都不打算维护IE了,兄弟们,放弃IE拥抱chrome吧!
  • Composition Api的写法需要花一点点时间来适应,毕竟学习新语法还是需要成本的

如何解锁vue3.0

体验vue3.0的4中姿势

  • 通过CDN
<script src="https://unpkg.com/vue@next"></script>
  • npm
# 最新稳定版
npm install vue@next
npm install -D @vue/compiler-sfc

如果你是从Vue 2.x升级的,请注意 @vue/compiler-sfc 替换掉了 vue-template-compiler

  • vue-cli
npm install -g @vue/cli
vue upgrade --next
  • vite
npm init vite@latest <project-name> -- --template vue
cd <project-name>
npm install
npm run dev

推荐使用第4种方式,直接使用官方推荐最新的vite打包工具,直接初始化项目。

核心的composition API

setup

  • setupvue3提出的一个非常重要的选项,也是Composition Api最为核心的语法之一。
  • setup执行时机是在beforeCreate之前执行的。
  • setup返回的是一个对象,对象中的所有属性都是可以在template中使用
  • setup中不能使用this
  • setup中注册生命周期onMountedwatchcomputed等,我们会在下边详细讲解

setup参数

  • props
  • context
<script>
export default {
  setup (props, context) {
    return {}
  }
}
</script>

setup语法糖

既然上边提到了setup语法,那就有必要把setup语法糖介绍一下,我们在实际的项目开发中在熟悉了setup语法的本质后,也推荐大家使用setup语法糖来编写,这样也可以大大提升开发效率。

  • 不需要像上述一样return,只需要在<script setup>中声明一下即可
  • 任何在 <script setup> 声明的顶层的绑定 (包括声明的变量,函数声明,以及 import 引入的内容) 都可以在模板中直接使用
  • 组件在语法糖中可以自动注册,无需再通过components进行注册
<script setup>
import {ref} from 'vue'
let property = ref('这里是响应式属性');
// 这里我们引入了子组件SetUp.vue
import SetUp from '@/components/SetUp.vue'
</script>

ref、reactive

  • refreactive都是vue3中用来做数据定义使用的,如同vue2中在data中做数据定义一样,示例代码如下:
<template>
    <h3>{{ state.count }}</h3>
    <h3>{{ num }}</h3>
    <el-button @click="handleAdd" type="primary">ref计算</el-button>
</template>

<script>
import { ref, reactive } from 'vue'

export default {
    setup() {
        const num = ref(0)
        const state = reactive({ count: 1 })
        function handleAdd() {
            state.count++;
            num.value += 2;
        }
        return {
            state,
            num,
            handleAdd
        }
    }
}
</script>
  • refreactive的区别在哪呢?很多人分不清楚,网上有很多文章简单的定义为ref负责处理基本数据类型的双向绑定,reactive负责处理对象的双向绑定。其实,这样笔者会觉得给很多初学者带来很多误导,其实ref也可以处理对象的双向绑定,就像下边这段代码一样。
<template>
    <el-button @click="handleAdd" type="primary">ref计算</el-button>
    <h3>{{ obj.count }}</h3>
</template>

<script>
export default {
    setup() {
        // ref 对象双向绑定
        const obj = ref({ count: 1 })
        function handleAdd() {
            obj.value.count = obj.value.count + 1
        }
        return {
            obj,
            handleAdd
        }
    }
}
</script>

watch跟watchEffect

watchEffect

  • 当传入一个函数时,可以响应式的自动收集依赖,当依赖变更时重新运行该函数;
  • 使用是需要配置flush: post,否则依赖在监听时无法被立即更新
  • 也可以使用stop来立即停止对函数的监听
<template>
    <div ref="root">This is a root element</div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
    setup() {
        const root = ref(null)
        watchEffect(() => {
            console.log(`watchEffect监听:${root.value}`);
        }, {
            flush: 'post'
        })
        return {
            root
        }
    },
}
</script>

watch

watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生变化时被调用。

watchEffect 相比,watch

  • 是一个返回任意值的getter函数
  • 是一个包装的对象,可以是ref对象、也可以reactive对象
  • 可以同时监听多个数据源
  • 监听是需要配置deep: true,否则回调函数无法被触发
<template>
    <h3>监听单个数据源1:{{state1.count}}</h3>
    <button @click="handleWatchSingle1">watch监听测试1</button>
    <h3>监听单个数据源2:{{state2}}</h3>
    <button @click="handleWatchSingle2">watch监听测试2</button>
    <h3>监听复杂对象数据源:{{state3.player}}</h3>
    <button @click="handleWatchSingle3">watch监听测试3</button>
</template>
<script>
import {ref, reactive, watch} from 'vue'

export default {
    setup() {
        const state1 = reactive({ count: 1 })
        const state2 = ref(0)
        const state3 = reactive({
            player: {
                name: 'James',
                achievement: ['4次NBA常规赛mvp', '03年选秀状元', '4次NBA总冠军']
            }
        })
        watch(() => state1.count, (newVal, oldVal) => {
            console.log('watch监听reactive中的newVal:', newVal);
            console.log('watch监听reactive中的oldVal:', oldVal);
        })
        watch(() => state2.value, (newVal, oldVal) => {
            console.log('watch监听ref中的newVal:', newVal);
            console.log('watch监听ref中的oldVal:', oldVal);
        })
        watch(() => state3.player, (newVal, oldVal) => {
            console.log('watch监听复杂对象中的newVal:', newVal);
            console.log('watch监听复杂对象中的oldVal:', oldVal);
        }, {
            deep: true,
            // immediate: true
        })
        // 同时监听多个值
        // watch([() => state1.count, state2.value], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
        //     console.log('watch监听中的newVal:', newVal1, newVal2);
        //     console.log('watch监听oldVal:', oldVal1, oldVal2);
        // })
        function handleWatchSingle1() {
            state1.count++
        }
        function handleWatchSingle2() {
            state2.value++
        }
        function handleWatchSingle3() {
            state3.player = {
                name: 'Wade',
                achievement: ['3次NBA总冠军', '曾经的热火三巨头之一', '1次NBA总决赛mvp']
            }
        }
        return {
            state1,
            state2,
            state3,
            handleWatchSingle1,
            handleWatchSingle2,
            handleWatchSingle3
        }
    },
}
</script>

computed(计算属性)

  • 接受一个 getter 函数,并根据getter 的返回值返回一个不可变的响应式 ref 对象。
  • 接受一个具有 getset 函数的对象,用来创建可写的 ref 对象
<template>
    <div style="margin-top:30">
        <h3>computedNum值为:{{computedNum}}</h3>
        <h3>computedNum2值为:{{computedNum}}</h3>
        <button @click="handleComputed">computed计算测试</button>
    </div>
</template>

<script>
import { ref, computed } from 'vue'

export default {
    setup() {
        const state = ref(1)
        const computedNum = computed(() => {
            return state.value + 1
        })
        console.log('computed缓存后的值:', computedNum.value);
        // 只可读属性,不可写,会抛出警告 Write operation failed: computed value is readonly
        function handleComputed() {
            computedNum.value++
        }
        const computedNum2 = computed({
            get: () => state.value + 2,
            set: val => {
                count.value = val - 0
            }
        })
        return {
            computedNum,
            computedNum2,
            handleComputed
        }
    },
}
</script>

组件通信

组件通信这块跟vue2的区别不大,我们就拿常用的props跟emit来讲解一下。

props

  • 父级组件向子组件传递数据

emit

  • 子组件想父组件传递数据
  • 需要通过emits选项来定义组件可触发的事件

父组件

<template>
   <Children :msg1="msg1" :msg2="msg2" @childClick="handleClick" />
</template>
<script>
import {ref, reactive} from 'vue';
import Children from './children.vue'

export default {
    setup() {
        const msg1 = ref('给子组件传递的消息1')
        const msg2 = reactive({
            name: '给子组件传递的消息2'
        })
        return {
            msg1,
            msg2
        }
    },
    methods: {
        handleClick(val) {
            console.log('接收子组件emit过来的数据:', val);
        }
    },
    components: { Children }
}
</script>

子组件

<template>
    <div style="margin-top: 30px">props传递给子组件的消息:{{ msg1 }}</div>
    <button @click="$emit('childClick', 6666)" style="margin-top: 30px">向父组件emits事件</button>
</template>
<script>
export default {
    props: ['msg1', 'msg2'],
    emits: ['childClick'],
    setup(props) {
        console.log('子组件接收父级组件传递过来的消息:', props);
    },
}
</script>

插槽

vue2中的使用

子组件

<template>
    <slot name="title"></slot>
</template>

父组件

<template slot="title">
    <h2>周岭:《认知觉醒》</h2>
<template>

vue3中的使用

vue3插槽中提供了v-slot:name 写法,我们就拿作用域插槽来举例

子组件

我们定一个可循环的插槽content

<template>
    <!-- <slot name="title"></slot> -->
    <div v-for="(item, index ) in items" :key="index">
        <slot :item="item" name="content"></slot>
    </div>
</template>

<script setup>
import {ref} from 'vue';
const items = ref(['认知觉醒', '认知驱动']);
</script>

父组件

父组件中可以有两种方式来引入子组件中的插槽,其一是通过v-slot:content="scopend"的方式,其二是通过简写#content="{item}"的方式

<template>
    <SlotChild>
        <!-- <template v-slot:content="scoped">
            <div>{{ scoped.item }}</div>
        </template> -->

        <template #content="{item}">
            <div>{{ item }}</div>
        </template>
    </SlotChild>
</template>

<script setup>
import SlotChild from './SlotChild.vue'
</script>

生命周期

vue3的声明周期如果是使用选项性Api的话,原来的生命周期钩子可以照常使用,那如果选用vue3组合式Api的话,生命周期需要通过import引入的方式在setup中调用。下图是vue3跟vu2声明周期的区别

<template>
    <div id="test">
        <h3>{{ counter }}</h3>
        <button @click="handleClick">声明周期测试</button>
    </div>
</template>

<script>
import {
    ref,
    onMounted,
    onBeforeMount,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnmount,
    onUnmounted
} from 'vue'
export default {
    setup() {
        const counter = ref(0);
        console.log('....');
        function handleClick() {
            counter.value += 1;
        }
        onBeforeMount(() => {
            console.log("组件挂载之前");
        });
        onMounted(() => {
            console.log("DOM挂载完成");
        });
        onBeforeUpdate(() => {
            console.log("DOM更新之前", document.getElementById("test").innerHTML);
        });
        onUpdated(() => {
            console.log("DOM更新完成", document.getElementById("test").innerHTML);
        });
        onBeforeUnmount(() => {
            console.log("实例卸载之前");
        });
        onUnmounted(() => {
            console.log("实例卸载之后");
        });
        return {
            counter,
            handleClick
        }
    },
}
</script>

vue-router 4.0

vue-router 3.x跟vue-router 4.x比起来写法上的区别

vue-router 3.x

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'

Vue.use(Router)

const router = new Router({
  routes
})
export default router

// main.js
import Vue from 'vue'
import router from './router'
// ...

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

vue-router 4.x

// router/index.js
import { createRouter } from 'vue-router'
import routes from './routes'

const router = createRouter({
  history: createWebHistory(), // history模式
  routes
})

// main.js
import { createApp } from 'vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')
  • new Router()改成createRouter()
  • mode: 'history'改成 history: createWebHistory()

Composition API

useRouter、useRoute

通过useRouter进行路由跳转

<template>
  <div class="mg30">
    <el-button @click="handleJump" type="primary">关于我们</el-button>
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

const handleJump = (query) => {
  router.push({
    name: "about",
    query: {
      id: 1
    }
  })
}
</script>

通过useRoute来获取传递过来的id

<template>
    <div>关于我们</div>
</template>

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
console.log('id>>>', route.query.id);
</script>

路由守卫

全局守卫

/router/index.js

详情页面meta中添加登录标识needLogin

let routes = [
    {
        path: '/detail',
        name: 'detail',
        component: () => import('@/views/detail.vue'),
        meta: {
            needLogin: true
        }
    }
]

main.js

添加守卫

import router from './router'

// 全局路由守卫
router.beforeEach((to, from) => {
    if (to.meta.needLogin) {
        return {
            name: 'login'
        }
    }
})

路由独享守卫

/router/index.js

let routes = [
    {
        path: '/category/:id',
        name: 'category',
        component: () => import('@/views/category.vue'),
        beforeEnter: (to, from) => {
            // 如果不是正确的分类,跳转到NotFound的页面
            console.log('id>>>>', to.params.id);
            if (!["0", "1", "2"].includes(to.params.id)) {
              return {
                name: "NotFound",
                // 这个是在地址栏保留输入的信息,否则地址栏会非常的丑
                params: { pathMatch: to.path.split("/").slice(1) },
                query: to.query,
                hash: to.hash,
              };
            }
        }
    }
]

组件内部守卫

<template>
    <div>关于我们</div>
</template>

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 页面内部的路由守卫
onBeforeRouteLeave((to, from) => {
    const answer = window.confirm('是否确认离开')
    if (answer) {
        console.log('不离开');
        return false
    }
})

// 对于一个带有动态参数的路径 /category/:catId,在 /category/1 和 /category/2 之间跳转的时候, 会触发onBeforeRouteUpdate的路由钩子函数,在钩子函数中可以进行数据的更新。
onBeforeRouteUpdate((to, from) => {
     console.log('to>>>', to);
     console.log('from>>>', from);
    // if (to.params.id !== from.params.id) {
    //     userData.value = await fetchUser(to.params.id)
    // }
})
</script>

keep-alive 和 transition 必须用在 router-view 内部

// vue-router 3
<keep-alive>
 <router-view />
</keep-alive> 

// vue-router 4
<router-view v-slot="{component}">
 <keep-alive>
  <component :is="component" />
 </keep-alive>
</router-view> 

style新特性

跟vue2不同的是,vue3中提供了提供了很多不同的选择器方便我们在样式编写上更加的灵活多变。

深度选择器

类似于sass语法中的v::deep,不过vue3中的样式自带深度作用域

<style scoped>
.parent :deep(div) {
    margin-bottom: 10px;
}
</style>

<template>
  <div class="parent">
    <div class="set-up">:deep 深度作用域测试</div>
  </div>
</template>

全局选择器

不用像vue2一样写全局作用域时,需要单独开启一个style标签,同时去掉scoped属性;vue3提供了一种便捷的写法,只需要使用global属性传递你想全局修改的样式即可。

<template>
    <div>全局选择器测试</div>
    <p :class="$style.green">module样式测试</p>
</template>

<style scoped>
:global(div) {
    color: red;
}
</style>

<style module> 标签会被编译为 CSS Modules 并且将生成的 CSS 类作为 $style 对象的键暴露给组件。

<template>
    <p :class="$style.green">module样式测试</p>
</template>

<style module>
.green {
    color: green;
}
</style>

通过module自定义注入名称

<template>
    <p :class="classes.blue">useCssModule样式测试</p>
</template>

<style module="classes">
.blue {
    color: blue;
}
</style>

与组合式 API 一同使用

<script>
import { h, useCssModule } from 'vue'
export default {
  setup() {
    const style = useCssModule()
    return () =>
      h(
        'div',
        {
          class: style.success
        },
        'Task complete!'
      )
  }
}
</script>

<style module>
.success {
  color: #090;
}
</style>

Typescript基础&项目中如何使用Typescript

对于TS,笔者认为小项目中也不必集成TS,反倒会提升项目的编译成本。那如果是大型项目的话,有必要尝试接入TS,一方面可以减少不必要的类型判断及文档注释,同时可以及早的发现错误,做静态类型检查时就可以及时的发现问题。另一方面,类、接口的使用更易于构建和维护组件;那么,对于初学者我们有必要对TS的一些基本用法做一下普及。

基本的数据类型

/**
 * @description: 基本的数据类型
 * @return {*} boolean(布尔值)number(数值) Array<number> (泛型数组)Object (对象)null undefined
 */
let isDone: boolean = false;
console.log('isDon', isDone);

let num: number = 1;
console.log('num', num);

let str: string = '认知觉醒';
console.log('str', str);

let arr: number[] = [1, 2, 3];
console.log('arr', arr);

// 泛型数组
let arr2: Array<number> = [1, 2, 3]
console.log('arr2', arr2);

let obj: Object = { id: 1 }
console.log('obj', obj);

let u: undefined = undefined
console.log('u', u);

let n: null = null;
console.log('n', n);

枚举

// 数字类型枚举与数字类型
enum CardSuit {
    Clubs,
    Diamonds,
    Hearts,
    Spades
}

console.log('CardSuit', CardSuit.Clubs); // 0
let col = CardSuit.Clubs;
col = 0 // 安全有效的
console.log('col', col); // 0

// 数字类型枚举与字符串类型
enum Tristate {
    False,
    True,
    Unkonw
}
console.log('字符串', Tristate[0]); // 'False'
console.log('number', Tristate['False']); // 0
console.log('字符串', Tristate[Tristate.False]); // 'False'

// 字符串枚举
enum LogLevel {
    info = 'info',
    warn = 'warn',
    error = 'error'
}
console.log('LogLevel', LogLevel.info); // 'info'

元祖

/**
 * @description: 元祖
 * @return {*} 允许数组各元素的类型不必相同
 */
let x: [string, number, boolean];
x = ['hello', 10, true];
console.log('正确元祖', x); // ['hello', 10, true]
// y = [10, 'hello', false]
// console.log('错误的元祖', y);

任意值 Any

/**
 * @description: 任意值 Any
 * @return {*} 表示任意类型, 通常用于不确定内容的类型,比如用户的输入或者是第三方库代码;实际项目中,此类型建议少用
 */
let notSure: any = 4;
notSure = 'maybe a string instead';
console.log('notSure', notSure); // 'maybe a string instead'
notSure = true;
console.log('notSure', notSure); // true

空值 void

/**
 * @description: 空值 void
 * @return {*} 与any相反,通常用于函数,表示没有返回值
 */
const voidFunc = (): void => {
    console.log('这个函数没有返回任何值');
    // return msg; // 不能return
}
voidFunc()

interface

/**
 * @description: 接口 interface
 * @return {*} 类型契约,跟我们平时与服务端接口要先定义字段是一个道理
 */
interface Point {
    x: number
    y: number
    z?: number
    readonly l: number
}

const point: Point = { x: 10, y: 20, z: 30, l: 40 }
console.log('point', point);

const point2: Point = { x: '10', y: 20, z: 30 } // Error x应该是Number类型

const point3: Point = { x: 10, y: 20, z: 30 } // Error l字段也是必传

const point4: Point = { x: 10, y: 20, z: 30, l: 40, m: 50 } // Error m字段没有定义

const point5: Point = { x: 10, y: 20, l: 40 } // 正常
point5.l = 20; // Error l字段是只读类型,不能修改

函数参数类型与返回值类型

/**
 * @description: 函数参数类型与返回值类型
 * @return {*}
 */
function sum(a: number, b: number): number {
    return a + b;
}
console.log('sum', sum(2, 3)); // 5

// 配合interface使用
interface Point {
    x: number
    y: number
}

function sum2({x, y}: Point): number {
    return x + y;
}
console.log('sum2', sum2({x: 1, y: 2})); // 3

泛型

/**
 * @description: 泛型
 * @return {*} 泛型的意义在于函数的重用性,设计原则希望组件不仅能够支持当前的数据类型,同时也支持未来的数据类型
 * 语法:<T>(arg: T): T
 */

// 比如我们最初设计函数identity 入参为String
function identity(arg: String) {
    return arg;
}
console.log(identity('hello')); // hello

// 后来随着业务的迭代我们又需要支持 Number
function identity2(arg: String) {
    return arg;
}
console.log(identity(2)); // Argument of type 'number' is not assignable to parameter of type 'String'

// 那我们为什么不用any呢?使用any会导致丢失掉一些信息,我们无法确定要返回值到底是属于什么数据类型
const hello1: String = 'Hello vue3';
const hello2: Number = 666;
function say<T>(arg: T): T {
    return arg;
}
console.log('泛型1:', say(hello1)); // Hello vue3
console.log('泛型2:', say(hello2)); // 666

// 泛型约束
// 我们使用同样的例子,加了一个console,但是很不幸运,报错了,因为泛型无法保证每种类型都有.length 属性
const hello3: String = 'Hello vue3';
function say2<T>(arg: T): T {
    console.log(arg.length); // Property 'length' does not exist on type 'T'
    return arg;
}
console.log('泛型3:', say2(hello3)); // Hello vue3

interface Lengthwise {
    length: number
}

function say3<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
console.log(say3(1)); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
console.log(say3({ value: 'hello vue', length: 10 })); // '{ value: 'hello vue', length: 10 }'

交叉类型

interface foo {
    x: number
}

interface bar {
    b: string
}

type intersection = foo & bar

const result: intersection = {
    x: 10,
    b: 'hello'
}

console.log('result', result);

联合类型

/**
 * @description: 联合类型
 * @return {*} 表示一个值可以为几种数据类型之一
 */
type arg = string | number | boolean

const foo = (arg: arg): any => {
    console.log('arg', arg);
}
foo(1)
foo('1')
foo(true)

函数重载

/**
 * @description: 函数重载
 * @return {*} 1个函数可以执行多项任务的能力
 */

// add函数,它可以接收string类型的参数进行拼接,也可以接收number类型的参数进行相加
function add <T, U>(arg1: T, arg2: U) {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2
  if (typeof arg1 === 'string' && typeof arg2 === 'string') {
    return arg1 + arg2
  } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
    return arg1 + arg2
  }
}
console.log('number类型相加', add(1, 2));
console.log('string类型拼接', add('1', '2'));

vue3项目中如何集成TS

  • 首先,你可以在初始化项目的时候就选择TS模板,直接将TS相关配置集成到项目中去。
  • 当然,你也可以手动去配置TS

安装TS

npm i typescript

项目根目录新建tsconfig.json文件,用于TS的编译基础文件

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
  • 项目中使用

script标签中声明langg="ts",然后就可以愉快的使用TS的项目语法了,下边这段代码只是一些简单的示例。

<template>
   <div>
    <h2>标题:{{book.title}}</h2>
    <h2>作者:{{book.author}}</h2>
    <h2>出版日期:{{book.year}}</h2>
    <hr>
    <h3>{{allTitle}}</h3>
    <el-button @click="setTitle('我是传入的数据')" type="primary">设置数据</el-button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, toRefs, reactive } from 'vue';

// 定义Book接口
interface Book {
    title: String
    author: String
    year?: Number,
    handleChangeName?(): void
}

export default defineComponent ({
    data() {
        let book: Book = {
            title: 'vue3 typescript',
            author: "vue Team",
            year: 2020,
        }
        return {
            book
        }
    },
    setup() {
        let year1 = ref<String | Number>('2022')
        console.log('year1', year1.value);

        // 第一种方式
        // const book1: Book = reactive({
        // name: year1.value,
        // desc: "vue3 进阶学习加油",
        // setNamechange(){
        //     this.name = "我是新设置的"
        // }
        // });
        // // 第二种方式
        // const book2 = reactive<Book>({
        // name: "vue3--typeScript",
        // desc: "学习ts加油",
        // year: 2020,
        // });
        // // 第三种方式
        // const book3 = reactive({
        //     name: "vue3--typeScript-第三种方式",
        //     desc: "ts类型第三种方式",
        //     year: 2022,
        // }) as Book;

        return {
            // ...toRefs(book1),
            // book2,
            // book3,
            // year1,
        };
    },
    computed: {
        // 返回值类型为String
        allTitle(): String {
            return `欢迎语 : ${this.book.title}`
        }
    },
    methods: {
        // 入参为String 返回空值
        setTitle(arg: String): void {
            this.book.title = arg;
            this.book.year = 2022
            this.book.author = '尤雨溪'
        }
    }
})
</script>

状态管理Pinia

由于本文章篇幅较大,会在之后的文章中单独来讲解。

到此这篇关于vue3基础知识剖析的文章就介绍到这了,更多相关vue3基础知识内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue3.0自定义指令(drectives)知识点总结

    在大多数情况下,你都可以操作数据来修改视图,或者反之.但是还是避免不了偶尔要操作原生 DOM,这时候,你就能用到自定义指令. 举个例子,你想让页面的文本框自动聚焦,在没有学习自定义指令的时候,我们可能会这么做. const app = Vue.createApp({ mounted(){ this.$refs.input.focus(); }, template: `<input type="text" ref="input" />`, }); 在mou

  • vue3组件化开发常用API知识点总结

    目录 组件化思想 组件通讯 $props $emits $parent $attrs proviede & inject 插槽 slot 渲染作用域 作用域插槽 v-model 表单组件 自定义组件 改变默认参数 样式绑定相关 class style 总结 组件化思想 为什么使用组件化开发? 当前前端比较流行的 Vue React 等框架,都会通过编写组件来完成业务需求,也就是组件化开发.包括小程序开发也会用到组件化开发的思想. 分析组件化思想开发应用程序: 将一个完整页面拆分成很多个小组件 每

  • Vue3.x项目开发的一些常用知识点总结

    目录 一.定义组件属性 二.formatter简写 三.子父组件通信 四.监听组件属性变化 五.自定义指令 总结 PS:以下知识点都是基于 vue3.x + typescript + element-plus + setup语法糖 使用的. 一.定义组件属性 const props = defineProps({ visible: { type: Boolean, default: false } }) console.log(props.visible) [warning] 注意:define

  • vue3基础知识剖析

    目录 声明 vue3.0有哪些新特性 vue3.0的优缺点 如何解锁vue3.0 体验vue3.0的4中姿势 核心的composition API setup setup语法糖 ref.reactive watch跟watchEffect computed(计算属性) 组件通信 props emit 插槽 vue2中的使用 vue3中的使用 生命周期 vue-router 4.0 Composition API 路由守卫 keep-alive 和 transition 必须用在 router-v

  • VUE3基础学习之click事件详解

    目录 1. 概述 2. click 事件 2.1 实现数字递减 2.2 事件方法中获取 event 对象 2.3 事件方法中增加参数 2.4 有参事件方法中获取 event 对象 2.5 点击按钮执行多个方法 2.6 事件冒泡 2.7 阻止冒泡 2.8 事件捕获 2.9 事件只执行一次 3. 综述 1. 概述 老话说的好:努力帮别人解决难题,你的难题也就不难解决了. 言归正传,今天我们来聊聊 VUE3 的 click 事件的相关知识. 2. click 事件 2.1 实现数字递减 <body>

  • AngularJS实用基础知识_入门必备篇(推荐)

    前言 今天来和大家学习一下AngularJS-- AngularJS 通过新的属性和表达式扩展了 HTML. AngularJS 可以构建一个单一页面应用程序. AngularJS 学习起来非常简单. 一.AngularJS指令与表达式 [AngularJS常用指令] 1.ng-app:声明Angular所管辖的区域,一般写在body或HTML上,原则上一个页面只有一个. 2.ng-model:把元素值(比如输入域的值)绑定到应用程序的变量中. eg:<input type="text&q

  • AngularJS 最常用的八种功能(基础知识)

    AngularJS 使用基础知识 第一 迭代输出之ng-repeat标签 ng-repeat让table ul ol等标签和js里的数组完美结合 <ul> <li ng-repeat="person in persons"> {{person.name}} is {{person.age}} years old. </li> </ul> 你甚至可以指定输出的顺序: <li ng-repeat="person in pers

  • PHP小白必须要知道的php基础知识(超实用)

    很多人看到PHP就以为是程序员,就以为钱很多(虽然是事实),但是也要考虑下自己是不是适合这一行,知道PHP是什么吗?PHP都有什么样的功能,都能用来干嘛? PHP是什么? •PHP(PHP: Hypertext Preprocessor,超文本预处理器的缩写),是一 种被广泛应用的开放源代码的.基于服务器端的用于产生动态网页 的.可嵌入HTML中的脚本程序语言,尤其适合 WEB 开发. •当客户端向服务器的程序提出请求时,web服务器根据请求晌应对应 的页面,当页面中含有php脚本时,服务器会交

  • ASP新手必备的基础知识

    我们都知道,ASP是Active Server Page的缩写,意为"动态服务器页面".ASP是微软公司开发的代替CGI脚本程序的一种应用,它可以与数据库和其它程序进行交互,是一种简单.方便的编程工具.下面介绍一些基本知识,供大家参考. 一.数据库连接 以下为引用的内容: <% set conn=server.createobject("adodb.connection") conn.open "driver={microsoft access dr

  • 学习shell脚本之前的基础知识[图文]

    日常的linux系统管理工作中必不可少的就是shell脚本,如果不会写shell脚本,那么你就不算一个合格的管理员.目前很多单位在招聘linux系统管理员时,shell脚本的编写是必考的项目.有的单位甚至用shell脚本的编写能力来衡量这个linux系统管理员的经验是否丰富.笔者讲这些的目的只有一个,那就是让你认真对待shell脚本,从一开始就要把基础知识掌握牢固,然后要不断的练习,只要你shell脚本写的好,相信你的linux求职路就会轻松的多.笔者在这一章中并不会多么详细的介绍shell脚本

  • HTTP报文及ajax基础知识

    HTTP报文 客户端传递给服务器的内容 和 服务器传递给客户端的内容 都属于HTTP报文 起始行:请求起始行  响应起始行 首部:请求首部 响应首部 通用首部(请求和响应都有的) 自定义首部 主体:请求主体  响应主体 客户端传递给服务器端数据: 请求URL后面问号传参的方式传递给服务器  /getList?name=zhangsan&age=7 设置请求的首部(设置请求头信息) 设置请求主体,把传递给服务器的内容放在请求主体中传递给服务器 服务器端传递给客户端数据: 设置响应头信息 设置响应主

  • Lua中函数与面向对象编程的基础知识整理

    函数 1. 基础知识 调用函数都需要写圆括号,即使没有参数,但有一种特殊例外:函数若只有一个参数且参数是字面字符串或table构造式,则圆括号可有可无,如dofile 'a.lua',f{x=10, y=20}. Lua为面向对象式的调用提供冒号操作符的特殊语法,如o.foo(o, x)等价于o:foo(x).和Javascript类似,调用函数时提供的实参数量可以与形参数量不同,若实参多了则舍弃,不足则多余的形参初始化为nil. 1.1 多重返回值 Lua允许函数返回多个结果,函数返回如ret

  • GO语言(golang)基础知识

    今天说一些golang的基础知识,还有你们学习会遇到的问题,先讲解hello word 复制代码 代码如下: package main import "fmt" func main() {    fmt.Println("你好,我们"); } package name 包机制,每一个独立的go程序都需要有一个package main的申明,主要是要为下边入口函数main()做申明的,import和java一样导入包用的 就是下边我们函数用的fmt.Println()

随机推荐