# 起步
安装
npm i -g @vue/cli |
创建
vue create mypoject | |
npm run serve |
# vsCode 插件
vetur
:代码高亮Vue VSCode Snippets
:输入 vbase-
生成代码基本结构
<template> | |
<div></div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return {} | |
}, | |
methods: {} | |
} | |
</script> | |
<style scoped></style> |
# 自定义组件
在 mypoject/src/components
中新建 HelloWorld.vue
<template> | |
<div></div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return {} | |
}, | |
methods: {} | |
} | |
</script> | |
<style scoped></style> |
在 mypoject/src/App.vue
中添加
<template> | |
<!-- 输出 --> | |
<div id="app"> | |
<HelloWorld /> | |
</div> | |
</template> | |
<script> | |
// 引用 | |
import HelloWorld from './components/HelloWorld.vue' | |
// 挂载 | |
export default { | |
components: { | |
HelloWorld | |
} | |
} | |
</script> | |
<style></style> |
# 根组件详解
主要是根组件的代码解析
# render 渲染函数
该渲染函数接收一个 createElement
方法作为第一个参数用来创建 VNode,也就是这个 h
它来自单词 hyperscript
,这个单词通常用在 virtual-dom 的实现中
hyperscript
本身是指生成 HTML 结构的 script 脚本,因为 HTML 是 hyper-text markup language 的缩写(超文本标记语言)
render: function (createElement) { | |
return createElement(App); | |
} | |
// 可以缩短为: | |
render (createElement) { | |
return createElement(App); | |
} | |
// 作为上述 h 别名 createElement,可以缩短为: | |
render (h){ | |
return h(App); | |
} | |
// ES6 语法 | |
render: h => h(App); |
# createElement 函数
new Vue()
创建一个新的 Vue 实例
createElement
生成一个 VNode 节点, render
函数接受 VNode 节点,返回给 Vue.js 的 mount
函数,渲染成真实 DOM 节点,并手动挂载到根节点上
# 节点、树以及虚拟 DOM
浏览器会建立一个 DOM 节点树 来保持追踪所有内容
每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。
使用一个模板或者一个渲染函数里,Vue 都会自动保持页面的更新
createElement()
会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息,我们把这样的节点描述为虚拟节点 (virtual node),也常简写它为 VNode
Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM
虚拟 DOM
是我们对由 Vue 组件树建立起来的整个 VNode树
的称呼
# 生命周期钩子
生命周期图示
查看图片
生命周期,主要有 4 个阶段,8 个函数,2 个常用
<template> | |
<!-- ./components/Pra05.vue --> | |
<div> | |
<h1 ref="title"></h1> | |
<button @click="str = 'Vue太幸福了'">点击修改</button> | |
<div class="box" ref="box"></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
str: '我爱Vue', | |
num: 88, | |
timer: null | |
} | |
}, | |
// 生命周期函数写在 data 的同级 | |
beforeCreate() { | |
// 数据创建之前 undefined | |
console.log(this.num) | |
}, | |
created() { | |
// 数据创建之后 num =88 | |
console.log(this.num) | |
this.timer = setInterval(() => { | |
console.log('定时器') | |
}, 2000) | |
}, | |
beforeMount() { | |
// 页面挂载之前 undefined | |
console.log(this.$refs.box) | |
}, | |
mounted() { | |
// 页面已经渲染完毕了 | |
// <div data-v-6f3466e4="" class="box"></div> | |
console.log(this.$refs.box) | |
}, | |
// 更新阶段不会在 DOM 初次渲染时调用 | |
beforeUpdate() { | |
// DOM 更新之前 str: "我爱 Vue" | |
//console.log (' 执行了 beforeUpdate') | |
console.log(this.$refs.title.innerHTML) | |
}, | |
updated() { | |
//DOM 更新之后 str = 'Vue 太幸福了' | |
console.log(this.$refs.title.innerHTML) | |
}, | |
// 定时器并不会被回收 | |
beforeDestroy() { | |
clearInterval(this.timer) | |
console.log('在销毁之前') | |
}, | |
destroyed() { | |
console.log(this.timer) | |
console.log('在销毁之后') | |
} | |
} | |
</script> | |
<style scoped> | |
.box { | |
width: 100px; | |
height: 100px; | |
background-color: #ccc; | |
} | |
</style> |
当我们点击切换销毁组件,就清除定时器
<template> | |
<!-- App.vue --> | |
<div id="app"> | |
<button @click="flag = !flag">点击切换</button> | |
<Pra04 v-if="flag" /> | |
<Pra05 v-else /> | |
</div> | |
</template> | |
<script> | |
import Pra04 from './components/Pra04.vue' | |
import Pra05 from './components/Pra05.vue' | |
export default { | |
data() { | |
return { | |
flag: true | |
} | |
}, | |
components: { | |
Pra04, | |
Pra05 | |
} | |
} | |
</script> | |
<style></style> |
由于定时器是在 window
下的方法,组件销毁并不会清除定时器
上述代码需要在 data
定义定时器名称,下面代码无需定义
可以使用程序化的事件侦听器通过 $once
来监听定时器,在 beforeDestroy
钩子可以被清除
mounted() { | |
const timer = setInterval(() => { | |
console.log(100); | |
}, 1000); | |
this.$once("hook:beforeDestroy", () => clearInterval(timer)); | |
} |
# mount 挂载
使用 vue2.x 写法
//vue2.x 写法 | |
new Vue({ | |
router, | |
render: (h) => h(App) | |
}).$mount('#app') | |
//vue1.x 写法 | |
new Vue({ | |
el: '#app', | |
router, | |
components: { App } | |
}) |
# 指令
指令都有 v-text
、 v-html
、 v-show
、 v-if
、 v-else
、 v-else-if
、 v-for
、 v-text
、 v-on
、 v-bind
、 v-model
、 v-slot
、 v-pre
、 v-cloak
及 v-once
# 表单输入绑定
你可以用 v-model
指令在表单 <input>
、 <textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
# v-bind 指令
作用在标签的属性上,意味着值就可以写表达式
# methods 属性
类型: { [key: string]: Function }
mthods 一般用于事件的注册,和普通的调用,普通调用需要加上括号,调用一次,执行一次
methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例。
var vm = new Vue({ data: { a: 1 }, methods: { plus: function () { this.a++ } } }) vm.plus() vm.a // 2 |
# filters 属性
在页面输出内容时对原字符进行过滤和格式化
<template>
<div><p>{{ s | addStr }}</p></div>
</template>
<script>
export default {
data() {
return {
s: 'Vue'
}
},
filters: {
addStr(s) {
return `我超爱${s}`
}
}
}
</script>
# computed 属性
类型: { [key: string]: Function | { get: Function, set: Function } }
computed 有依赖缓存,必须返回一个结果,在 DOM
首次加载时会执行一次,如果依赖的数据没有发生改变,会直接使用使用缓存的结果,不会重新计算,只有依赖的数据发生了改变才会重新计算,性能高,一般用于页面的数据计算,声明函数的时候,必须有 return 返回值
# watch 侦听器
无法监听到的场景
- 通过数组索引改变数组元素的值
- 改变数组的长度
- 对象属性的添加或移除
选项:deep
为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
选项:immediate
在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调
对已有的数据进行监听,当 data
中的该数据发生改变,则会调用这个函数
<template>
<div>
<p>{{ num }}<button @click="num++">添加</button></p>
<p>{{ str }}</p>
<p>{{ JSON.stringify(obj) }}<button @click="obj.name = 'https'">添加</button></p>
</div>
</template>
<script>
export default {
data() {
return {
num: 88,
str: 'Vue',
obj: {
name: 'http'
}
}
},
watch: {
num() {
// 当num发生改变就会执行这个参数
},
str: {
handler() {
// 当设置了 immeadite 属性之后,就会在页面加载时候立即调用
},
immediate: true
},
obj: {
// 如果是对象的属性,被修改了,默认监听不上
handler() {
// 当设置了 deep属性,则对象的属性发生了改变就会执行这个函数
},
deep: true
}
}
}
</script>
# 计算属性 vs 侦听属性 vs 方法
计算属性
:当你有其它数据变动需要一些数据也变动时,例如购物车的商品数量和金额小计,需要计算购物车商品数量和金额这些其他数据来影响页面多处的小计金额和数量,简单说是计算属性是被影响其他多个数据被影响,数据计算和字符处理适合计算
查看图片
变量不在 data
中定义,而是定义在 computed
中,写法就是写函数方法,必须有返回值。函数名直接在页面模板中渲染,不加小括号调用
<template> | |
<div> | |
<p><input type="text" v-model="x" /></p> | |
<p><input type="text" v-model="m" /></p> | |
<p><button @click="fun">检查</button>=></p> | |
<p><input v-model="money" /></p> | |
</div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
x: 'x', | |
m: 'm', | |
money: '100' | |
} | |
}, | |
methods: { | |
fun() { | |
console.log(this.x, this.m) | |
} | |
}, | |
computed: { | |
name: function () { | |
console.log('输出了') | |
return this.x + this.m | |
} | |
}, | |
watch: { | |
money: function (s) { | |
console.log(s) | |
if (s > 5000) { | |
console.log('最大额度是5000') | |
this.money = 5000 | |
} | |
} | |
} | |
} | |
</script> |
侦听属性
:当需要在数据变化时执行异步或开销较大的操作时,执行异步操作是计算属性无法做到的,例如购物车的商品对象,需要全选和反选功能,我们可以监听这个对象的变化来影响全选按钮的勾选,简单说是监听的属性改变时影响其他数据,弹框提示等事件交互适合侦听
变量需要在 data
中定义,写法有 3 种,不需要返回值,无需小括号调用
查看图片
methods属性
:可能使用计算属性和方法可能获得同样的效果,但是每次调用 methods
每次调用里面的方法都会执行一次,不像 computed
有缓存,例如购物车中的全选全不选
普通调用需要加小括号调用,事件注册需要传参也可以加小括号
methods: { | |
fun() { | |
this.goodlist.forEach((val) => { | |
if (!this.checked) { | |
val.state = true; | |
} else { | |
val.state = false; | |
} | |
}); | |
}, | |
}, |
# 响应原理
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
<template> | |
<div> | |
<p></p> | |
<p></p> | |
<button @click="setMessage">变化</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
regObj: {}, | |
item: [{ id: 1 }] | |
} | |
}, | |
methods: { | |
setMessage() { | |
//this.regObj.test = "这是一个测试"; | |
this.$set(this.regObj, 'test', '现在可以更新了') | |
// this.item[0] = { id: 4 }; | |
this.$set(this.item[0], 'id', 4) | |
console.log(this.regObj) | |
} | |
} | |
} | |
</script> | |
<style scoped></style> |
# 对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式
var vm = new Vue({ | |
data: { | |
a: 1 | |
} | |
}) | |
// `vm.a` 是响应式的 | |
vm.b = 2 | |
// `vm.b` 是非响应式的 |
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 this.$set(object, propertyName, value)
方法向嵌套对象添加响应式 property
vm.$set(this.someObject, 'b', 2) |
# 数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:vm.item [indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.item.length = newLength
var vm = new Vue({ | |
data: { | |
items: ['a', 'b', 'c'] | |
} | |
}) | |
vm.items[1] = 'x' // 不是响应性的 | |
vm.items.length = 2 // 不是响应性的 |
使用以下方法解决上述问题
vm.$set(vm.items, indexOfItem, newValue) | |
vm.items.splice(newLength) |
# 路由
Vue Router
是 Vue.js 官方的路由管理器
$route
:路由信息对象,主要是获取路由信息
this.$router.push()
:在 history 栈中添加一条新的记录this.$router.go()
:在 history 记录中前进或者后退
$router
:全局路由对象,主要是操作路由
this.$route.path
:字符串,返回当前路由对象的绝对路径,比如 "/home/order"this.$route.params
:对象,包含路由中的动态片段和全匹配片段的键值对this.$route.query
:对象,包含路由中查询参数的键值对this.$route.router
:路由规则所属的路由器this.$route.matched
:数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象this.$route.name
:当前路径的名字,如果没有使用具名路径,则名字为空
# 安装
创建项目安装路由
vue create app | |
npm install vue-router -S |
新建 ../components/One.vue
<template> | |
<div>这是一个组件</div> | |
</template> |
新建 ./src/router/index.js
// 在 Vue 中注册路由 | |
import Vue from 'vue' | |
import VueRouter from 'vue-router' | |
// 注册路由代码 | |
Vue.use(VueRouter) | |
// 指定路由规则 | |
const routes = new VueRouter({ | |
routes: [ | |
{ | |
path: '/', | |
component: () => import('../components/One.vue') | |
} | |
] | |
}) | |
// 导出 | |
export default routes |
入口文件引入挂载
import Vue from 'vue' | |
import App from './App.vue' | |
import router from './router' | |
Vue.config.productionTip = false | |
new Vue({ | |
router, | |
render: (h) => h(App) | |
}).$mount('#app') |
根组件需要路由器占位符
<template> | |
<router-link /> | |
</template> |
测试路由
npm run serve |
创建项目选择手动自定义
# Manually select features | |
vue create app |
选择项如下
查看图片
测试路由
npm run serve |
# 使用
一些简单使用
# 路由跳转
路由跳转相当 a
标签
<router-link to="/">路由跳转</router-link> |
或者传入一个对象
// 字符串 | |
<router-link to="home"> to apple</router-link> | |
// 对象 | |
<router-link :to="{path:'foo'}"> to apple</router-link> | |
// 命名路由 | |
<router-link :to="{ name: 'home' }">home</router-link> | |
//直接路由带查询参数query,地址栏变成 /foo?color=123 | |
<router-link :to="{path: 'foo', query: {id: '123' }}"> to apple</router-link> | |
// 命名路由带查询参数query,地址栏变成/bar?id=123 | |
<router-link :to="{name: 'bar', query: {id: '123' }}"> to apple</router-link> | |
//命名路由path带路由参数params,params 会被忽略 | |
<router-link :to="{path: 'foo', params: { id: '123' }}"> to apple</router-link> | |
// 命名路由name带路由参数params,地址栏是/user/123 | |
<router-link :to="{ name: 'bar', params: { id: 123 }}"> to apple</router-link> |
同样的规则还可以使用下面方法就不一一列举了
差不多类似 location.href = ''
this.$router.push({ path: 'coach', query: this.$route.params.id }) |
query
类似于 Ajax
get 请求,在浏览器地址栏中显示参数params
类似于 Ajax
post 请求,在浏览器地址栏中不显示参数
# 子路由
添加 children
属性配置二级路由
还需要在上级组件中添加占位符
三级路由四级路由同理
const routes = new VueRouter({ | |
routes: [ | |
{ | |
path: '/', | |
component: () => import('../components/One.vue'), | |
children: [ | |
{ | |
path: '/two', | |
component: () => import('../components/Two.vue') | |
} | |
] | |
} | |
] | |
}) |
# 重定向
当用户访问 /a
时,URL 将会被替换成 /b
,然后匹配路由为 /b
添加 redirect
属性
const router = new VueRouter({ | |
routes: [{ path: '/a', redirect: '/b' }] | |
}) |
# 别名
/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样
URL 不变,匹配规则变为别名,添加 alias
属性
const router = new VueRouter({ | |
routes: [{ path: '/a', component: A, alias: '/b' }] | |
}) |
# 命名路由
通过一个名称来标识一个路由
我们可以用于面包屑
const router = new VueRouter({ | |
routes: [ | |
{ | |
path: '/user/:userId', | |
// name: 'user', | |
mate: { name: 'user' }, | |
component: User | |
} | |
] | |
}) |
基操勿六皆坐观之
export default { | |
methods: { | |
getBreadcrumb() { | |
const arr = [{ path: '/', name: '后台首页' }] | |
this.$route.matched.forEach((val) => { | |
if (val.meta.name) { | |
arr.push({ | |
path: val.path, | |
name: val.meta.name | |
}) | |
} | |
}) | |
this.breadArr = arr | |
console.log(arr) | |
} | |
}, | |
watch: { | |
'$route.path'() { | |
this.getBreadcrumb() | |
} | |
}, | |
created() { | |
this.getBreadcrumb() | |
} | |
} |
可能会有重复路由的报错
注册路由后使用下方重写路由
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/order". |
解决办法在路由文件中添加
import Vue from 'vue' | |
import VueRouter from 'vue-router' | |
const originalPush = VueRouter.prototype.push | |
VueRouter.prototype.push = function push(location) { | |
return originalPush.call(this, location).catch((err) => err) | |
} | |
Vue.use(VueRouter) |
# 插槽
当我们向组件中输出另一个组件,我们使用的是单标签
但是我们有时候需要添加其他节点就可以双标签内使用插槽
插槽内可以是任意内容
# 普通插槽
默认插槽没有名字
<son> <span>1234</span> </son> |
子组件
<slot></slot> |
# 具名插槽
具名插槽就是给这个插槽起名字
<son> | |
<template v-slot:demo> <span> 指令 v-slot </span> </template> | |
<template #test> <p>简写</p> </template> | |
</son> |
子组件
<slot name="demo"></slot> <slot name="test"></slot> |
# 作用域插槽
数据在子组件中,在父组件调用子组件内部的自定义内容中使用
<son> | |
<template v-slot="{ num }"> <p></p> </template> | |
</son> |
子组件
<slot :count="num"></slot> | |
<script> | |
export default { | |
data() { | |
return { | |
num: 100 | |
} | |
} | |
} | |
</script> |
# 组件通信
一个组件本质上是一个拥有预定义选项的一个 Vue 实例
我们都知道每一个组件都是独立且复用
我们有时候需要组件之间数据互相使用
# 父向子
父组件输出子组件内容添加自定义属性
<Son :n="num" :list="arr" /> | |
<script> | |
export default { | |
data() { | |
return { | |
num: 100, | |
arr: [1, 2, 3, 4] | |
} | |
} | |
} | |
</script> |
子组件通过 props
属性接受
对象和数组引用类型时需要使用箭头函数返回的方式来定义默认值
<script> | |
export default { | |
props: { | |
n: { | |
type: Nubmer, | |
default: 0 | |
}, | |
list { | |
type: Array, | |
default: () => [] | |
} | |
} | |
} | |
</script> |
# 子向父
<p></p> | |
<Son @fn="fun" /> | |
<script> | |
export default { | |
data() { | |
return { | |
str: '我爱Vue' | |
} | |
}, | |
methods: { | |
fun(s) { | |
//this.str = ' 真是棒' | |
this.str = s | |
} | |
} | |
} | |
</script> |
子组件通过 $emit (event,arg)
触发父组件的自定义函数
<button @click="$emit('fn', 'piu')">触发父组件的自定义函数</button> |
# 兄弟间的组件通信
入口文件
Vue.prototype.$Bus = new Vue() |
父组件
async uploadSave() { | |
const { msg, code } = await avatarEdit({ | |
imgUrl: this.intermediate | |
}) | |
if (code === 0) { | |
this.$Bus.$emit('amountUrl') | |
successMsg(this, msg, 'success') | |
} | |
} |
子组件
created() { | |
this.getData() | |
this.$Bus.$on('amountUrl', () => { | |
this.getData() | |
}) | |
} |
# 单向数据流
Vue 文档
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
数据从父级组件传递给子组件,只能是单向绑定
子组件内部不能直接修改从父级传递过来的数据
这样可以防止从子组件意外改变父级组件的状态