目录
1.组合式API
1.组合式 API 基础
1.带 ref 的响应式变量
2.watch 响应式更改
3.watch和watchEffect的区别:
4.独立的 computed 属性
5.生命周期钩子
2.Setup
1.Props
2.Context
3.使用渲染函数
4.访问组件的 property
3.Provide / Inject
1.Provide
2.Inject
3.添加响应性
4.在单文件组件(SFC)中使用组合式API的编译时语法糖
4.单文件组件script setup
1.基本语法
2.Vue Router
1.安装和引用
2.使用
router-link
router-view
3.带参数的动态路由匹配
4.404页面
5.在参数中自定义正则
可重复的参数
6.嵌套路由
7.编程式导航
1.导航到不同的位置
2.替换当前位置
8.命名路由
9.命名视图
9.重定向和别名
1.重定向
2.别名
10.将 props 传递给路由组件
1.布尔模式
2.defineProps 和 defineEmits
3.命名视图
11.不同的历史模式
1.Hash 模式(有#)
2.HTML5 模式(无#)
使用 (
data
、computed
、methods
、watch
) 组件选项来组织逻辑通常都很有效。然而,当组件开始变得更大时,逻辑关注点的列表也会增长,这会导致组件难以阅读和理解。这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。
// 组合式API,将同一个逻辑关注点相关代码收集在一起
// msg的逻辑代码
// 操作msg
const msg='hello',
function changeMsg() {
}
为了开始使用组合式 API,需要一个可以实际使用它的地方。在 Vue 组ttt件中,将此位置称为
setup
。
setup
选项在组件被创建之前执行,一旦 props
被解析完成,它就将被作为组合式 API 的入口setup
是围绕 beforeCreate
和 created
生命周期钩子运行的,所以不需要显式地定义它们。即在这些钩子中编写的任何代码都应该直接在 setup
函数中编写。setup
中你应该避免使用 this
,因为它不会找到组件实例。setup
的调用发生在 data
property、computed
property 或 methods
被解析之前,所以它们无法在 setup
中被获取。(组件创建之前被调用,数据代理data还没有开始,所以不能拿到这个数据)setup
返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。 setup () {//组件被创建之前执行,不需要使用this,this不会指向实例
console.log('setup')
// msg的逻辑代码
let msg = 'he'
console.log(msg)//he
function changeMsg () {
msg = 'nihao'
console.log(msg)//nihao 说明数据不是响应式的
}
return { msg, changeMsg }
},
beforeCreate () {
console.log('beforeCreate')
},
created () {
console.log('created')
},
// setup beforeCreate created
// setup取代了beforeCreate created
{{ msg }}
点击按钮后,页面没有改变,因为变量msg是非响应式的,从用户角度来看,仓库列表始终为空。
ref
的响应式变量
ref
为我们的值创建了一个响应式引用。ref定义number和string
ref
函数使任何响应式变量在任何地方起作用;ref
接收参数并将其包裹在一个带有 value
property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值;Number
或 String
等基本类型是通过值而非引用传递的)import { ref } from 'vue'
setup () {
// 通过ref定义响应式变量
const counter = ref(0)//ref()是一个函数,返回带有value属性的对象
function changeCounter () {
// counter++不能直接改,可以改value属性
counter.value++
}
return { counter, changeCounter }
},
{{ counter }}
reactive :定义引用数据类型,定义对象
import { reactive } from 'vue'
setup(){
// 通过reactive定义响应式引用类型的数据
// obj的逻辑代码
const obj=reactive({
name:'zz',
age:10
})
function changeObjName() {
obj.name='ls'
}
return { obj,changeObjName }
}
{{ obj.name }}
toRefs使解构后的数据,重新获得响应式
import { reactive, toRefs } from 'vue'
const obj = reactive({
name: 'zz',
age: 10,
childern: {
name: '里面'
}
})
function changeObjName () {
obj.name = 'ls'
}
// toRefs(object)使解构后的数据,重新获得响应式
// 法一:
let { name, childern } = toRefs(obj)
return { obj, changeObjName, name, childern }
// 通过ES6扩展运算符进行解构使得对象中的属性不是响应式的,需要使用toRefs
// 法二:
return { obj, changeObjName, ...toRefs(obj) }
{{ obj.name }}
{{ name }}
watch
响应式更改就像我们在组件中使用
watch
选项并在user
property 上设置侦听器一样,我们也可以使用从 Vue 导入的watch
函数执行相同的操作。
它接受 3 个参数:
import { ref, reactive, watch, watchEffect } from 'vue'
// 组合式API:将同一个逻辑关注点相关代码收集在一起
setup () {
const counter = ref(0)
function changeCounter () {
counter.value++
}
const user = reactive({
name: 'zz',
age: 10
})
function changeUserName () {
user.name = 'ls'
}
// watch(侦听的响应式引用,回调函数)
watch(counter, (newVal, oldVal) => {
console.log("newVal---", newVal)
console.log("oldVal---", oldVal)
})
watch(user, (newVal, oldVal) => {
console.log("newVal---", newVal)
console.log("oldVal---", oldVal)
// newVal--- Proxy {name: 'ls', age: 10}
// oldVal--- Proxy {name: 'ls', age: 10}
})
// 需要深度监听,才能监听到对象中的属性->watchEffect
// watchEffect(回调函数)注意:不需要指定监听的属性,组件初始化的时候会执行一次回调函数,自动收集依赖
watchEffect(() => {
console.log(user.name)//ls
})
return { counter, user, changeCounter, changeUserName }
},
// 选项式API
watch: {
message: function (newVal, oldVal) {
}
},
{{ counter }}
{{ user.name }}
computed
属性与
ref
和watch
类似,也可以使用从 Vue 导入的computed
函数在 Vue 组件外部创建计算属性。
computed
函数传递了第一个参数,它是一个类似 getter 的回调函数,输出的是一个只读的响应式引用。ref
一样使用 .value
property。import { ref, reactive, computed } from 'vue'
export default {
// 组合式API:将同一个逻辑关注点相关代码收集在一起
setup () {
const counter = ref(0)
const msg = ref('hello')
// 在外面使用computed
const reverseMsg = computed(() => {//返回一个带有value属性的对象
return msg.value.split('').reverse().join('')
})
console.log(reverseMsg.value)//olleh
const user = reactive({
name: 'zz',
age: 10,
// 在里面使用computed
reverseMsg: computed(() => {
return msg.value.split('').reverse().join('')
})
})
console.log(user.reverseMsg)//olleh
return { counter, user, msg }
},
// 选项式API
computed: {
reverseMsg: function () {
return this.message.split('').reverse().join('')
}
},
}
可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from 'vue'
export default {
setup () {
// 生命周期钩子函数中有一个参数:回调函数
onBeforeMount(() => {
console.log('onBeforeMount')
})
// 可以执行多次
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
//onBeforeMount onMounted
return
},
}
使用 setup
函数时,它将接收两个参数:
props
context
setup
函数中的第一个参数是props
。正如在一个标准组件中所期望的那样,setup
函数中的props
是响应式的,当传入新的 prop 时,它将被更新。
App.vue
data () {
return {
message: 'hello'
}
},
Content.vue
props: {
message: {
type: String,
default: 'nihao'
}
},
setup (props) {
console.log(props)//Proxy {message: 'hello'}
console.log(props.message)//hello
},
data () {
return {
counter: 0
}
},
// mounted () {
// console.log(this.message),在setup中拿不到值
// }
因为 props
是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性
//App.vue
message: 'hello'
//Content.vue
import { onUpdated, toRefs } from 'vue'
props: {
message: {
type: String,
default: 'nihao'
}
},
setup (props) {
const { message } = toRefs(props)
// console.log(message)需要使用value(message拿到的是对象的形式)
console.log(message.value)//hello
// 点击后没有执行setup,使用onUpdated
onUpdated(() => {
console.log(message)//拿到的还是旧的值hello,不是响应式的,使用toRefs
console.log(message.value)//nihaoa
})
},
{{ message }}
如果没有传的话,
toRefs将不会为message创建一个ref。你需要使用toRef替代它:
// Content.vue
import { toRef } from 'vue'
setup(props) {
const message= toRef(props, 'title')
console.log(message.value)
}
传递给 setup
函数的第二个参数是 context
。context
是一个普通 JavaScript 对象,暴露了其它可能在 setup
中有用的值:
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
//App.vue
console.log(context.attrs)//可以将组件中的属性,放在这里展示
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
触发事件
//Content.vue
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context
使用 ES6 解构。
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
attrs
和 slots
是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x
或 slots.x
的方式引用 property。请注意,与 props
不同,attrs
和 slots
的 property 是非响应式的。如果你打算根据 attrs
或 slots
的更改应用副作用,那么应该在 onBeforeUpdate
生命周期钩子中执行此操作。
如果
setup
返回一个对象,那么该对象的 property 以及传递给setup
的props
参数中的 property 就都可以在模板中访问到。返回函数出去
expose
来解决这个问题,给它传递一个对象,其中定义的 property 将可以被外部组件实例访问。
执行 setup
时,你只能访问以下 property:
props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
refs
(模板 ref)在
setup()
中使用provide
时,我们首先从vue
显式导入provide
方法。这使我们能够调用provide
来定义每个 property。
provide
函数允许你通过两个参数定义 property:
类型)父组件
import { provide } from 'vue'
export default {
setup () {
provide('name', 'zz')
return
},
}
在
setup()
中使用inject
时,也需要从vue
显式导入。导入以后,我们就可以调用它来定义暴露给我们的组件方式。
inject
函数有两个参数:
子组件
{{ name }}
import { inject } from 'vue'
export default {
setup () {
const name = inject('name')
return { name }
}
}
通过provide和inject可以实现通信。(直接通过provide传过去的值,不是响应式的)
为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用ref或reactive。
import { provide, ref } from 'vue'
export default {
setup () {
const name = ref('zz')
provide('name', name)//不要.value,因为它返回的是一个对象
function changeName () {
name.value = 'ls'
}
return { changeName }
},
components: {
Content
}
}
当父组件里面的值发生改变时,子组件也会跟着改变。(如果这两个 property 中有任何更改,子组件也将自动更新)
Vue 单文件组件(又名
*.vue
文件,缩写为 SFC)是一种特殊的文件格式,它允许将 Vue 组件的模板、逻辑 与 样式封装在单个文件中。
script setup
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的
语法,它具有更多优势:
要使用这个语法,需要将 setup
attribute 添加到 代码块上:
setup()
函数的内容。这意味着与普通的
只在组件被首次引入的时候执行一次不同,
中的代码会在每次组件实例被创建的时候执行。顶层的绑定会被暴露给模板,当使用 的时候,任何在
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用。
import 导入的内容也会以同样的方式暴露。意味着可以在模板表达式中直接使用导入的 helper 函数,并不需要通过 methods
选项来暴露它。
响应式状态需要明确使用响应式 APIs 来创建。和从 setup()
函数中返回值一样,ref 值在模板中使用的时候会自动解包。
{{ a }}
{{ b }}
单文件组件 | Vue.js
SFC 语法规范 | Vue.js
单文件组件 vue3--组合式API
只要没有匹配的页面,则显示404页面。(当上面路径都没有匹配的话,则执行404页面)
import NotFound from "../views/NotFound.vue"
{
//404页面,上面的页面为动态路由
// 使用正则的方式,匹配任意的
path: '/:path(.*)',
component: NotFound
},
对参数由一定的限定。
import News from "../views/News.vue"
{
// 动态路由的参数一定是数字
path: '/news/:id(\\d+)',http://localhost:3000/#/news/456/213
// 由多个参数
path: '/news/:id+',//http://localhost:3000/#/news/456/213/guuk
// 参数可有可无 * 参数可以重复叠加
path: '/news/:id*',//http://localhost:3000/#/news/
// 参数可有可无 ? 参数不可以重复叠加
path: '/news/:id?',
component: News
},
如果你需要匹配具有多个部分的路由,如 /first/second/third
,你应该用 *
(0 个或多个)和 +
(1 个或多个)将参数标记为可重复:
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
一些应用程序的 UI 由多层嵌套的组件组成。
在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
/user/johnny/profile /user/johnny/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
通过 Vue Router,你可以使用嵌套路由配置来表达这种关系。
//App.vue
Go to parent
//router/index.js
import Parent from "../views/Parent.vue"
import Styleone from "../views/Styleone.vue"
import Styletwo from "../views/Styletwo.vue"
{
path: '/parent',
component: Parent,
children: [
{
path: 'styleone',
component: Styleone
}, {
path: 'styletwo',
component: Styletwo
}
]
},
//Parent.vue
父组件
Parent
样式1
样式2
除了使用
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。需要经过判断,才能跳转页面(通过js进行跳转)
在 Vue 实例中,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。
$route:当前活跃的路由对象,path,params,query,name(当前的)
$router :push,forward,go方法(全局的)
想要导航到不同的 URL,可以使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
当你点击
时,内部会调用这个方法,所以点击
相当于调用 router.push(...)
:
声明式 | 编程式 |
---|---|
|
router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。
//index.js
{
name: 'news',
path: '/news/:id?',
component: News
},
// 通过传入对象
this.$router.push({ path: "/" })
// 带参数
this.$router.push({ path: "/user/123" })
//http://localhost:3000/#/user/123
this.$router.push({ name: "news", params: { id: 234 } })
//http://localhost:3000/#/news/234
// 带问号
this.$router.push({ path: "/about", query: { name: "zz" } })
//http://localhost:3000/#/about?name=zz
mounted () {
console.log(this.$route.query.name)//zz
console.log(this.$route)
//fullPath:当前路径,现在展示出来的路径
//path:定义的路由
},
它的作用类似于
router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
---|---|
|
router.replace(...) |
也可以直接在传递给 router.push
的 routeLocation
中增加一个属性 replace: true
。
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
// 替换当前位置
this.$router.push({ path: "/about", query: { name: "zz" }, replace: true })
this.$router.replace({ path: "/about", query: { name: "zz" } })
goBack () {
// 前进,传入为正值;后退,传入的值为负值
this.$router.go(-1)
this.$router.back() //后退,等于go(-1)
this.$router.forward()//前进,等于go(1)
}
除了
path
之外,你还可以为任何路由提供name
。这有以下优点:
params
的自动编码/解码。const routes = [
{
path: '/user/:username',
name: 'user',
component: User,
},
]
要链接到一个命名的路由,可以向 router-link
组件的 to
属性传递一个对象:
User
这跟代码调用 router.push()
是一回事:
router.push({ name: 'user', params: { username: 'erina' } })
在这两种情况下,路由将导航到路径 /user/erina
。
Go to News
//http://localhost:3000/#/news/234
想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有
sidebar
(侧导航) 和main
(主内容) 两个视图,这个时候命名视图就派上用场了。访问一个页面显示多个视图。
你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。
如果
router-view
没有设置名字,那么默认为default
。
//index.js
{
path: '/shop',
components: {
// 默认显示ShopMain
default: ShopMain,
// 它们与 `` 上的 `name` 属性匹配
ShopTop: ShopTop,
// 简写
ShopFooter
}
},
//App.vue
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标:
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
案例
{
path: '/',
// 重定向
redirect: '/home'
}, {
name: 'home',
path: '/home',
component: Home
},
或者反着写
{
path: '/home',
// 重定向
redirect: '/'
}, {
path: '/',
component: Home
},
{
path: '/',
// 重定向
// 命名路由
redirect: { name: "home" }
}, {
path: '/home',
name: 'home',
component: Home
},
// 方法:在跳转时需要做判断的时候用
redirect: (to) => {
console.log(to)
return { path: '/home' }
}
重定向是指当用户访问
/home
时,URL 会被/
替换,然后匹配成/
。
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
上面对应的路由配置为:
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以
/
开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
{
path: '/parent',
alias: '/father',//小名
alias: ['/father', '/fuqing'],//多个小名
component: Parent,
}
在你的组件中使用
$route
会与路由紧密耦合,这限制了组件的灵活性,因为它只能用于特定的 URL。虽然这不一定是件坏事,但我们可以通过props
配置来解除这种行为。
我们可以将下面的代码
const User = {
template: 'User {{ $route.params.id }}'
}
const routes = [{ path: '/user/:id', component: User }]
替换成
const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ['id'],
template: 'User {{ id }}'
}
const routes = [{ path: '/user/:id', component: User, props: true }]
这允许你在任何地方使用该组件,使得该组件更容易重用和测试。
当
props
设置为true
时,route.params
将被设置为组件的 props。
{
path: '/user/:id',
component: User,
props:true
},
//选项式API
export default {
props: ['id'],
mounted () {
console.log(this.$route.params.id)
console.log(this.id)
}
}
//组合式API
2.defineProps
和 defineEmits
单文件组件
defineProps
和 defineEmits
都是只在 中才能使用的编译器宏。他们不需要导入且会随着
处理过程一同被编译掉。
defineProps
接收与 props 选项相同的值,defineEmits
也接收 emits 选项相同的值。
defineProps
和 defineEmits
在选项传入后,会提供恰当的类型推断。
传入到 defineProps
和 defineEmits
的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。
对于有命名视图的路由,你必须为每个命名视图定义
props
配置:
const routes = [
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
//index.js
{
path: '/shop/:id',
components: {
default: ShopMain,
ShopTop: ShopTop,
ShopFooter
},
// 只在ShopMain能够接收这个id
props:{default:true,ShopFooter:false,ShopTop:false}
},
//ShopMain.vue
export default {
props: ['id'],
mounted () {
console.log(this.id)
// /http://localhost:3000/#/shop/1233 可以拿到1233
}
}
//ShopFooter.vue
export default {
props: ['id'],
mounted () {
console.log(this.id)
// undefined
}
}
在创建路由器实例时,
history
配置允许我们在不同的历史模式中进行选择。
hash 模式是用 createWebHashHistory()
创建的:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
它在内部传递的实际 URL 之前使用了一个哈希字符(#
)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。(改变url,不会向服务器发起请求)
当浏览器改变url的时候,不会向服务器发送请求,就不会刷新页面,可以让性能提高
用 createWebHistory()
创建 HTML5 模式,推荐使用这个模式:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
当使用这种历史模式时,URL 会看起来很 "正常",例如 https://example.com/user/id
。
https://example.com/user/id
,就会得到一个 404 错误。index.html
相同的页面。(后端)import { createWebHistory } from "vue-router"
const router = createRouter({
// 使用history模式
history: createWebHistory(),//http://localhost:3000/home
routes, // `routes: routes` 的缩写
})
//修改后,需要重启项目 二者区别有无hash符号(#)