Vue CLI是vue官方脚手架,可以快速创建vue项目
安装vue-cli3
npm install -g @vue/cli
或者
yarn global add @vue/cli
升级
npm update -g @vue/cli
或者
yarn global upgrade –latest @vue/cli
创建vue项目
vue create vue-demo
如果提示vue : 无法将“vue”项识别为 cmdlet、函数、脚本文件或可运行程序的名称,可以使用npx vue create vue-demo
default (babel, eslint) // 默认选项,包含babel和eslint
Manually select features // 自定义创建配置工程
一般比较常用的有Babel,TypeScript,Router,CSS Pre-processors,Linter / Formatter
空格为选择,enter为下一步
跑vue项目
npm run serve
或者
yarn start/yarn run dev
如果运行报错error Component name “Home” should always be multi-word vue/multi-word-component-names
只需要在vue.config.js添加lintOnSave: false配置,例如:
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false
})
这个问题的原因在于vue-cli默认开启了ESLint检测,关闭就好了
Vue-router是vue官方推出的vue官方路由管理工具,和vue核心深度集成
该Vue-router路由被用于单页面应用,组件的切换,而不想其他那样可以使用超链接来切换页面
安装router
npm install vue-router –save-dev
或者
vue add router
例如:
App.vue
<template>
<router-link to="/Home/Index">hallo index</router-link>
<router-link to="/Hallo">hallo</router-link>
<router-view></router-view>
</template>
router.js
import {createRouter, createWebHashHistory} from "vue-router"
import Hallo from './components/Hallo'
import Home from './components/Home'
import Index from './components/Index'
const routes = [
{
path: "/Home",
name: "Home",
component: Home,
children: [
{path:"/",component: Home},
{path:"Index",component: Index}
]
},
{
path: "/Hallo",
name: "Hallo",
component: Hallo
}
]
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
export default router
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
为路由匹配到组件(路由视图),它会表示渲染出使用切换的组件
path是链接路径,name为链接名称,component为导入的组件模板名,多个地址用逗号分开,children为子路由声明,
带参数的动态路由
User.vue
<template>
<div>
{{ $route.params.id }}
</div>
</template>
router.js
{ path: “/User/:id”, name: “User”, component: User }
访问http://localhost:8080/#/User/admin
可以看页面显示了admin,说明通过路由传递了参数到模板中,其中id的参数在模板是以$route.params.id方式获取的
去掉#:因为我这里vue-router路由模式设置为hash(createWebHashHistory),需要开启history(createWebHistory)
import {createWebHistory} from "vue-router"
const router = createRouter({
history: createWebHistory(),
routes: routes
})
获取获取以什么开头的路由
{
path: '/User:pathMatch(.*)*',
name: 'User',
component: User
},
可以看到使用了路由,例如http://localhost:8080/Useraaa/等等,只要是Userxxx以及Userxxx/xxx都会渲染User组件
path: ‘/User:afterUser(.*)’,这个效果和上面一样
当Url需要纯数字时:
{
path: '/User/:id(\\d+)',
name: 'User',
component: User
},
http://localhost:8080/User/666
复用参数(0个或多个用*,1个或多个用+)
在上面的基础上改:
{
path: '/User/:id(\\d+)+',
name: 'User',
component: User
},
访问http://localhost:8080/#/User/666/111,可以看到,模板的{{ $route.params.id }},渲染出[ “666”, “111” ]这样的数组,而不是字符串了,因此可以使用$route.params.id[1],来精确获取第几个路由
嵌套路由(User路由中使用了Admin路由,子路由)
User.vue
<template>
<div>
{{ $route.params.id }}
<router-view></router-view>
</div>
</template>
UserAdmin.vue
<template>
<div>
hallo admin
</div>
</template>
router.js
{
path: '/User/:id(\\d+)+',
name: 'User',
component: User,
children: [
{
path: 'admin',
component: UserAdmin
}
]
},
访问http://localhost:8080/#/User/666/admin,可以看到hallo admin
编程式路由(非router-link)
App.vue
<template>
<div @click="goAdmin">go</div>
<br/>
<router-view></router-view>
</template>
<script>
import {useRouter} from 'vue-router'
export default{
setup(){
const router = useRouter()
let goAdmin = () =>{
router.push("/User/123")
}
return{
goAdmin
}
}
}
</script>
可以看到点击了<div @click=“goAdmin”>go,就会跳转到http://localhost:8080/User/123
除了通过字符串外,还可以router.push({path: ‘/User/123’})
传递参数(模板{{ $route.params.id }})
params方式
router.push({name: ‘User’, params: { id: 666 }})
访问http://localhost:8080/User/666
query方式
router.push({path: ‘/User’, query: { id: 123 }})
访问http://localhost:8080/User?id=123
query方式获取参数,需要使用{{$route.query.id}},而不是$route.params.id了
hash方式
router.push({ path: ‘/User’, hash: ‘#admin’ })
访问http://localhost:8080/User#admin
使用{{$route.hash}}获取参数
动态路由参数
let user = ‘admin’ router.push({ name: ‘User’, params: { user } })
replace(使用该跳转,将不会创建会话历史记录,会替代上一个会话的记录)
router.replace({ path: ‘/User’})
使用replace: true参数也是可以给push设置(push的replace默认为false)
router.push({ path: ‘/User’, replace: true })
会话历史记录跳跃
前进n条记录(前进2页) router.go(2)
返回n条记录(后退2页面)
router.go(-2)
例如:
App.vue
import {useRouter} from 'vue-router'
export default{
setup(){
const router = useRouter()
let goAdmin = () =>{
router.go(2)
}
return{
goAdmin
}
}
}
路由的name属性的作用:
除了像这样用外Go
还可以用name属性指向一个路由的path,防止路由地址写错,设置params属性给模板接收参数(这个参数不会作用于URL),Go
当需要在同级下渲染多个router-view,如何确保其router-view渲染的是自己想要的,利用router-view的name属性(该属性默认值为default)
App.vue
router.js
import User from './components/User.vue'
import A from './components/a.vue'
import B from './components/b.vue'
import C from './components/c.vue'
const routes = [
{
path: '/User',
name: 'User',
components:{
default: User,
a:A,
b:B,
c:C
}
}
]
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
export default router
访问http://localhost:8080/User,可以看到渲染出了a组件,b组件,c组件
其中router.js的小写a就是指向了router-view的name属性,大写的A就是组件,和name属性对应上(键值对),将其对应渲染到相应的router-view中
components属性为多组件配置
重定向(当希望访问到某个路由时,能跳转到另一个路由时)
const routes = [
{
path: "/Home",
name: "Home",
redirect: '/',
components: {
default: Home
}
}
]
访问http://localhost:8080/Home
设置还可以指向一个路由的name属性,const routes = [{ path: ‘/Home’, redirect: { name: ‘User’ } }]
另外如果一个路由使用了redirect重定向,那么是可以不用写component了,因为不会真正访问到(访问到就重定向到另一个路由了),但是存在子路由时就需要写component了
当重定向发生时,也需要传递参数到重定向时,例如:
{
path: '/User/:id',
name: 'User',
components:{
default: User,
} ,
redirect: to =>{
return { path: '/', query: { id: to.params.id } }
}
}
当访问http://localhost:8080/User/123时,会重定向到http://localhost:8080/?id=123,如果/User/123的123,被传到query了,另外还可以在重定向的目标组件模板用{{$route.query.id}}获取到该参数
重定向别名
{
path: "/Home",
name: "Home",
components: {
default: Home,
},
alias: '/User',
}
别名就是访问http://localhost:8080/User/时,实质上是访问/Home这个路由,和redirect的区别就是,redirect会改变URL,而alias不会改变URL
利用子路由来给多路由匹配组件
{
path: "/Home/:id",
name: "Home",
component: Home,
children: [
{
path: '',
component: Homea,
alias: [':id','admin', '/User']
},
]
}
当访问http://localhost:8080/Home/123,http://localhost:8080/Home/admin,http://localhost:8080/User时,渲染的是Home组件
路由props传参(通过props接收参数)
routes.js
{
path: '/hallo/:id',
component: Home,
props: true
}
Home.vue
<template>
<div>
hallo {{id}}
</div>
</template>
<script>
export default {
props: ['id']
}
</script>
当props为true时,其route.query(router.params)将被设置为该组件的属性,可直接访问其参数
对于有命名视图的路由,必须为每个命名视图定义props配置,例如:
{
path: "/Home/:id",
name: "Home",
components: {
default: Home,
index: Index
},
props:{
default: true,
index: false
},
},
在上面例子中props是对象形式,可以看到index组件就算props接收了id参数,但是因为其路由props设置了false,导致其route.query(router.params)没有设置为该组件的属性,因此没有id参数
routes.js
{
path: "/Home",
name: "Home",
component: Home,
props: (route) => ({id : route.query.id })
},
Home.vue
<template>
<div>
hallo {{id}}
</div>
</template>
<script>
export default {
props: ['id']
}
</script>
访问http://localhost:8080/Home?id=123,Home输出123
上面例子中route.query.id就是当前query(id=123),将其传入到id中,然后模板接收
注意:函数模式请不要用于多组件components中,在多组件components中,需要给除default外的组件设置router-view的name属性
导航守卫(导航守卫实质上就是路由跳转时的钩子函数,守卫是异步执行的)
全局前置守卫:beforeEach
router.beforeEach((to,form)=>{ return false})
守卫接收2个参数,to是跳转的目标路由,form是跳转离开的路由,返回值如果为false时,表示取消当前导航,当返回值为路由地址时,会跳转到该路由地址,还有一个next()可选参数,该可选参数表示放行
当next(false)时,表示取消当前导航,当next(’/Home’)时,会跳转到/Home(和router.push类似)
如果守卫抛出Error时,取消导航并且调用router.onError()
守卫正常时是undefined或返回true
例如:
routes.js
const router = createRouter({
history: createWebHistory(),
routes: routes
})
router.beforeEach((to, from, next) => {
if (to.name !== "Home") {
next({
name: 'Home'
})
} else {
next()
}
})
像上面的例子中,全局路由守卫将会监听要跳转的路由的name属性是否为Home,如果不是Home就会调用next()方法跳转到name为Home的路由,如果是Home的话守卫放行
全局解析守卫:beforeResolve
全局解析守卫和全局前置守卫类似,都是路由跳转前调用,但是beforeResolve会在beforeEach(全局前置守卫)和beforeRouteEnter(组件路由守卫)之后触发,afterEach(全局后置守卫)之前触发
router.beforeResolve((to, from, next) => {
if (to.name !== "Home") {
next({
name: 'Home'
})
} else {
next()
}
})
可以看到效果和beforeEach一样
全局后置守卫:afterEach(没有next(),全局后置守卫顾名思义,就是路由跳转后触发)
router.afterEach((to, from) => {
console.log(to.name)
console.log(from.name)
})
路由独享的守卫beforeEach(在路由内部定义的beforeEach守卫)
{
path: "/Home",
name: "Home",
component: Home,
beforeEach: (to,from) =>{
if (to.name !== "Home") {
next({
name: 'Home'
})
} else {
next()
}
}
},
路由独享的守卫beforeEach只在进入路由时触发(params,query,hash改变并不会触发)
组件路由守卫:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
beforeRouteEnter:在渲染该组件时对应的路由被确定前触发,在该守卫触发时,组件实例未被创建,因此不能获取组件实例的任何东西
beforeRouteUpdate:当前路由发送改变,但是该组件又被复用时触发,比如传参,只是改变参数,但是复用了当前组件,该守卫触发时,组件实例已被挂载,可访问组件实例
beforeRouteLeave:当导航离开渲染该组件的路由时触发,可访问组件实例
例如:
Home.vue
export default {
beforeRouteEnter(to, from, next) {
next(vm =>{
console.log(vm) // 可以看到vm是组件实例,说明可以通过next来访问组件实例,next()会在导航被确认时被回调
})
},
beforeRouteUpdate(to, from, next) {
if (to.name !== "Home") {
next({
name: 'Home'
})
} else {
next({
name: 'User'
})
}
console.log(to.name)
console.log(from.name)
},
beforeRouteLeave(to, from, next) {
if(to.name !=="Home"){
if(confirm('您的信息还未保存,确定要离开吗?') == true){
next()
}else{
next(false)
}
}
console.log(to.name)
console.log(from.name)
},
}
路由元信息(路由meta字段属性)
{
path: "/Home",
name: "Home",
component: Home,
meta: { ha: false }
}
},
router.beforeEach((to, from, next) => {
if (to.name == 'Home' && to.meta.ha){
next()
}else{
next(false)
}
})
路由懒加载(动态导入)
const User = () => import('./components/User.vue')
const routes = [{
path: '/User',
name: 'User'
component: User
}]
动态路由
const routes = [{
path: '/:id',
name: 'User'
component: User
}]
const router = createRouter({
history: createWebHistory(),
routes: routes
})
router.addRoute({ path: '/Home',name: 'Home', component: Home }) // 添加路由
router.removeRoute('Home') // 删除路由
注意:当路由被删除时,路由别名和子路由同时也被删除
不只是在路由router中设置,还可以在路由守卫设置,而且当2个addRoute路由的name重名时,也会触发删除路由,当路由没有name属性时,可通过定义回调函数,让其重新添加路由(实质上是删除路由,因为重复了),例如:
const RoutesData = router.addRoute(path: '/Index', component: Home )
RoutesData()
嵌套路由
router.addRoute({ path: '/User', name: 'User', component: User })
router.addRoute('User', { path: 'Admin', component: Home })
vue中解决xss脚本攻击(依赖于xss模块)
npm install xss –save
引用xss模块
import xss from 'xss'
Object.defineProperty(Vue.prototype, '$xss', {
value: xss
})
在评论框或者其他输入框等等要针对免疫xss地方使用$xss()方法
自定义拦截规则
在data字段,return字段下设置白名单,格式就是标签名加属性,只要不是白名单的标签和属性,就是会被过滤掉
options : {
whiteList: {
a: ['href', 'title'],
div: ['class']
}
}
vue中解决html和Markdown转换
MarkdownToHtml(处理md转换为html)
npm install showdown –save
使用方法:
import showdown
let converter = new showdown.Converter()
let text = '# hallo word' // md格式的文本
let html = converter.makeHtml(text)
HtmlToMarkdown(处理html转换为md)
npm install turndown –save
使用方法:
import turndown
let turndownService = new turndown()
let markdown = turndownService.turndown('<h1>hallo word</h1>')
vuejs是声明式视图层框架,视图层框架分为命令式和声明式两种,命令式关注过程,声明式关注结果,命令式的代码描述了工作过程,而声明式的代码只声明,结果由框架来提供
vuejs内部实现肯定是命令式的,但是进行封装了工作过程,对外暴露的是声明式,声明式的性能并没有命令式的性能优,因为命令式的工作流程一目了然,去除或者添加某个功能,理论上能做到最优性能
而声明式只关注了结果,需要查找前后代码的差异(虚拟dom+diff算法),然后再更新,vuejs选择声明式的方案是为了可维性,不需要关心工作过程,只需要关注结果就好了
虚拟DOM对比innerHTML操作DOM
创建DOM:虚拟DOM和innerHTML操作DOM都是创建所有DOM元素,因此虚拟DOM和innerHTML操作DOM基本上性能是一致的(DOM层计算)
更新DOM:innerHTML操作DOM更新是先重新创建HTML字符串,然后再更新DOM元素(销毁旧的DOM元素,重新创建一个完整的DOM元素),而虚拟DOM是通过创建新的JavaScript对象,然后前后俩个JavaScript对象进行DIff比较,得到差异,只更新差异
不难看出,虚拟DOM的优势在于更新DOM
渲染器的作用就是将虚拟DOM(JavaScript对象)渲染成真实的DOM
渲染器将JavaScript对象的值获得,并且以原生JavaScript方式渲染出来,当tag为字符串时,渲染标签元素,如果是函数(或者对象)那么表示该是组件,组件函数返回值本身就是虚拟DOM,如果是对象的话,获取对象的render函数,得到其返回值(这个实质上也是虚拟DOM),编译器(模板)原理上和渲染器是一样的
transition(过渡和动画)
过渡:元素属性从一个属性过渡为另一个属性,例如元素的背景颜色从黑色过渡到白色
动画:一个元素从一个地方移动到另一个地方
vue内置了组件和API来完成动画和过渡
过渡:
const app = Vue.createApp({
data(){
return {
ishallo: false
}
},
methods:{
onhalloClick(){
this.ishallo = ! this.ishallo
}
},
template: `
<button @click='onhalloClick'>点我隐藏/显示</button>
<transition>
<div v-if="ishallo">hallo word</div>
</transition>
`
})
const vm = app.mount("#app")
动画
vue
const app = Vue.createApp({
data(){
return {
ishallo: false
}
},
template: `
<div :class='{ hallo: ishallo }'>
<button @click='ishallo = true'>点我触发动画</button>
<div v-if="ishallo">hallo word</div>
</div>
`
})
const vm = app.mount("#app")
css样式
.hallo {
animation: hallo 1s linear
}
@keyframes hallo {
0% {
transform: translate3d(0px, 0, 0)
}
10% {
transform: translate3d(-2px, 0, 0)
}
60% {
transform: translate3d(2px, 0, 0)
}
100% {
transform: translate3d(0px, 0, 0)
}
}
硬件加速:使用了perspective,backface-visibility和transform: translateZ()都可以触发硬件加速
解决过渡和动画时间不一致
,当使用该属性,动画结束,过渡会跟着结束
,当使用该属性,过渡结束,动画会跟着结束
统一管理动画和过渡时间
,该属性值单位为毫秒,意思为1秒后结束动画和过渡
,enter为进入动画和过渡时间,leave为离开动画和过渡时间
transition内置组件在一个过渡周期会触发6个状态,分别是v-enter-from,v-enter-active,v-enter-to,v-leave-from,v-leave-active,v-leave-to
v-enter-from:表示过渡的开始状态,元素插入之前触发,元素插入后的下一帧移除
v-enter-active:表示过渡生效时状态,在元素插入之前触发,在动画和过渡完成后移除
v-enter-to:表示过渡的结束状态,在元素插入后下一帧触发,在动画和过渡完成后移除
v-leave-from:表示离开过渡的开始状态,离开过渡时立即触发,下一帧后移除
v-leave-active:表示离开过渡的生效状态,离开过渡时立即触发,在动画和过渡完成后移除
v-leave-to:表示离开过渡的结束状态,离开过渡后下一帧触发,在动画和过渡完成后移除
用法:
const app = Vue.createApp({
data(){
return {
ishallo: false
}
},
template: `
<transition>
<button @click='ishallo = true'>点我触发动画</button>
<div v-if="ishallo">hallo word</div>
</transition>
`
})
const vm = app.mount("#app")
样式
.v-enter-active{
animation: hallo 1s; // 表示入场时触发时hallo动画
}
.v-leave-active{
animation: hallo 1s; // 表示出场时触发时hallo动画
}
@keyframes hallo {
0% {
transform: translate3d(0px, 0, 0)
}
10% {
transform: translate3d(-2px, 0, 0)
}
60% {
transform: translate3d(2px, 0, 0)
}
100% {
transform: translate3d(0px, 0, 0)
}
}
关闭css动画:,使用该属性表示将通过组件事件来触发动画,不再使用css动画了
过渡模式:,该属性有2个值,out-in和in-out,默认同时进行
out-in:当前元素先进行过渡,完成后新元素过渡进入
in-out:新元素先进行过渡,完成后当前元素过渡离开
该常用于动态组件的切换过渡,也是说out-in是当前元素先进行过渡,切换到新元素时,过渡进入,而in-out是新元素过渡,切换到当前元素时过渡离开
name属性:,该属性表示自动生成css过渡类名,像例子中的,将生成为.hallo-enter等等,就是将过渡周期中的6个状态的v改成name属性值了
vue devtools调试工具
下载https://github.com/vuejs/vue-devtools
解压,并且在该目录执行npm install
修改manifest.json(该文件在项目目录的packages/shell-chrome),将persistent改为true
打包npm run build
以谷歌浏览器为例子,加载已解压的扩展程序,选择刚刚打包的目录的packages/shell-chrome文件夹
vue3引入了tree-shaking技术,当没有在项目中使用某个模块时,那么该模块的代码将不会出现在打包中
vue3抛弃了Object.defineProperty,改用Proxy来做数据的响应式
vue3不再使用src托管,而采用monorepo管理
vue3采用typescript编写源码,对typescript的兼容更好,vue2使用的是flow来处理类型检查
vue3提供了Composition API,对于逻辑复用支持更好