Vue3 基础

主要特点

Proxy代理

不再使用 defineProperty(getter/setter)监听对象单个属性,性能有所提升,且解决了数组/对象新增属性时监听失效的问题。
注意:因 IE 11 不支持 Proxy,且 Babel 也无法将 Proxy 转为 ES5,Vue3 无法兼容 IE11

组合式API
  • Vue2的语法风格称为 选项式api,Vue3中增加了组合式apisetup,用于解决 mixins 的痛点(命名冲突、数据来源不清晰、隐式的跨 mixin 交流),将服务于每种功能的 data、computed、methods、watch 代码尽量书写在一起。
  • 当两种语法风格同时使用时,同名属性、方法等以组合式API为准
优化算法

Vue3 优化了diff 算法(不再比对所有dom)和 渲染算法(闭包缓存),性能更强大


项目构建

  • Vue2 通过 vue-cli (webpack)构建
  • Vue3 通过 @vue/cli (基于webpack)构建,或直接通过 Vite 构建
    npm init vue@latestnpm create vue@latest(npm create 、npm innit 都是 npm init 的别名)

创建实例

Vue2通过const app = new Vue({render: h => h(App)}).$mount('#app')创建实例

Vue3通过const app = Vue.createApp(App).mount('#app')创建实例

全局配置

  • Vue2中,.component.directive.use都是Vue类的方法
    可以通过向Vue.prototype上添加内容实现全局属性、方法
  • Vue3中,.component.directive.use都是Vue实例的方法
    可以通过向app.config.globalProperties上添加内容实现全局属性、方法
    可以通过app.config.errorHandler = (err) => {}实现全局错误处理器

模板语法

动态参数(Vue2.6以上也已支持)

注意动态名不能出现空格和引号,大写会变成小写,动态参数值应该是字符串或null(表示移除该动态参数绑定)

<a v-bind:[attribute_name]="url"> ... </a>
<a :[attribute_name]="url"> ... </a>
<a v-on:[event_name]="doSomething"> ... </a>
<a @[event_name]="doSomething"> ... </a>
事件修饰符
  • .stop .prevent .self .capture .once .passive事件修饰符
    其中passive对应addEventListener中第三个参数里的passive:true,开发者保证监听器不会调用preventDefault(),不用等待js脚本而立刻响应行为,通常用在touch或者wheel上,确保了流畅的滚动体验。
  • .enter .tab .delete (捕获“Delete”和“Backspace”两个按键) .esc .space .up .down .left .right键盘按键修饰符
  • .ctrl .alt .shift .meta系统按键修饰符,并可通过.exact修饰(用于保证没有其他键按下)
  • .left .right .middle鼠标按键修饰符

三种风格

1. 选项式API
2. 使用setup方法的组合式API
  • 可以和选项式API的内容在同一个export default里混用,但由于setup方法在 props 之后,beforeCreate、data property、computed property 、methods 、refs 解析之前调用,因此无法访问选项式API的data等内容,且其中 this 并非指向当前组件实例。
  • 只有return的内容才能在模板中访问
  • setup 函数包含两个参数:
    • 第一个参数通常命名为 props,为一个 reactive 实例,通常配合toReftoRefs使用,防止响应性丢失。对应 propsdefineProps
    • 第二个参数通常命名为 context,是一个非响应式的普通对象,包含如下属性:
      • attrs 属性,非响应式对象,对应 $attrsuseAttrs
      • slots 插槽,非响应式对象,对应 $slotsuseSlots
      • emit 触发事件,方法,对应 $emitdefineEmits
      • expose 暴露公共允许父组件访问的公共内容,对应 exposedefineExpose
import { ref } from 'vue'

export default {
  // `setup` 是一个特殊的钩子,专门用于组合式 API。
  setup(props,{attrs, slots, emit, expose}) {
    const count = ref(0)

    // 将 ref 暴露给模板
    return {
      count
    }
  }
}
3. 使用单文件组件的<script setup>的组合式API
  • 顶层的import导入组件、声明的变量/函数等,可无需return,直接在模板中使用
  • 需通过import导入组合式API所需内容,如refuseAttrsuseSlotsnextTick
  • 使用编译宏,如definePropsdefineEmitsdefineExposedefineModel

nextTick

  • 选项式API中,可以使用this.$nextTick(同Vue2)
  • 组合式API中,需要引入nextTick实现:
import { createApp, nextTick } from 'vue'
const app = createApp({
 setup() {
   const message = ref('Hello!')
   const changeMessage = async newMessage => {
     message.value = newMessage
     // 这里获取DOM的value是旧值
     await nextTick()
     // nextTick 后获取DOM的value是更新后的值
     console.log('Now DOM is updated')
   }
 }
})

v-if 和 v-for

  • v-if 和 v-for 可以在一个元素上同时使用(不建议),但 v-if 优先级更高,因此其中无法引用 v-for 里的变量
  • v-if 不再需要 key,v-for 的 key 可以配置在 template 中而非实际元素
  • key现在可以正确配置给<template>,而非其中的子元素

生命周期

  • 使用beforeUnmountunmounted代替Vue2中的beforeDestroydestroyed
组合式API的生命周期钩子
  • 组合式 API 上的生命周期钩子与选项式 API 的名称相同,但需要添加前缀 on
  • 不存在 onCreatedonBeforeCreate钩子,相关内容应直接在 setup 中实现
  • 生命周期函数不一定要在setup中顶层调用来注册,但不能在异步内调用(可能注册时已经错过生命周期)
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// 在我们的组件中
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

computed 计算属性

