单文件组件
Vue 单文件组件(又名 *.vue
文件,缩写为 SFC)是一种特殊的文件格式,它允许将 Vue 组件的模板、逻辑 与 样式封装在单个文件中。下面是 SFC 示例:
<script>
export default {
data() {
return {
greeting: 'Hello World!'
}
}
}
</script>
<template>
<p class="greeting">{{ greeting }}</p>
</template>
<style>
.greeting {
color: red;
font-weight: bold;
}
</style>
Vue SFC 是经典的 HTML、CSS 与 JavaScript 三个经典组合的自然延伸。每个 *.vue
文件由三种类型的顶层代码块组成:<template>
、<script>
与 <style>
:
<script>
部分是一个标准的 JavaScript 模块。它应该导出一个 Vue 组件定义作为其默认导出。<template>
部分定义了组件的模板。<style>
部分定义了与此组件关联的 CSS。
工作原理
Vue SFC 是框架指定的文件格式,必须由 @vue/compiler-sfc 预编译为标准的 JavaScript 与 CSS。编译后的 SFC 是一个标准的 JavaScript(ES)模块
[!note]
我感觉就是一个一个的组件打包在一块,要用其他组件就引入就行了
如果要引入其他组件,用法如下:
import MyComponent from './MyComponent.vue'
export default {
components: {
MyComponent
}
}
SFC 中的 <style>
标签通常在开发过程中作为原生 <style>
标签注入以支持热更新。对于生产环境,它们可以被提取并合并到单个 CSS 文件中
工具
在线演练场
你不需要在你的机器上安装任何东西来尝试 Vue 单文件。这里有很多在线演练场允许你在浏览器中运行:
- Vue SFC Playground (官方,基于最新的提交)
- VueUse Playground
- Vue on CodeSandbox
- Vue on Repl.it
- Vue on Codepen
- Vue on StackBlitz
- Vue on Components.studio
- Vue on WebComponents.dev
在报告问题时也建议通过这些在线演练场来提供复现。
项目脚手架
Vue CLI 是 Vue 官方基于 webpack 的构建工具。可以通过 Vue CLI 进行使用:
创建后的项目配置可以参考:https://cli.vuejs.org/zh/config/
npm install -g @vue/cli
vue create hello-vue
[!tip]
浏览一下创建的目录,结合之前的一些知识,一目了然
目录说明
git(隐藏文件) =》git init
node_modules =》项目本地所有依赖的包文件
public =》本地服务的文件夹
|index.html =》主页
src =》工作目录
|assets =》资源文件(图片、css)
|components =》组件
|App.Vue =》跟组件
|main.js =》项目的全局配置
.gitignore =》不需要上传到仓库中的文件的配置
babel.config.js =》有关bable的配置
package.json =》项目基本配置说明
package-lock.json =》版本范围
README.md =》说明文件
IDE支持
VSCode插件 + Volar扩展
多版本nodejs
因为我电脑有gitbook,需要nodejs版本为v10.24.1
,但是vue cli
有需要12以上的,所以需要多版本共存
# nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
# node安装
nvm install node # "node" is an alias for the latest version
nvm install 6.14.4 # or 10.10.0, 8.9.1, etc
nvm install 14 # 长期维护版本
# 列出已安装的版本
nvm ls
# 设置当前node版本
nvm alias default stable # 最新稳定版
nvm alias default 14 # 指定版本
# 不同node版本切换
nvm use 14
路由
简单路由
如果你只需要非常简单的路由而不想引入一个功能完整的路由库,可以像这样动态渲染一个页面级的组件:
[!note]
简而言之,就是定义几个组件,然后在不同的路由的时候就切换到对应的组件,有点麻烦
<html>
<head>
<script type="text/javascript" src="./vue.global.js"></script>
</head>
<body>
<div id="test">
</div>
<script type="text/javascript">
const { createApp, h } = Vue
// 定义3个组件
const NotFoundComponent = { template: '<p>Page not found</p>' }
const HomeComponent = { template: '<p>Home page</p>' }
const AboutComponent = { template: '<p>About page</p>' }
// 定义路由
const routes = {
'/': HomeComponent,
'/about': AboutComponent
}
// 定义根组件
const SimpleRouter = {
data: () => ({
currentRoute: window.location.pathname
}),
computed: {
CurrentComponent() {
return routes[this.currentRoute] || NotFoundComponent
}
},
render() {
return h(this.CurrentComponent)
}
}
createApp(SimpleRouter).mount('#test')
</script>
</body>
</html>
官方路由
对于大多数单页面应用,都推荐使用官方支持的 vue-router 库。更多细节可以移步 vue-router 文档
Via CDN:
<script src="https://unpkg.com/vue-router@4"></script>
In-browser playground on CodeSandbox
Add it to an existing Vue Project:
npm install vue-router@4
当加入 Vue Router 时,我们需要做的就是将我们的组件映射到路由上,让 Vue Router 知道在哪里渲染它们
[!note]
官方教程没有用脚手架,是单独的,获取一个app应用,然后
app.use(router)
;考虑到后期基本都是脚手架模式来开发,所以我这里就用脚手架的例子来记录
HTML部分说明
<div id="app">
<h1>Hello App!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
router-link
[!tip]
用来跳转路由的,
to
属性是路由的位置
没有使用常规的 a
标签,而是使用一个自定义组件 router-link
来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
router-view
[!tip]
用来展示路由对应组件内容的
router-view
将显示与 url 对应的组件内容。你可以把它放在任何地方,以适应你的布局。
创建带有路由的脚手架项目
vue create hello-vue
cd hello-vue
vue add router
这样就是一个带有路由的脚手架项目了
编写过程
我感觉主要有4步
components
中编写组件views
中导入显示组件(简单的情况下可以和上面的合并成一个步骤)router/index.js
中添加路由- 对应的展示界面添加
<router-link to=>
动态路由
带参数的动态路由
就是动态参数映射到一个路由上,比如User/aaa
和User/bbb
都映射到User/:name
上,再根据对应的用户名返回对应的结果;这种在路径中使用一个动态字段来实现,我们称之为 路径参数
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params
的形式暴露出来。因此,我们可以通过更新 User
的模板来呈现当前的用户 ID:
vue
文件
<template>
<div class="home">
Now param {{ $route.params }} <br/>
Now name {{ $route.params.name }}
</div>
</template>
<script>
export default {
name: 'HomeTest',
}
</script>
- 路由
{
path: '/test/:name',
name: 'hometest',
component: TestView
}
访问 /test/aaa
可以在同一个路由中设置有多个 路径参数,它们会映射到 $route.params
上的相应字段。例如:
匹配模式 | 匹配路径 | $route.params |
---|---|---|
/users/:username | /users/eduardo | { username: 'eduardo' } |
/users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
除了 $route.params
之外,$route
对象还公开了其他有用的信息,如 $route.query
(如果 URL 中存在参数)、$route.hash
等。你可以在 API 参考中查看完整的细节。
响应路由参数的变化
上面那个动态路由,每一次访问后,比如从aaa
到bbb
,都会重新渲染一次,相同的组件实例将被重复使用,造成了一定情况下资源的浪费;
所以有了另一种方法,不需要销毁了再创建,而是直接监听变化,这样效率会更高。不过,这也意味着组件的生命周期钩子不会被调用。
要对同一个组件中参数的变化做出响应的话,你可以简单地 watch $route
对象上的任意属性,在上面的场景中,就是 $route.params
<template>
<div class="home">
Now param {{ $route.params }} <br/>
Now name {{ $route.params.name }}
</div>
</template>
<script>
export default {
name: 'HomeTest',
watch: {
'$route'(to, from) {
console.log(to)
console.log(from)
}
}
}
</script>
[!warning]
触发的话,需要用鼠标点击跳转过去才会触发,直接改浏览器的URL地址是不会有效果的
捕获所有路由或 404 Not found 路由
如果我们想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
- 路由JS
{
path: '/:pathMatch(.*)*',
name: '404',
component: NotFound
},
参数中定义正则
假如存在2个路由,/:orderId
和 /:productName
,两者会匹配完全相同的 URL,所以我们需要一种方法来区分它们(不考虑改变路由的情况)
情况分析:orderId
只能是数字,productName
可以是任何值
解决方法:
{
path: '/test/:name',
name: 'hometest',
component: TestView
},
{
path: '/test/:id(\\d+)',
name: 'numberPath',
component: HomeView
},
如果访问/test/123
那么就会使用组件HomeView
[!note]
确保转义反斜杠(
\
),就像我们对\d
(变成\\d
)所做的那样,在 JavaScript 中实际传递字符串中的反斜杠字符。
匹配多部分路由
如果你需要匹配具有多个部分的路由,如 /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*' },
]
也可以通过在右括号后添加它们与自定义正则结合使用
const routes = [
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: '/:chapters(\\d+)+' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
严格匹配
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users
将匹配 /users
、/users/
、甚至 /Users/
。这种行为可以通过 strict
和 sensitive
选项来修改,它们可以既可以应用在整个全局路由上,又可以应用于当前路由上:
const router = createRouter({
history: createWebHistory(),
routes: [
// 将匹配 /users/posva 而非:
// - /users/posva/ 当 strict: true
// - /Users/posva 当 sensitive: true
{ path: '/users/:id', sensitive: true },
// 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
{ path: '/users/:id?' },
]
strict: true, // applies to all routes
})
可选参数
可以通过使用 ?
修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
请注意,*
在技术上也标志着一个参数是可选的,但 ?
参数不能重复。
嵌套路由
上面的都是定义好了所有的路由,可能一个user
路由下面有多个其他的路由,比如user/info
、user/name
等,这样每一个都单独写一次/user
会很麻烦,所以也就有了嵌套
TestView.vue
文件
<template>
<div class="home">
Now param {{ $route.params }} <br/>
Now name {{ $route.params.name }}
<router-view></router-view>>
</div>
</template>
<script>
export default {
name: 'HomeTest',
}
</script>
- 路由
{
path: '/test/:name',
name: 'hometest',
component: TestView,
children: [
{
path: 'child',
name: 'home1',
component: HomeView // HomeView会被渲染到TestView的 <router-view> 中
},
]
},
- 效果
编程式导航
导航到其他路径
在 Vue 实例中,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
。
当你点击 <router-link>
时,内部会调用这个方法,所以点击 <router-link :to="...">
相当于调用 router.push(...)
:
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
简单举例如下:
<template>
<div class="home">
Now param {{ $route.params }} <br/>
Now name {{ $route.params.name }}
<a @click="test" href="#">123</a>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'HomeTest',
methods: {
test() {
this.$router.push({ path: '/test/555' })
}
},
}
</script>
点击123
那么就会跳转到/test/555
其他的一些常规用法:
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
[!note]
如果提供了
path
,params
会被忽略,上述例子中的query
并不属于这种情况。
由于属性 to
与 router.push
接受的对象种类相同,所以两者的规则完全相同。
替换当前位置
它的作用类似于 router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
也可以直接在传递给 router.push
的 routeLocation
中增加一个属性 replace: true
:
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
前后跳转
就是对于历史记录的前进和后退,类似于 window.history.go(n)
例子
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
命名路由
前面跳转到一个路由,都是通过路径path
来,比如/test/123
,其实vue也提供了name
参数来支持,优点如下:
- 没有硬编码的 URL
params
的自动编码/解码。- 防止你在 url 中出现打字错误。
- 绕过路径排序(如显示一个)
假设一个路由如下:
{
path: '/test/:name',
name: 'hometest',
component: TestView,
},
那么我们可以用如下的方式来:
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link :to="{name: 'hometest', params: {name: 123}}">Test</router-link>
</nav>
<router-view></router-view>
</template>
和router.push()
一样
router.push({ name: 'hometest', params: { name: '123' } })
命名视图(多个组件)
有时候需要同级展示多个组件视图,而不是嵌套展示,但是通常只有一个<route-view>
出口,这个时候命名视图派上用场了
<router-view></router-view>
<router-view name="HomeView"></router-view>
如上编写视图展示的地方,路由如下:
{
path: '/test/:name',
name: 'hometest',
components: {
default: TestView,
HomeView // HomeView: HomeView 的缩写
}
},
这样HomeView
组件就会渲染到对应的地方了
嵌套命名视图和嵌套路由类似,只要对应的组件有输出点即可。
重定向和别名
重定向
还是通过设置router
来完成
{
path: '/test/:name',
name: 'hometest',
components: {
default: TestView,
HomeView // HomeView: HomeView 的缩写
},
redirect: "/"
},
访问/test/xxx
会直接跳转到/
同理,也可以重定向到一个命名的路由
{
path: '/test/:name',
name: 'hometest',
components: {
default: TestView,
HomeView // HomeView: HomeView 的缩写
},
redirect: {
name: "home"
}
},
甚至返回一个方法
{
path: '/test/:name',
name: 'hometest',
components: {
default: TestView,
HomeView // HomeView: HomeView 的缩写
},
redirect:
to => {
return {
path: '/about',
query: {
q: to.params.name
}
}
}
},
别名
重定向是指当用户访问 /home
时,URL 会被 /
替换,然后匹配成 /
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
{
path: '/test/:name',
name: 'hometest',
components: {
default: TestView,
HomeView // HomeView: HomeView 的缩写
},
alias: "/aaa/:name",
},
如上,访问/aaa/xxx
就相当于访问/test/xxx
,且路由显示为/aaa/xxx
props传参
之前在用组件嵌套的时候,通过props
传参的,这里仍然可用
当 props
设置为 true
时,route.params
将被设置为组件的 props。
TestView.vue
<template>
<div class="home">
Now name {{ name }}
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'HomeTest',
props: ['name'],
}
</script>
- 路由
{
path: '/bbb/:name',
name: 'hometest2',
component: TestView,
props: true
},
- 效果