不用再搞methods
<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
事件监听
- vue3 子组件上的事件监听能直接透传进去监听原生
- 不再有事件修饰符.native
- vue3没有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')
响应式一般用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");
- 3.5 及以上版本,当在同一个代码块中访问由
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
事件监听器。最常见的例子就是 class
、style
和 id
。会直接继承在组件最外层(未被props声明消费掉的属性 会暴露出来能看到)
- 禁用 Attributes 继承
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
- 禁止继承后,不会默认挂载到最外层,可以使用透传进来的 attribute 。在模板的表达式中直接用
$attrs
访问到。$attrs
对象包含了除组件所声明的props
和emits
之外的所有其他 attribute,例如class
,style
,v-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来接收
总结: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 })
}
]