计算属性值会基于其响应式依赖被缓存,仅会在其响应式依赖更新时才重新计算
计算属性默认只读,可以通过提供 gettersetter 来创建可写的计算属性(一般用不到,仅用于如模拟实现v-model等特殊场景):

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

const firstName = ref('John')
const lastName = ref('Doe')

//const fullName = computed(()=>firstName.value + ' ' + lastName.value)
const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

注意:如果每次返回的是个新对象,则新旧值始终不同,总是触发更新。此时可以手动比较新旧值来优化:

const computedObj = computed((oldValue) => {
  const newValue = {
    isEven: count.value % 2 === 0
  }
  if (oldValue && oldValue.isEven === newValue.isEven) {
    return oldValue
  }
  return newValue
})

watch 侦听器

侦听器可以监听单个响应式对象、多个来源组成的数组、一个 getter 函数。
支持以下配置项

  • once: true 只触发一次
  • deep: true 深层监听对象
  • immediate: true 使侦听器创建时立即触发回调。
  • flush 决定侦听器回调的触发时机
    "pre":默认值。侦听器回调会在父组件更新 (如有) 后、所属组件的 DOM 更新前被调用。这意味着在侦听器回调中访问到的所属组件的DOM是更新前的状态。
    "post":当UI渲染更新后再触发回调
    "sync":响应式依赖改变时立即触发回调(谨慎使用,可能会有性能或数据一致性问题)
选项式API中可以通过this.$watch配置:
var unwatch = this.$watch('someObject', callback, {
  deep: true
})

unwatch()//通过调用返回值来主动卸载侦听器
选项式API也可以将方法放在handle属性中,并附加配置:
export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // 注意:在嵌套的变更中,
        // 只要没有替换对象本身,
        // 那么这里的 `newValue` 和 `oldValue` 相同
      },
      deep: true,  
    }
  }
}
组合式API中通过watch方法配置:
import { ref, watch } from 'vue'

const x = ref(0)
const y = ref(0)

//单个ref
watch(x, (newValue, oldValue) => {
  console.log('The new counter value is: ' + x.value)
},
{ deep: true }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

// 监听一个 getter 函数
watch(
  () => x + y,
  (sum) => {
    console.log(`sum is: ${sum}`)
  }
)

注意,组合式API中不能直接侦听响应式对象的属性值(因为这个值本身不是响应式对象),例如可以这样:

const obj = { count: ref(0) }

watch(obj.count, (count) => {
  console.log(`Count is: ${count}`)
})

但如果用const obj = ref({ count: 0 }),就只能这样了:

watch(
  () => obj.count,
  (count) => {
    console.log(`Count is: ${count}`)
  }
)
组合式API额外提供了语法糖:watchEffect 和 watchSyncEffect

自动跟踪回调函数中同步方法内的所有响应式依赖,且自带immediate:true

import { ref, watchEffect } from "vue";
const obj = ref({ age: 18 });
const unbind = watchEffect(()=>{
  console.log(obj.value.age)
})

watchEffect(callback, { flush: 'sync' })可进一步使用别名watchSyncEffect

解除侦听
  • $watchwatchwatchEffect函数返回值都是一个可用于解除侦听的函数。
  • 用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。异步语句内创建的侦听器只能手动卸载,以防内存泄露。

ref 模板引用

  • 选项式API通过this.$refs.XXX来引用模板
  • 组合式API声明一个和模板里的 ref 同名的 ref 来存放引用:
<script setup>
import { ref, onMounted } from 'vue'
const input = ref(null)
onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>
  • 当 ref 和 v-for 一起使用的时候,可以得到包含了对应数据源的这些子组件的数组,但顺序不一定相同
约束可用内容
  • 如果使用了选项式API,默认父元素可以调用所有子组件内容(此时同Vue2,被引用的实例等价于子组件中的this指向)。也可以通过配置expose属性名数组,约束可用内容。
  • 如果使用了setup方法,默认父元素可以调用所有子组件内容。也可以使用setup方法参数里的expose方法,约束可用内容。
export default {
  // 只有 `publicMethod` 在公共实例上可用
  expose: ['publicMethod'],
  setup(props, { expose }) {
    var aa = 100;
    var bb = 200;
    expose({ aa });
    return { aa, bb };
  },
  methods: {
    publicMethod() {
      // ...
    },
    privateMethod() {
      // ...
    }
  }
}
  • 如果使用了<script setup>,则其中内容默认都是私有的,必须通过编译宏defineExpose暴露:
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

JSX/TSX

Vue3支持JSX和TSX(但语法和React的不同)


SSR 服务端渲染(Nuxt)

服务端渲染优势:更快的首屏加载速度、更好的SEO


Typescript

defineComponent

为了让 TypeScript 正确地推导出组件选项内的类型,我们需要通过 defineComponent() 这个全局 API 来定义组件:

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  // 启用了类型推导
  props: {
    message: String
  },
  setup(props) {
    props.message // 类型:string | undefined
  }
})
</script>
泛型
  • <script setup> 中使用 generic 属性声明泛型:
<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
  selected: T
}>()
</script>
  • 其他情况使用defineComponent函数签名:
const Comp = defineComponent(
  <T extends string | number>(props: { msg: T; list: T[] }) => {
    // 就像在 <script setup> 中一样使用组合式 API
    const count = ref(0)

    return () => {
      // 渲染函数或 JSX
      return <div>{count.value}</div>
    }
  },
  // 目前仍然需要手动声明运行时的 props
  {
    props: ['msg', 'list']
  }
)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,431评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,637评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,555评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,900评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,629评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,976评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,976评论 3 448
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,139评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,686评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,411评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,641评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,129评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,820评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,233评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,567评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,362评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,604评论 2 380

推荐阅读更多精彩内容