vue3复习摘录

38 阅读8分钟

不用再搞methods

<script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。

事件监听

  • vue3 子组件上的事件监听能直接透传进去监听原生
    • 不再有事件修饰符.native
    • vue3没有listeners了,都在listeners了,都在attrs里
  • vue2若子组件上有@click,需要子组件emit click事件,父组件才能监听到
    • 或者父组件里用@click.native
  • 透传

esmodule

<script type="module"> ES 模块只能通过 http:// 协议工作,也即是浏览器在打开网页时使用的协议。为了使 ES 模块在我们的本地机器上工作,我们需要使用本地的 HTTP 服务器,通过 http:// 协议来提供 index.html

要启动一个本地的 HTTP 服务器,请先安装 Node.js,然后通过命令行在 HTML 文件所在文件夹下运行 npx serve。你也可以使用其他任何可以基于正确的 MIME 类型服务静态文件的 HTTP 服务器。

传统方式
<script src="script.js"></script>
模块化方式
<script type="module" src="script.js"></script> 两种方式的主要区别:

传统方式:当浏览器遇到传统的 <script> 标签时,会立即加载并执行其中的 JavaScript 代码。这意味着代码中的全局变量和函数都会污染全局作用域,可能导致命名冲突和变量覆盖等问题。

模块化方式:使用 type="module" 属性告诉浏览器,这个 <script> 标签中的 JavaScript 代码是一个模块。浏览器会按照模块化的方式去加载和执行这段代码,包括解析依赖关系、异步加载等。模块具有自己的作用域,模块内部的变量和函数不会污染全局作用域,也不会受到其他模块的影响。

script setup 的都会编译成type="module" 打包后无法文件里直接打开index.html。vue2的项目打包后可以直接打开

module

用setup时,一个组件的script就是type="module",防止全局变量污染。不同script之间的变量方法不会被融合,也无法直接跨文件调用(vue2的编译后 script 的type不是"module" 跨组件直接调用值/方法没问题),导致一个组件通过ref调用另一个组件的属性/方法时找不到。需要组件内部通过defineExpose暴露出去

<script setup> 
    import { ref } from 'vue' 
    const a = 1 
    const b = ref(2) 
    // 像 defineExpose 这样的编译器宏不需要导入 
    defineExpose({ a, b }) 
</script>

响应式变量里的ref会被自动解包

  • ref里套ref
----子组件----
const mesVal = ref(100)
defineExpose({
  mesVal,
})
----父组件-ref里套ref---
<Msg ref="msgRef"/>

const msgRef = ref()
msgRef.value.mesVal = 15 //不用msgRef.value.mesVal.value
  • reactive里套ref
const resObj = reactive({ name: '张三', age: ref(10) })
function changeVal() {
  resObj.name = '李四'
  resObj.age += 20
}

不用import就能使用的宏

一般 definexxx 这种宏都不用import就可以用

  • defineExpose
  • defineProps
  • defineEmits
const props = defineProps(['title'])
const emit = defineEmits(['enlarge-text']) 
emit('enlarge-text')

defineProps 宏

响应式一般用ref

可以通过 shallow ref 来放弃深层响应性。对于浅层 ref,只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。

<p>{{ calculateBooksMessage() }}</p> 方法调用总是会在重渲染发生时再次执行函数。 所以尽量用computed,有缓存

自定义组件命名

尽量用PascalCase

Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent 为名注册的组件,在模板中可以通过 <MyComponent> 或 <my-component> 引用。

props

定义时用camelCase,父组件给子组件html里属性写 kebab-case

组件名我们推荐使用 PascalCase,因为这提高了模板的可读性,能帮助我们区分 Vue 组件和原生 HTML 元素。然而对于传递 props 来说使用 camelCase 并没有太多优势,因此我们推荐更贴近 HTML 的书写风格。

--------子组件内定义----------
defineProps({ greetingMessage: String })
--------父组件用子组件----------
<MyComponent greeting-message="hello" />

<!-- 仅写上 prop 但不传值,会隐式转换为 `true` --> 
<BlogPost is-published />

如果使用了基于类型的 prop 声明 ,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }}

// 使用 <script setup> 
defineProps({ title: String, likes: Number })

<script setup lang="ts"> 
    defineProps<{ title?: string likes?: number }>() 
</script>
  • defineProps得到的变量是reactive
  • const {name,count }=defineProps(['name','count '])
    • 3.5 及以上版本,当在同一个代码块中访问由 defineProps 解构变量时,Vue 编译器会自动在前面添加 props.,直接用就是响应式的
    • 老版的,直接解构是常量,需要下面处理才能解构使用// 不建议拆开,直接props.xxx用
      const props = defineProps(['name','count '])
      const { count } = toRefs(props); // const count = toRef(props, "count");
    

emits

像组件与 prop 一样,事件的名字也提供了自动的格式转换。
camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听

所有传入 $emit() 的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3) 触发后,监听器函数将会收到这三个参数值。

vue3里用vue-router跳转

方式一(官方推荐)

import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
router.push('/home')

方式二

创建并导出router实例后
import router from '@/router'
router.push('/home')

暴露事件

  • 法一:
---------子组件-----------
<button @click="$emit('increaseBy', 1)"> Increase by 1 </button>
---------父组件-----------
<MyButton @increase-by="increaseCount" />
  • 法二:
