官网介绍它是一个构建用户界面的渐进式框架 ;javascript
渐进式框架 : 主张最少 , 每一个框架都不可避免会有本身的一些特色 , 从而对使用者有必定的要求 , 这些要求就是主张 , 主张有强有弱,它的强势程度会影响在业务开发中的使用方式 ; 而 Vue 虽然有全家桶套餐 , 可是你能够只用它的一部分 , 而不是用了它的 核心库 就必须用它的所有 .css
Vue.js 提供了简洁的模板语法声明式的将数据渲染至 DOM 中html
<div id="app">
{{ message }}
</div>
复制代码
const vm = new Vue({
el: '#app',
data: {
message: 'hello vue'
}
})
复制代码
el
: 元素挂载点;只有在 new 建立实例的时候生效 ; 实例挂载以后可使用 vm.$el
访问data
: Vue 实例的数据对象 , Vue 会递归的将 data 的 property 转换为 getter 和 setter , 从而让 data 的 property 可以响应数据变化 ; 对象必须是纯粹的对象 (含有 0 个或者 多个 键值对) 浏览器 API 建立的对象 , 原型上的 property 会被忽略 , 大概来讲 data 只能存在数据 , 不推荐观察拥有状态行为的对象 ;{{}}
: 插值表达式 ; 官网也称为 Mustache
语法当一个组件被定义时 (非根组件) data
必须声明为一个返回对象的函数 , 由于组件可能被用来建立多个实例, 若是 data 仍然是一个对象 , 这样全部实例讲共享引用同一个数据对象 , 经过提供 data 函数 , 每次建立一个实例的时候 , 咱们可以调用 data 函数 , 从而返回初始数据的一个全新数据对象vue
// 错误 示例
let options = {
data: {
uname: 'zs'
}
}
function Component(options) {
this.data = options.data
}
let user1 = new Component(options)
let user2 = new Component(options)
user1.data.uname = 'ls' // 修改 user1 触发了全部
console.log(user2.data.uname) // ls
复制代码
// 正确示例
let options = {
data() {
return {
uname: 'zs'
}
}
}
function Component(options) {
this.data = options.data()
}
let user1 = new Component(options)
let user2 = new Component(options)
user1.data.uname = 'ls'
console.log(user2.data.uname) // zs
console.log(user1.data.uname) // ls
复制代码
因为组件是能够屡次复用的 , 若是不使用 function return
每一个组件的 data 在内存中都是指向同一个地址的 , 因为 JavaScript 复杂数据类型的特性 , 那一个数据改变其余的也改变了 , 可是若是用了 function return
其实就至关于申明了新变量 , 相互独立 , 天然就不存在以上例子中存在的问题 ; JavaScript 在赋值 Object
时 , 是直接一个相同的内存地址 , 因此为了每一个组件的独立 , 采用了这种方式 ; 但因为根组件只有一个 , 不存在数据污染的状况 , 因此就能够是一个对象 ;java
参考资料 :webpack
v-cloak
这个指令能够配合着
CSS
隐藏未编译的Mustache
标签 , 直到实例准备完毕web
问题展现 :ajax
/* css */
[v-cloak] { display: none; }
复制代码
<div v-cloak>{{ root }}</div>
复制代码
v-text
更新某个元素节点下的值 ; 注意 : 会更新所有内容 , 若是想要局部更新 , 可使用
Mustache
语法算法
<div v-text="root"></div>
复制代码
v-html
更新元素的
innerHTML
注意 : 普通 html 内容vue-cli在网站上使用 HTML 是很是危险的 , 容易致使 XSS 工具 , 用户提交内容时 切记勿要使用
<div v-html="html"></div>
复制代码
new Vue({
el: '#app',
data: {
html: '<p>hello vue</p>'
}
})
复制代码
v-pre
原文输出 , 不会参与编译 , 输入什么内容就展现什么内容
<div v-pre>{{ will not compile }}</div>
复制代码
v-once
被定义了
v-once
指令的元素或者组件 (包括元素或组件内的子孙节点) 只能被渲染一次 , 首次渲染收 , 即时数据发生变化 , 也不会被从新渲染 , 通常用于静态内容展现 ;
<div v-once>{{ content }}</div>
复制代码
const vm = new Vue({
el: '#app',
data: {
content: 'this is init data'
}
})
vm.content = 'update data'
复制代码
v-show
与 v-if
这里的
v-if
不仅仅是这一个指令 , 它包含v-else-if
v-else
功能差很少 , 这里就统一解释了
v-show
: 根据表达式的真假值 , 判断元素是否隐藏 ( 切换元素的 display : block/none )
v-if
: 根据表达式的值来有条件的渲染数据 , 在切换时元素以及它的数据绑定 / 组件被销毁并重建
差别 :
<div v-if="isShow"> v-if </div>
<div v-show="isShow"> v-show </div>
复制代码
分支判断代码演示
<!-- 最终一会展现一个 p 标签中的内容 -->
<div>
<p v-if="score > 90">
<span>成绩优异 : {{ score }}</span>
</p>
<p v-else-if="score > 70">
<span>成绩及格 : {{ score }}</span>
</p>
<p v-else>
<span>不及格 : {{ score }}</span>
</p>
</div>
复制代码
v-for
在
v-for
中被循环的对象 , 必须是一个可迭代对象iterable
( Array | Number | Object | String ... )语法格式为
alias in expression
其中的 in 也可使用 of 替代能够为数组或者对象增长索引值
<!-- 数组循环 -->
<div v-for="(item, index) in items">
{{ item.text }}
</div>
复制代码
<!-- 对象循环 -->
<div v-for="(val, key, index) in object">
{{ val }} {{ key }} {{ index }}
</div>
复制代码
v-for
必须添加惟一 key
当 Vue 正在更新使用 v-for 渲染的数据列表时 , 它默认使用 就地更新 策略 , 若是数据项的顺序被改变 , Vue 将不会移动 DOM 来匹配数据项的数据 , 而是就地更新每一个元素 , 保证它们在每一个索引位置的正确渲染 ;
key
key
会使用一种最大限度减小动态元素而且尽量的尝试就地修改/复用相同类型元素的算法 , 而若是使用了key
它会基于 key
的变化从新排列元素顺序 , 而且会移除 key
不存在的元素index
作 key
// 组件数据定义
const vm = new Vue({
el: '#app',
data: {
users: [
{ id: 1, uname: 'zs', age: 23 },
{ id: 2, uname: 'ls', age: 24 },
{ id: 3, uname: 'we', age: 25 },
{ id: 4, uname: 'mz', age: 26 },
]
}
})
复制代码
index
错误示例
重点在于上面咱们所说的会基于 Key 的变化从新排列元素顺序 , 能够看出若是咱们用 index 做为 key 数组翻转的时候 , 其实 Key 的顺序是没有变的 , 可是传入的值彻底变了 , 这时候本来不同的数据 , 被误觉得同样了 , 因此就形成如下问题 ;
<!-- 具体语法稍后介绍; 意思为点击翻转数组 -->
<button @click="users.reverse()">年龄排序</button>
<ul>
<!-- 循环这个 users 生成一个数据列表 而且里面带有 多选框 以供咱们测试 -->
<li v-for="(user, index) of users" :key="index">
<input type="checkbox" />
<span>{{ user.uname }}</span>
</li>
</ul>
复制代码
惟一 Id 正确示例
此时的 key 和数据作绑定 , 当你翻转数组的时候 , 绑定的实际上是这一条数据 , 而不是索引 , 就不会形成以上问题了
<li v-for="(user, index) of users" :key="user.id">
......
</li>
复制代码
参考资料 :
http://www.javashuo.com/article/p-bztnguuf-bh.html
v-bind
属性绑定 ; 可缩写为
:
// 绑定 attrbute
<div v-bind:content="message"></div>
// 绑定 class
<div :class="{box: isBox}"></div>
<div :class="['box', 'box1']"></div>
<div :class="['box', {box1: isBox}]"></div>
// 绑定 style
<div :style="{fontSize: '20px', color: 'white'}"></div>
<div :style="[{fontSize: '20px'}, {color: 'white'}]"></div>
复制代码
v-on
事件绑定 ; 可缩写
@
监听DOM事件 , 并在触发时运行一些 js 代码
<button v-on:click="count += 1"></button>
{{ count }}
复制代码
能够接收一个方法名称 ;
注意 : 当只是一个方法名称时, 默认第一个参数为事件对象 e
当须要传入参数时 , 那么事件对象就须要手动的传入, 最后一个 而且强制写成
$event
<button @click="handle">点击1</button>
<button @click="handle1('content', $event)">点击2</button>
复制代码
methods: {
handle(e) {
console.log(e.target)
},
handle1(ct, e) {
console.log(ct)
console.log(e.target)
}
}
复制代码
.prevent
: 阻止默认事件.stop
: 阻止冒泡.self
: 只有当前元素触发事件.once
: 只触发一次该事件.native
: 监听组件根元素的原生事件// 定义子组件
Vue.component('my-component', {
template: `
<button @mousedown="handle" :style="{color: 'white', lineHeight: '20px', backgroundColor: 'black'}">组件</button>
`,
methods: {
handle() {
console.log('///')
}
}
})
复制代码
加了 native
至关于把自定义组件当作了 html 能够直接在上面监听原生事件, 不然自定义组件上面绑定的就是自定义事件 , 而你在自定义事件上没有定义这个事件 , 因此不加 native
不会执行
// 父组件中引用
<my-component @click.native="handle('父组件')"></my-component>
复制代码
参考资料 :
https://segmentfault.com/q/1010000011186651
.capture
: 添加事件监听时 , 使用 捕获模式// 此时会优先捕获 box1
<div class="box" style="background: skyblue; width: 180px;" @click.capture="handle('box1')">
<div class="box1 box" style="background: slateblue; width: 140px;" @click="handle('box2')">
<div class="box2 box" style="background: red;" @click="handle('box3')"></div>
</div>
</div>
复制代码
// 容许只有修饰符 prevent 阻止默认事件
<a href="http://www.baidu.com" @click.prevent >百度</a>
// 多个事件修饰符能够连用触发时机也是相同的
<a href="http://www.baidu.com" @click.prevent.stop="handle('a')">baidu</a>
复制代码
Vue 中容许为
v-on
监听键盘事件时添加键盘修饰符<input v-on:keyup.enter="submit"> 复制代码
固然提供了大多数的按键码别名 按键码
还能够经过全局
Vue.config.keyCodes
自定义修饰符别名Vue.config.keyCodes.f1 = 112 复制代码
v-model
在表单元素上建立数据双向绑定 , 它会根据控件类型自动选取正确的值来更新元素
// 文本
<input type="text" v-model="message">
<p>{{ message }}</p>
复制代码
// 多行文本
<textarea cols="30" rows="10" v-model="message"></textarea>
<p>{{ message }}</p>
复制代码
// 单选框
<input type="radio" value="男" v-model="sex">男
<input type="radio" value="女" v-model="sex">女
<p>{{ sex }}</p>
复制代码
// 单个复选框
<input type="checkbox" v-model="checked">
<p>{{ checked }}</p>
复制代码
// 多个复选框
<input type="checkbox" value="打篮球" v-model="hobby"/>打篮球
<input type="checkbox" value="打皮球" v-model="hobby"/>打皮球
<input type="checkbox" value="打气球" v-model="hobby"/>打气球
<input type="checkbox" value="打棒球" v-model="hobby"/>打棒球
<p>{{ hobby }}</p>
复制代码
// 选择框 -> 单选
<select v-model="selected">
<option>javascript</option>
<option>html</option>
<option>css</option>
</select>
<p>{{ selected }}</p>
复制代码
// 选择框 -> 多选
<select v-model="selectList" multiple>
<option>javascript</option>
<option>html</option>
<option>css</option>
</select>
<p>{{ selectList }}</p>
复制代码
// 实例对象
new Vue({
el: '#app',
data: {
message: '', // 多行, 单行文本
sex: '', // 单选框
checked: false, // 复选框单个
hobby: [], // 复选框多个
selected: '', // 选择框 -> 单个
selectList: [] // 选择框 -> 多个
}
})
复制代码
.lazy
: 默认状况下 v-model
在每次的 input 事件触发后将输入框内容进行同步 , 添加 lazy
修饰符后 , 会变成 change 事件后同步数据<input v-model.lazy="message">
复制代码
.number
: 用户输入的值转为数值类型<input v-model.number="age">
复制代码
.trim
: 过滤输入框中的左右空白<input v-model.trim="message">
复制代码
Vue.set
若是在实例建立以后添加新的属性到实例上 , 它不会触发更新视图 怎么理解呢 ?
data() {
return {
info: {
uname: 'zs'
}
}
}
mounted() {
// 此时是不会生效的 , 若是再模块化的开发中还会报错
this.info.age = 23
}
复制代码
受 ES5 的限制 , Vue 不能检测到对象属性的添加或者删除 , 由于 Vue 在初始化的时候将属性转换为getter
setter
因此属性必需要在 data 对象上才能让 Vue 转换 , 只有在 data 对象上 才是响应式的
mounted() {
// 正确写法
this.$set(this.info, 'age', 23)
}
复制代码
Vue.set() : 与 this.$set
没有区别, 一个全局 一个局部 官网说 this.$set
是 Vue.set
的一个别名
methods
methods 将会被混入到Vue 实例中 , 能够直接经过 vm 实例访问这些方法 , 或者在指令表达式中使用 , 方法中的
this
自动绑定 Vue 实例注意 : methods 中的 方法 不要使用 箭头函数 , 箭头函数中的
this
指向父级做用域的上下文 , 因此this
将不会指向 Vue 实例
new Vue({
methods: {
handle() {
console.log(this)
}
}
})
复制代码
computed
模板内写表达式当然是很方便的 , 可是你应该明白 , 表达式的初衷是用来计算的 , 好比处理一些字符串 , 时间格式等等 , 若是咱们写成方法吧 ! 每次都要去调用 , 那就太麻烦了 , 为此 Vue 提供了 计算属性 computed
能够看出每次咱们都去调用这个参数 , 感受很不方便
<input type="text" v-model.number="input1"/> +
<input type="text" v-model.number="input2"/> =
<span>{{ getSum() }}</span>
复制代码
data() {
return {
sum: '',
input1: '',
input2: ''
}
},
methods: {
getSum() {
return this.sum = this.input1 + this.input2
}
}
复制代码
下面咱们使用计算属性解决 ; 能够看出咱们去除了 data 中的 sum 属性 在 computed 中新增了 getSum 函数
<input type="text" v-model.number="input1"/> +
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
复制代码
data() {
return {
input1: '',
input2: ''
}
},
computed: {
getSum() {
return this.input1 + this.input2
}
},
复制代码
那么问题来了 为何定义了一个函数, 却当成属性执行 ? 其实这个只是简写而已 , 算是一个语法糖 , 每个计算属性包含 get 和 set 当只有 get 时能够简写为 函数的格式
export default {
computed: {
getSum: {
get() {
// 获取数据
},
set(val) {
// val 是这个计算属性被修改以后的数据 设置数据
}
}
}
}
复制代码
示例 : 看完这个例子就明白了为何叫 计算属性了吧
<input type="text" v-model.number="input1"/> +
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
<!-- set 函数能够接收这里传递过来的值 -->
<button @click="getSum = '未知数'">修改 getSum</button>
复制代码
computed: {
getSum: {
get() {
return this.input1 + this.input2
},
// 接收 getSum 这个属性改变后的值
set(val) {
console.log(val)
this.input1 = 20
this.input2 = 30
}
}
},
复制代码
watch
虽然计算属性在大多数状况下都适用 , 但有时也须要一个自定义的侦听器 , 这个时候就须要 侦听属性 watch
仍然是计算两数之和 ; 在 watch 监听了 input1 的属性 input1 触发时 求出 sum ; 仔细看已经出现了问题, 修改 input2 的时候就不会再触发了 ;
总结 : 它监听 data 某一个属性的变化 , 并不会创造新的属性
<input type="text" v-model.number="input1"/> +
<input type="text" v-model.number="input2"/> =
<span>{{ getSum }}</span>
复制代码
data() {
return {
input1: '',
input2: '',
getSum: ''
}
},
watch: {
// 这样写看着是一个函数, 和属性理解不一致, 固然还能够写成这样
input1(newVal, oldVal) {
console.log(newVal, oldVal)
this.getSum = this.input1 + this.input2
}
input2: {
// 回调函数监听 input2 的变化 函数名必须是 handler
handler(newVal, oldVal) {
console.log(newVal, oldVal)
this.getSum = this.input1 + this.input2
}
}
}
复制代码
若是咱们须要侦听对象属性, 能够在选项参数中使用 deep: true
注意监听数据的变动不须要这么作
watch: {
obj: {
handler() {
// ....
},
deep: true
}
}
复制代码
watch 使用时有一个特色 , 就是当值第一次绑定的时候 , 不会执行监听函数 , 只有值发生改变时才会执行 , 若是咱们须要在最初绑定值的时候也执行函数 , 则须要用到 immediate: true
watch: {
apiData: {
handler(newVal, oldVal) { },
deep: true,
immediate: true
}
}
复制代码
watch
就是单纯的监听某个数据的变化 , 支持深度监听 , 接收两个参数一个最新值, 一个变化前的旧值 , 结果不会被缓存 , 而且 watch 能够处理异步任务
computed
是计算属性, 依赖于某个或者某些属性值 , 计算出来的结果会出现缓存 , 只有当数据的依赖项变化时才会发生变化 , 会建立一个新的属性
methods
是函数调用 , 没有缓存 , 主要处理一些业务逻辑, 而不是监听或者计算一些属性
filter
能够被用于一些常见的文本格式化 , 容许被应用在两个地方
{{}}
v-bind
中
{{ msg | formatMsg }}
<div v-bind:msg="msg | formatMsg"></div>
复制代码
Vue.component('son-component', {
template: `
<div>{{ msg | formatMsg }}</div>
`,
data() {
return {
msg: 'this is message'
}
},
filters: {
formatMsg(msg) {
return msg.toString().toUpperCase()
}
}
})
复制代码
Vue.filter('formatMsg', function(msg) {
return msg.toString().toUpperCase()
})
复制代码
|
前面的的内容做为过滤器的第一个参数 , 还能够再次传入传输<div>{{ msg | formatMsg('lower') }}</div>
复制代码
Vue.filter('formatMsg', function(msg, args) {
console.log(msg) // lower
if (args === 'lower') {
return msg.toString().toLowerCase()
}
})
复制代码
directive
与上面提到的指令一致 , 若是那些指令不能知足使用要求 , 能够本身进行定制
自定获取焦点案例
<input type="text" v-focus/>
复制代码
// 全局指令 定义时不须要 v- 调用时要加上 v- 前缀
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
// 或者能够定义为局部
directives: {
'focus': {
inserted(el) {
el.focus()
}
}
}
复制代码
bind
: 只调用一次 , 指令第一次绑定元素时调用 , 在这里能够进行一次性的初始化设置 ;inserted
: 被绑定元素插入父节点时调用 , 不必定渲染完成 , html 已经建立好了update
: 所在组件的 VNode 更新时调用componentUpdated
: 指令所在的组件的 VNode 所有更新完成后unbind
: 指令与元素解绑时调用el
: 指令所绑定的元素 , 能够直接操做 DOMbinding
: 指令相关的配置对象
modifiers
: 一个包含修饰符的对象 示例 v-drag.limit
name
: 指令名 , 不包含前缀value
: 指令绑定的值 v-drag="true"
<div v-drag>
复制代码
// 拖拽方块案例
Vue.directive('drag', {
// 初始化样式
bind(el) {
el.style.position = 'absolute'
el.style.top = 0
el.style.left = 0
el.style.width = '100px'
el.style.height = '100px'
el.style.background = 'skyblue'
el.style.cursor = 'pointer'
},
// 元素对象存在后, 开始写拖动逻辑
inserted(el, binding) {
let draging = false
let elLeft = 0
let elRight = 0
document.addEventListener('mousedown', function (e) {
draging = true
let move = el.getBoundingClientRect()
elLeft = e.clientX - move.left
elRight = e.clientY - move.top
})
document.addEventListener('mousemove', function (e) {
let moveX = e.clientX - elLeft
let moveY = e.clientY - elRight
if (draging) {
el.style.left = moveX + 'px'
el.style.top = moveY + 'px'
}
})
document.addEventListener('mouseup', function () {
draging = false
})
}
})
复制代码
相信你们仔细看上面的代码可能会发现这个方格拖拽还存在一些问题 ; 它仍是能够拖拽到可视区域以外的 , 那么可不能够传递一个修饰符 , 来告诉他呢 ? 这时候就须要用到 binding 这个指令配置相关的对象了
// 咱们先传入修饰符 limit 为本身定义的修饰符
<div v-drag.limit>
复制代码
// 既然不想让他拖拽出视口, 那么就应该在鼠标移动的时候加入一些逻辑
document.addEventListener('mousemove', function (e) {
let moveX = e.clientX - elLeft
let moveY = e.clientY - elRight
// 是否传入了修饰符 limit 为何这样能够获取 下面就上截图
if (binding.modifiers.limit) {
moveX = moveX <= 0 ? moveX = 0 : moveX
moveY = moveY <= 0 ? moveY = 0 : moveY
}
if (draging) {
el.style.left = moveX + 'px'
el.style.top = moveY + 'px'
}
console.log(binding) // binding 对象
})
复制代码
上面咱们已经解决了拖出视口的问题 , 只要传递一个修饰符就解决了 , 那么如今咱们但愿能够手动的暂停拖拽 , 固然也是可行的 ;
<div v-drag.limit="{isDrag: false}">
复制代码
document.addEventListener('mousemove', function (e) {
let moveX = e.clientX - elLeft
let moveY = e.clientY - elRight
// 是否传入了修饰符 limit
if (binding.modifiers.limit) {
moveX = moveX <= 0 ? moveX = 0 : moveX
moveY = moveY <= 0 ? moveY = 0 : moveY
}
// 是否传入 isDrag 判断是否可滑动
if (!binding.value.isDrag) return
if (draging) {
el.style.left = moveX + 'px'
el.style.top = moveY + 'px'
}
})
复制代码
一般一个组件会以一棵嵌套的组件数的形式来组织 ; 为了能在模板中使用 , 这些组件必须先注册以便 vue 可以识别 ;
Vue.component('GlobalComponent', {
template: `<div> hello component </div>`
})
复制代码
// 命名时推荐驼峰 , 调用时推荐 - 连接, html 不识别大小写
<global-component></global-component>
复制代码
new Vue({
el: '#app',
components: {
SonComponent: {
template: `<div>hello private component</div>`
}
}
})
复制代码
// 组件能够被复用屡次
<private-component></private-component>
<private-component></private-component>
<private-component></private-component>
复制代码
import SonComponent from '@/components/SonComponent.vue'
export default {
components: {
SonComponent
}
}
复制代码
<son-component></son-component>
复制代码
props
向子组件传递数据prop 是组件上一些自定义的 attribute , 当一个值传递给一个 prop attribute 的时候 , 它就变成那个组件实例的 property ;
// 父组件
<div>
<son-component content="传递给子组件的数据, 若是动态传值能够加 v-bind"></son-component>
</div>
复制代码
// 子组件
Vue.component('SonComponent', {
// 多个单词能够是驼峰式, 可是父组件传递时多个单词必须是 - 链接
// props 中的值, 能够像 data 中的数据同样访问 this.content / {{ content }}
// props 是只读的 切记不要修改 会报错
props: ['content'],
template: `<div> {{ content }} </div>`
})
复制代码
props 能够是数组也能够是一个对象 , 用来接收来自父组件的数据 ;
对象容许配置高级选项 , 如类型检测等
type
: 能够是 Number
String
Boolean
Array
Object
Date
Function
任何自定义构造函数 , 或上述内容组成的数组 , 会检查一个 prop 是不是给定的类型 , 不然抛出异常default
: 默认值 , 对象或者数组的默认值必须从一个工厂函数中返回required
: boolean 是否为必填项validator
: Function 自定义验证函数会将 prop 的值做为惟一的参数传入props: {
content: {
type: String,
// default: 0, 普通值可直接默认返还
default: () => [1, 2, 3],
required: true,
// 若是传进来的 content 长度大于 20 就会报错
validator: (value) => value.length >= 20
}
}
复制代码
$emit
有些时候 , 父组件须要用的子组件中特定的值时 , 可使用
$emit
把这个值传递出去
// 子组件
<template>
<div class="son">
// $emit 第一个参数自定义事件, 第二个及之后是传递的数据
<button @click="$emit(son-com, [1, 2, 3])"></button>
</div>
</template>
复制代码
// 父组件
// 监听子组件定义的自定义事件 , 经过 $event 访问第一个传递的参数
<son-component @son-com="msg = $event"></son-component>
复制代码
// 子组件
<template>
<div class="son">
<button @click="sonHandle"></button>
</div>
</template>
<script>
export default {
methods: {
sonHandle() {
this.$emit('son-com', '须要传递的值')
}
}
}
</script>
复制代码
// 父组件
<template>
<div class="parent">
<son-component @son-com="parentHandle"></son-component>
// 显示传入其余参数的话 必须使用 $event 接收子组件传递过来的值
<son-component @son-com="parent('显示传入参数', $event)"></son-component>
</div>
</template>
<script>
export default {
methods: {
// 默认第一个值就是传递过来的参数
parentHandle(arg) {
console.log(arg) // 须要传递的值
},
// 对应传入参数的位置
parent(params, arg) {
console.log(params) // 显示传入参数
console.log(arg) // 须要传递的值
}
}
}
</script>
复制代码
// 子组件
<template>
<div class="son">
<button @click="sonHandle"></button>
</div>
</template>
<script>
export default {
methods: {
sonHandle(event) {
// 事件对象能够在任意位置 , 放到前面相对比较好接收
this.$emit('son-com', event, '须要传递的值', '须要传递的第二个值')
}
}
}
</script>
复制代码
// 父组件
<template>
<div class="son">
<button @son-com="parent"></button>
</div>
</template>
<script>
export default {
methods: {
parent(event, ...args) {
console.log(event)
// 若是不想使用剩余参数, 也能够多传递参数逐个使用
console.log(args)
}
}
}
</script>
复制代码
v-model
在使用这个功能以前咱们须要先了解一个东西 , v-model
到底是什么 ; 其实它从某种程度来讲就是一个语法糖
<input type="text" v-model="msg"/>
复制代码
等价于
<input :value="msg" @input="msg = $event.target.value"/>
复制代码
应用到组件中就是下面这样 为了避免引发歧义, 我把自定义的事件以及属性加了test 前缀详情看官网
// 父组件
<model-input
:test-value="searchText"
@test-input="searchText = $event"
>
</model-input>
复制代码
$emit
将自定义的 test-input 在暴露出去// 子组件
<input
type="text"
v-bind:value="testValue"
@input="$emit('test-input', $event.target.value)"
>
// script
props: ['testValue'] // 自定义属性传递过来的值
复制代码
此时咱们再优化一下 , 使用 v-model
// 父组件
<model-input v-model="searchText"></model-input>
复制代码
因为咱们组件中使用了 v-model 而前面咱们也提到了 v-model 实际上是 v-bind 和 v-on 的语法糖 , 因此只能用 value 属性和 input 事件
// 子组件
<input
type="text"
v-bind:value="value"
@input="$emit('input', $event.target.value)"
>
// script
props: ['value']
复制代码
那么问题来了 , 上面咱们提过 v-model 默认是 value属性 和 input事件 , 可是像单选框 , 复选框等类型怎么处理 ? 对此 Vue 提供了
model
选项来避免这样的冲突
// 父组件
<model-input v-model="isChecked"></model-input>
// script
data() {
return {
isChecked: false
}
},
复制代码
选中 和 未选中 返回 true / false
// 子组件
<input
type="checkbox"
v-bind:checked="checked"
@change="$emit('change', $event.target.checked)"
>
// script
export default {
name: 'ModelInput',
// v-model 拆分
model: {
prop: 'checked', // 将传进来的 isChecked 变成 checked 供后面的 props 使用
event: 'change' // 定义 emit 自定义的事件名字
},
props: {
checked: {
type: Boolean
}
}
}
复制代码
在 2.6.0 中 为具名插槽和做用域插槽提供了新的统一语法
v-slot
它取代了slot
和slot-scope
这两个目前已被废弃 , 可是尚未移除 (仍然可使用)插槽 : 简单理解就是 占坑 在组件模板中占好位置 , 当使用该组件的标签时 , 组件标签的内容就会自动填坑 , ( 替换组件模板中的
slot
位置 ) , 而且能够做为承载分发内容的出口
// 子组件
<template>
<div>
<p>这是组件的头部</p>
<slot></slot>
<p>这是组件的尾部</p>
</div>
</template>
复制代码
// 父组件
<<template>
<div>
<!-- 插槽内能够是任何内容 组件,文本,标签-->
<slot-test>
<p>这是插槽的内容</p>
</slot-test>
</div>
</template>
复制代码
规则 : 父级模板里的全部内容都是在父级做用域中编译的 ; 子模板的全部内容都在子做用域中编译
<slot>
标签内能够加入 组件, 文本, 标签等默认内容 , 若是父组件调用时, 没有传入内容, 那么就会展现默认的内容
// 子组件
<template>
<div>
<p>这是组件的头部</p>
<slot>我是默认内容</slot>
<p>这是组件的尾部</p>
</div>
</template>
复制代码
有些时候一个插槽是不能知足需求的 , 咱们可能须要多个 ; 对于这种状况 ,
<slot>
元素中有一个特殊的 attributename
这个 attribute 用来定义额外的插槽
// 子组件
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<!-- 若是没有指定 name 默认的 name 为default -->
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
复制代码
在向具名插槽提供内容的时候 , 能够在一个
template
元素中上 使用v-slot
指令 并以参数的形式提供名称
// 父组件
<template>
<div>
<slot-test>
<template v-slot:header> 我是 header 内容 </template>
我是 没有指定 name 的内容 <!-- 或者也能够写成下面的内容 -->
<template v-slot:default> 没有指定 name 的内容 </template>
<template v-slot:footer> 我是 footer 内容 </template>
</slot-test>
</div>
</template>
复制代码
<template>
中的全部内容都会传入响应的插槽 , 任何没有包裹在带有v-slot
的<template>
中的内容都会被视为默认插槽的内容 ; 通俗一点来讲 , 就是我在子组件中声明了多个 没有name
的<slot>
, 那么 我在父组件中 , 只须要渲染一次 , 全部子组件的插槽就都会被渲染 ;注意 :
v-slot
只能添加在<template>
上 , 还有一种特殊状况后面会说
v-slot:
替换为#
注意 : 只有带参数时可使用 , 其余状况下是无效的
// 子组件
<div>
<slot></slot>
</div>
复制代码
/* 父组件 */
// 错误示例
<slot-test>
<template #>
<!-- 内容。。。。-->
</template>
</slot-test>
// 正确示例
<slot-test>
<template #default>
<!-- 内容。。。。-->
</template>
</slot-test>
复制代码
从某种意义上来讲 , 插槽是子组件高可用 , 高定制化的一种手段 , 那么咱们确定会碰到插槽内容 , 须要访问子组件中数据的状况 ;
咱们能够经过 v-bind
把须要传递的值 绑定到 <slot>
上 , 而后在 父组件中 使用 v-slot
设置一个值来定义提供插槽的名字
// 子组件
<template>
<div>
<slot name="userInfo" :user="userInfo"></slot>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: {
firstName: 'firstName',
lastName: 'lastName'
}
}
}
}
</script>
复制代码
// 父组件
<template>
<div>
<slot-test>
<!-- 若是只有一个插槽能够吧 v-slot 写到 组件上面 具体看下面-->
<template v-slot:userInfo="slotProps">
{{ slotProps.user.firstName }}
</template>
</slot-test>
</div>
</template>
复制代码
当被提供的内容只有默认插槽时 , 组件的标签才能够当作插槽的模板使用 , 这样咱们就能够把
v-slot
直接用在组件上
// 子组件
<div>
<slot :user="userInfo"></slot>
</div>
// 组件数据
data(){
return {
userInfo: {
firstName: 'firstName',
lastName: 'lastName'
}
}
}
复制代码
// 父组件
<div>
<slot-test v-slot="slotProps">
{{ slotProps.user.firstName }}
</slot-test>
</div>
复制代码
<template>
语法Prop
插槽支持经过 ES6 结构传入具体的插槽 prop 至于原理 ;
官网说 : 做用域插槽内部原理是将你的插槽内容包括在一个传入单个参数的函数里 ; 没有听懂 😂 自身档次还不够, 仍是看看怎么用的吧
<div>
<slot-test v-slot="{ user }">
{{ user.firstName }}
</slot-test>
</div>
复制代码
在提供多个 prop 的时候 ,它一样开启了 prop 重命名的功能
<div>
<slot-test v-slot="{ user: preson }">
{{ preson.firstName }}
</slot-test>
</div>
复制代码
插槽 prop 容许咱们将插槽转换为可复用的模板 , 这些模板能够基于输入不一样的 prop 渲染出不一样的内容;
好比咱们设计一个 <todo-list>
组件 ,它是一个列表且包含必定的逻辑
// 子组件
<template>
<ul>
<li v-for="item in list" :key="item.id">
<!-- 这里把控制逻辑交出去,由父组件去控制必定的逻辑 -->
<slot name="todo" :item="item">
{{ item.uname }}
</slot>
</li>
</ul>
</template>
// 数据
[
{id: 1, uname: 'zs'},
{id: 2, uname: 'ls'},
{id: 3, uname: 'we'},
{id: 4, uname: 'mz'},
]
复制代码
// 父组件
<test v-slot:todo="slotProps">
<!-- 原本是显示名字的改为了显示id -->
{{ slotProps.item.id }}
</test>
复制代码
component
在说动态组件以前, 先动手实现一个 tab 分页,否则貌似不是很好理解 😂
// 模板
<div>
<button @click="handle('post')" :style="{background: !isShow ? 'red' : ''}">post</button>
<button @click="handle('list')" :style="{background: isShow ? 'red' : ''}">list</button>
<item-post v-show="!isShow"></item-post>
<item-list v-show="isShow"></item-list>
</div>
// script
import ItemPost from '@/components/ItemPost.vue'
import ItemList from '@/components/ItemList.vue'
export default {
name: 'Home',
components: {
ItemPost,
ItemList
},
data() {
return {
isShow: true
}
},
methods: {
handle(title) {
title === 'list' ? this.isShow = true : this.isShow = false
}
}
}
复制代码
能够看出上面咱们写了两个小组件,作了一个简易的 tab 选项卡 ;
重点能够看咱们引入组件的方式;就是简单的在模板中应用;常规操作;如今只引入两个标签还好,那若是三五十来个就显得有点麻烦了
有了动态组件以后, 相似这种简易的 tab 切换咱们就不须要再模板中使用这么多的组件标签了
看代码示例, 咱们只须要 template 中引入组件的部分 以及在 script 中的 data 新增一个属性
<!-- 每次点击 button 的时候触发 handle事件函数时 is绑定的值会从新渲染组件 -->
<component :is="showWhat"></component>
复制代码
data() {
return {
showWhat: 'ItemPost',
isShow: true
}
},
methods: {
handle(title) {
// 这里同时控制了切换以后 active 的样式,因此就不删了
title === 'list' ? this.isShow = true : this.isShow = false
// 加一层判断切换逻辑
title === 'list' ? this.showWhat = 'ItemList' : this.showWhat = 'ItemPost'
}
}
复制代码
这里就出现了一些问题, 虽然咱们完成了 组件之间的切换,可是关于 input 的选中状态咱们没有保留下来,缘由是每次切换的时候,Vue 都会建立一个新的
showWhat
实例 从新建立动态组件的行为一般来讲仍是很是有用的,可是在咱们这个案例中,更但愿它可以保留下来 为了解决这个问题,Vue 中提供了一个内置元素<keep-alive>
只须要将该元素把组件包裹起来便可
<!-- 失活的 tab 将会被缓存 -->
<keep-alive>
<component :is="showWhat"></component>
</keep-alive>
复制代码
在大型应用中, 咱们可能须要将应用中分割成小一些的代码块,而且只在须要的时候,再次加载这个模块;道理和 webpack 的按需加载是同样的;
这里先建立一个简单的组件
<template>
<div>这是一个简单的组件</div>
</template>
复制代码
而后在主组件内常规引入看下效果
<template>
<div>
<dynamic></dynamic>
</div>
</template>
<script>
import Dynamic from '@/component/Dynamic.vue'
export default {
name: 'Home',
components: {
Dynamic
}
}
</script>
复制代码
看下图, 上面的书写方式,加载出来的组件,会被所有渲染到一个文件里面,若是咱们的这个页面组件不少,或者说有些组件,只有触发了特定操做后才会显示,咱们是否是就没有必要在页面渲染时,就把全部的组件所有加载呢 ?
根据上面的例子,咱们修改一下 script 部分
export default {
components: {
// script 头部的导入, 直接在组件内导入
Dynamic: () => import Dynamic from '@/component/Dynamic.vue'
}
}
复制代码
能够看出下图多出了 0.js
因而可知 vue-cli 帮助咱们分开打包了文件;这个文件在被加载以后,就会被缓存起来;这个例子只是看出了它被拆开了,并不能证实我们开始说的按需渲染的道理;基于这个例子能够再改一下,加一个判断逻辑
<template>
<div>
// 就是增长一个按钮,点击控制,这里就给你们简化了
<dynamic v-if="isShow"></dynamic>
</div>
</template>
复制代码
下图能够看出没有显示子组件时, 把这个文件给缓存起来了,只有真正调用到的时候才会加载对应的资源;
固然这只是其中一个功能,好比网络很差的时候,组件加载的用户体验会受到损害,超时的处理等等;具体配置参考 vue 官网 : 处理加载状态
ref
尽管存在 prop 和事件 , 有的时候你扔可能须要再 JavaScript 中直接访问一个子组件 , 为了达到这个目的你可使用 ref
这个属性为子组件赋予一个 id 引用
// 模板
<template>
<div>
<item-list ref="itemRef"></item-list>
<input type="text" ref="inputRef"/>
</div>
</template>
// script
export default {
mounted() {
// 子组件的实例对象
console.log(this.$refs.itemRef)
// 元素的实例对象, 能够操做 dom
console.log(this.$refs.inputRef)
}
}
复制代码
若是 ref 和 v-for 一块儿使用的话 , 那么 this.refs
将会获得一个元素数组
<ul>
<li v-for="user of userList" :key="user.id" :style="{listStyle: 'none'}" ref="forRef">
<input type="checkbox" />
{{ user.id }} --- {{ user.uname }} --- {{ user.age }}
</li>
</ul>
mounted() {
console.log(this.$refs.forRef)
}
复制代码
$refs
只会在组件渲染完成后生效 , 而且它不是响应式的 , 这仅做为用于直接操做子组件的 逃生舱 应该尽可能避免在模板或者计算属性中访问$refs
每一个 Vue 实例建立时都须要通过一系列的初始化 ; 开始建立 , 初始化数据 , 编译模板, 挂载DOM , 更新 , 渲染 , 卸载等一系列过程 , 成为 生命周期 , 同时在这些过程当中也会运行一些叫作生命周期钩子的函数
beforeCreate
: 建立前 实例初始化以后,this指向建立的实例,不能访问到data
、computed
、watch
、methods
上的方法和数据
created
: 建立后 实例建立完成 , 能够访问 data
computed
watch
methods
数据 , 没有渲染进 浏览器 没法访问 DOM ; 注意 : 这个生命周期内发送 ajax 请求 是没有什么方法对实例化过程进行拦截的 , 所以加入某些数据必须获取以后才能进入这个页面的话 , 并不适合在这个方法内完成 , 建议使用 beforeRouterEnter
路由钩子中完成
beforeMount
: 挂载前 挂载开始以前调用 , beforeMount
以前会找到对应的 template 编译成 render 函数
mounted
: 挂载后 实例挂载到 DOM , 能够操做DOM $ref
可使用 ; 此时能够作一些 ajax 操做 , mounted 只会执行一次
beforeUpdate
: 更新前 响应式数据更新时调用 , DOM 从新渲染和打补丁以前 , 能够在这里进一步更改状态 , 不会进行二次渲染
updated
: 更新后 虚拟DOM从新渲染和打补丁以后调用 , 组件已经更新 , 能够执行后续操做 ; 避免在这里操做数据 , 可能会陷入死循环
activated
: 被 keep-alive 缓存的组件激活时调用
deacticated
: 被 keep-alive 缓存的组件停用时调用
// 子组件
// 要在子组件的声命周期内 , 才会触发这两个钩子函数
activated() {
console.log('itemList 被激活')
},
deactivated() {
console.log('itemList 被停用 / 失活')
},
复制代码
// 父组件
// toggle 切换时触发子组件中的这两个钩子函数
<button @click="com = 'ItemPost'">post</button>
<button @click="com = 'ItemList'">list</button>
<keep-alive>
<component :is="com"></component>
</keep-alive>
复制代码
beforeDestroy
: 销毁前 实例销毁以前调用 , 这里各类数据仍然能够访问 ; 能够再次销毁定时器, 解绑事件等操做destroyed
: 销毁后 实例销毁后调用 , Vue 实例的全部内容都会解除绑定 , 全部事件事件监听器会被移除 , 全部子实例也会被销毁<template>
<div>
<p>this is $el</p>
<button @click="info = '修改后的值'">{{ info }}</button>
<button @click="handle">卸载</button>
</div>
</template>
<script>
export default {
name: 'HomePage',
data() {
return {
info: 'data options',
flag: true
}
},
methods: {
handle() {
this.$destroy()
}
},
beforeCreate() {
console.group('##### 组件建立前 beforeCreate #####')
console.log('Vue', this)
console.log(this.info)
console.log(this.$el)
},
created() {
console.group('##### 组件建立后 created #####')
console.log(this.info)
console.log(this.$el)
},
beforeMount() {
console.group('##### 组件挂载前 beforeMount #####')
console.log(this.info)
console.log(this.$el)
},
mounted() {
console.group('##### 组件挂载后 mounted #####')
console.log(this.$el)
},
beforeUpdate() {
console.group('##### 组件更新前 beforeUpdate #####')
console.log(`这里的数据已经修改了只是没有渲染 ----- `+ this.info)
this.info = '又修改了一次'
},
updated() {
console.group('##### 组件更新后 updated #####')
console.log('更新后的新值: ', this.info)
},
beforeDestroy() {
console.group('##### 组件卸载前 updated #####')
console.log(this.info)
console.log(this.$el)
},
destroyed() {
console.group('##### 组件卸载后 updated #####')
console.log(this.info)
console.log(this.$el)
}
}
</script>
复制代码
beforeMount
钩子以后就会去加载 子组件 , 只组件加载完成后才会触发父组件的 Mounted
Vue 在插入,更新或者移出 DOM 时,提供多种不一样的过渡效果
在下列情形中能够给任何元素添加进入/离开过渡
- 条件渲染 (
v-if v-show
)- 动态组件 (
component
)- 组件根节点
过渡的类名:在进入/离开的过渡中,会有6个 class 切换
v-enter
: 定义过渡的开始 , 在元素被插入以前生效 , 在元素被插入以后的下一帧移除v-enter-active
: 定义进入过渡生效时的状态 , 在整个进入过渡的阶段中都会应用 . 在元素被插入以前生效 . 咋过渡/动画结束以后移除 , 这个类能够被定义进入过渡的过程时间 , 延迟 和曲线函数v-enter-to
: 定义进入过渡的结束状态 , 在元素被插入后的下一帧生效 ( 同时 v-enter 移除 ), 在过渡动画完成以后移除v-leave
: 定义离开过渡的开始时间 , 在离开过渡被触发时马上生效 , 下一帧被移除v-leave-active
: 定义离开过渡生效时的状态 , 在整个离开过渡的阶段中应用 , 在离开过渡被触发时 当即生效 , 在过渡动画完成以后移除 , 这个类能够被定义离开过渡的过程时间 , 延迟和曲线函数v-leave-to
: 定义离开过渡的结束状态 , 在离开过渡被触发后下一帧生效 (同时 v-leave 移除), 在过渡东环完成以后移除<transition>
<dynamic v-if="isShow"></dynamic>
</transition>
复制代码
若是 <transition>
没有 name 属性 类名默认是 v-
开头 , 若是使用了自定义名字 , 替换成自定名字开头 ; transition 不会被渲染成真是的 DOM 元素
示例 : <transition name="my-trans">
my-trans-enter
<style lang="css">
.v-enter, .v-leave-to{
opacity: 0;
transition: all .5s ;
}
.v-enter-to, .v-leave{
opacity: 1;
transition: all .5s ;
}
</style>
复制代码
<transition>
<keep-alive>
<component :is="showWhat"></component>
</keep-alive>
</transition>
复制代码
<style lang="css">
.v-enter, .v-leave-to{
opacity: 0;
}
.v-enter-active, .v-leave-active{
transition: opacity .3s ease;
}
</style>
复制代码
在切换 tab 的时候 内容被重绘了 , 一个是离开过渡的时候, 另外一个是进入过渡 , 这是
<transition>
的默认行为 , 进入和离开同时发生 ; 过渡模式仅适用于组件之间同时生效的进入和离开的过渡不能知足全部要求,因此 Vue 提供了过渡模式
in-out
: 新元素先进行过渡 , 完成以后当前元素过渡离开out-in
: 当前元素先进行过渡 , 完成以后新元素过渡进入能够看出上图的动画效果为当前元素尚未完成 , 新元素就进来了 , 此时咱们可使用 out-in
模式便可解决上述问题
<transition mode="out-in">
<keep-alive>
<component :is="showWhat"></component>
</keep-alive>
</transition>
复制代码
能够经过 appear
属性设置节点的初始渲染过渡 ; 须要注意的是, 这个关键字加上以后, 默认就会带有过渡效果 , translateY
也能够自定义类名和钩子 ; 初始渲染过渡
<transition appear>
<div v-if="toggle">post</div>
<div v-else>list</div>
</transition>
复制代码
列表过渡须要用到 <transition-group>
<transition-group tag="ul" appear>
<li v-for="user of users" :key="user.id">
<p>{{ user.uname }}</p>
</li>
</transition-group>
复制代码
更多动画相关 : Vue过渡动画
Vue 在更新 DOM 的时候是异步的 , 只要侦听到数据的变化 , 并缓存在同一事件循环中 , 等待事件循环中的全部的数据变化完成后, 统一更新视图 , 为了获得最新的DOM 因此设置了
nextTick()
将回调延迟到下次 DOM 更新循环以后执行 , 在修改数据以后当即使用他 , 而后等待 DOM 更新 ;
简单理解就是 :
nextTick
是将这个回调函数延迟在下一次 DOM 更新数据后调用 , 即 DOM 从新渲染后自动执行该函数
created() {
// created 钩子中能够操做 DOM 元素
this.$nextTick(() => {
this.$refs.divRef.innerHTML = 'hello vue'
})
}
复制代码