<script setup> 
   const emit = defineEmits(['inFocus', 'submit']) 
   
   function clicHandler(){
       emit('inFocus',1234)
   }
</script>

v-model

  • 这一块用法变动很大,实际上是越来越简化使用方式了

v2

  • 绑定一个
<base-checkbox v-model="lovingVue"></base-checkbox>

Vue.component('base-checkbox', {  
model: {  
prop: 'checked',  
event: 'change'  
},  
props: {  
checked: Boolean  
},  
template: `  
<input  
type="checkbox"  
v-bind:checked="checked"  
v-on:change="$emit('change', $event.target.checked)"  
>  
`  
})
  • 绑定多个
<text-document :title.sync="doc.title"></text-document>

-----------子组件--------
props:['title']
this.$emit('update:title', newTitle)

v3 (>3.4)

defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改 官方文档
旧版v3子组件靠导入props 和 emits update:xx
新版 增加了defineModel() 用它导入的prop 被修改时会被监听并emit出去
父组件使用子组件 模式不变 v-model:first-name="first"

  • 它的 .value 和父组件的 v-model 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新。
  • 绑定一个
<!-- Parent.vue --> 
<Child v-model="count" />
<!-- Child.vue --> 
<script setup> 
const model = defineModel() 
function update() { model.value++ } 
</script> 

<template> 
<div>parent bound v-model is: {{ model }}</div> 
</template>
  • 绑定多个
------父组件-----------
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

------子组件-----------
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>
  • 旧版绑定多个
------父组件-----------
<UserName
  v-model:first-name="first"
/>

------子组件-----------
<script setup>
defineProps({
  firstName: String,
})

defineEmits(['update:firstName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
</template>

透传

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 classstyle 和 id。会直接继承在组件最外层(未被props声明消费掉的属性 会暴露出来能看到)

  • 禁用 Attributes 继承
<script setup>
defineOptions({
  inheritAttrs: false
})
// ...setup 逻辑
</script>
  • 禁止继承后,不会默认挂载到最外层,可以使用透传进来的 attribute 。在模板的表达式中直接用 $attrs 访问到。$attrs 对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 classstylev-on 监听器等等。
  • v2里,事件是由 $linsteners 继承
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。

provide

应用层 Provide

除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。

import { createApp } from 'vue'

const app = createApp({})

app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

在应用级别提供的数据在该应用内的所有组件中都可以注入。这在你编写插件时会特别有用,因为插件一般都不会使用组件形式来提供值。

Inject (注入)

要注入上层组件提供的数据,需使用 inject() 函数:

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。

更改provide的数据

文档

  • 尽量在provide的组件里修改提供的响应式变量
  • 若想在inject的组件里改注入的变量,需要provide里再提供一个修改变量的方法 明并提供一个更改数据的方法函数:(虽然子组件内也能直接改,不提倡)
  • 所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

watchEffect

  • watch第一个参数要指定被监听对象,watchEffect会自动跟踪回调函数的响应式依赖
  • 回调会立即执行,不需要指定 immediate: true
  • 内部有响应式变量改变时 会自动被监听到并执行
  • toValue包裹ref响应式变量 在回调里 值改变时会触发
  • computed返回的是ref
// 当 props.id 改变时重新 fetch
const { data, error } = useFetch(() => `/posts/${props.id}`)

我们可以用 watchEffect() 和 toValue() API 来重构我们现有的实现:

// fetch.js
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  const fetchData = () => {
    // reset state before fetching..
    data.value = null
    error.value = null

    fetch(toValue(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error }
}

toValue() 是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。它的工作方式类似于 unref(),但对函数有特殊处理。

toValue(url) 是在 watchEffect 回调函数的内部调用的。这确保了在 toValue() 规范化期间访问的任何响应式依赖项都会被侦听器跟踪。

组合式函数

  • 因为不确定外面传进来的是不是响应式ref变量/getter函数,内部最好用toValue处理一下
import { toValue } from 'vue'

function useFeature(maybeRefOrGetter) {
  // 如果 maybeRefOrGetter 是一个 ref 或 getter,
  // 将返回它的规范化值。
  // 否则原样返回。
  const value = toValue(maybeRefOrGetter)
}
  • 规范:useFuc里return的对象里的属性值都是ref
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}

这样该对象在组件中被解构为 ref 之后仍可以保持响应性:

// x 和 y 是两个 ref
const { x, y } = useMouse()

如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:

const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
Mouse position is at: {{ mouse.x }}, {{ mouse.y }}

组合式函数导出的ref变量,在组件内是可以修改它的值的

vue-router路由传值可以用props来接收

router.vuejs.org/zh/guide/es…

总结:routes配置里面多了个props属性,该属性的值可以是boolean对象函数 最终效果都是让组件可以通过props来接收路由携带的参数

布尔值

  • 处理动态路由/prarams,当 props 设置为 true 时,route.params 将被设置为组件的 props
const routes = [ { path: '/user/:id', component: User, props: true } ]
<script setup>
defineProps({
  id: String
})
</script>

<template>
  <div>
    User {{ id }}
  </div>
</template>

对象

  • 它将原样设置为组件 props。当 props 是静态的时候很有用
const routes = [
  {
    path: '/promotion/from-newsletter',
    component: Promotion,
    props: { newsletterPopup: false }
  }
]

函数

  • 自由度最高,主要对于query方式传参进行处理
  • URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件。
const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]