一. 自定义指令 //操作DOM
有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
1.什么是自定义指令:
自己配置的指令
2.什么时候使用自定义指令?
当你不可避免的操作DOM时
3.如何全局注册指令
全局注册: 如果需要在多个组件中使用该指令,则应声明为全局
参数一:指令名
参数二:钩子函数
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
4.钩子函数
这里需要解释什么是钩子函数:
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind:一开始就执行 绑定阶段 ,在bind阶段 el无法拿到父元素 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:一开始就执行 绑定后插入父元素中的阶段 在inserted阶段 el可以拿到父元素 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:window更新时触发,其获取的是更新之前的DOM 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:组件数据更改时触发,其获取的是更新之后的DOM.指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
5.钩子函数参数
每个钩子函数都默认传入两个参数 el和binding
el是调用此函数的DOM元素, binding是 v-指令 对象
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下属性:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
6. 自定义指令函数简写
在很多时候,你可能想在bind和update时触发相同行为,而不关心其它的钩子。
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
7. 使用
指令的名字前面加 v-来调用 ,
如果是驼峰命名法,则应该转为小写并用 - 连接
如 autoFocus 引用时 v-auto-focus
例如:使输入框聚焦
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
8. v-指令:参数 = xxx 参数是如何传递的?
例如:v-bind:class='xxx' 这样的指令怎么定义?怎么把参数class传入 指令生成器中呢?
其实它是通过 第二个参数binding中的 arg传的
xxx怎么传入的呢?
其实它是通过 第二个参数binding中的value 传的
Document
9. 私有自定义指令
和全局的指令生成器差不多,只不过写在组件的 directives成员里面
hello
new Vue({
el: '#app',
data: {
},
directives: {
color: function (el, bingding) {
el.style.color = 'red'
}
}
})
二. 组件化
组件化思想就是将一个大视图拆分成一个个小的模块
组件化可以方便开发和维护,同时方便复用
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
template只能有一个根元素
template可以传值字符串,但是这样写没有高亮提醒
可以用vue里单文件组件解决这一个问题 详见:单文件组件
组件其实就是一个vue实例,所以他有自己的data methods等成员,也是一个独立的作用域
但是,组件的data必须是个方法,方法返回一个对象作为组件的data
组件可以被认为是js模块,所以存在组件间参数传递的问题
1.组件使用步骤 //只是阐述原理,现在这种写法已经不太常见了
(1).创建组件构造器
用Vue的extend构造方法构造组件
const myComponent =Vue.extend({ //我们用Vue的extend构造方法构造了一个组件
template:`这是个待办项 `
//其最重要的属性就是模板,vue会使用这个模板来渲染HTML
})
现在这种写法已经不太常见了
(2).注册组件
Vue.component(id, [definition])
Vue.component('myComponent', myComponent)
(3).使用组件
使用组件时注意,驼峰命名法会自动转为 - 命名法
2 组件定义的语法糖
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
这里,我们把组件构造器直接传入了组件注册器了
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {//(由于代码比较简单,这里,我们把组件构造器直接传入了组件注册器了)
template: '这是个待办项 '
})
var app = new Vue(...)
现在你可以用它构建另一个组件模板:
问题一: 感觉template写在模板里太乱了~~~!!
可以按如下的方法提取出来
在html的body里 先写一个template标签 命名好id
再把模板内容放入
在组件定于语法中写: template:'#id' 即可完成关联
Document
这是个待办项
这是个待办项
问题二: 这样写不算乱,但是模板如果写在body里,将来如何拆分文件????
的确是这样,看来模板不能写在body里 还是要写在script里,如果写在script里,我们可以这样做:
Document
这样写确实可以拆分了,但是在script里写html没有代码缩进和提示非常别扭,也不方便修改~!
的确是这样!!想要改变这一情况,请看后续章节中的 现代化的解决方案.
3.全局组件和局部组件
全局组件定义在全局,在任意组件中都可以直接使用
局部组件定义在局部,只能在当前组件中使用
建议把通用组件定义为全局,把不通用的涉及具体业务的组件定义为局部
全局注册也必须在Vue接管的作用域中用
在此之前,我们先弄清楚一个问题:组件看成是一个Vue实例的话,它有没有data属性?
当然了~~!!但是组件里的data必须是传入一个function,function里面再return对象出来 (这样做是为了防止组件多次调用时,如果他们在内存中指向同一个对象,会导致data内的数据被不同实例互相篡改)
(1).全局注册
Document
(2) 局部组件
首先明白子组件,子组件是声明在父组件的components属性内的组件,声明方法 如下:
Vue.component('my-component1', {
template: `
{{msg}}
`,
data: function () {
return {
msg: 'hello component!',
seen:true
}
},
components:{
component2:{
template:"hello
"
}
}
})
这种声明方式使得component2只能在my-component1的作用域内被引用,无法被全局引用
4.传参的组件
上面的组件没有参数,生成的标签千篇一律,为了解决这个问题,我们可以给自己的组件传参
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义 attribute。
// 这个 prop 名为 todo。
props: ['todo'],
template: '{{ todo.text }} '
})
这样,我们就可以生成不同内容的自定义组件了
例如:
Vue.component('todo-item', {
props: ['todo'],
template: '{{ todo.text }} '
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
在一个大型应用中,有必要将整个应用程序划分为组件
三. 父子组件
如果一个组件是另一个组件的局部组件,则我们称它为子组件,其外包组件为父组件.
典型的父子组件结构:
其实我们有一个组件通信大杀器:vuex,详见后续章节
组件的初衷就是配合使用,最常见的就是形成父子关系,A中用B,所以他们之间必须通讯,父组件要能够给子组件下发数据,子组件也可能要将内部的事件告知父组件
父子组件可以总结为prop向下传递,事件向上传递
1.props //给子组件传递数据
父组件调用子组件时,通过标签传参,子组件的props接收下发数据
①.在父组件template中找到子组件,通过声明属性的方式传递数据
注意:如果需要动态传递值,必须用v-bind 如果只是想传个简单的值,可以直接写在标签里,如下:
②.在子组件中声明成员props接收传递的参数 ,组件接收到的props数据后可以像访问data一样访问
props可以传入数组,也可以传入对象 ,
比较推荐传入对象,因为传入对象时,可以指定
数据类型type
,默认值default, //注意:如果type是数组或者对象,默认值必须是函数 return出来
是否必须required
props: ['foo']
props:{
movies: Array,
count:{
required:true
},
name:{
type:String,
default:'无名氏'
}
}
支持的数据类型如下: 除此之外还可以要求是你自己的自定义类型
还要注意一个问题!!!! 子组件props里的数据不要和子组件的表单做双向数据绑定!
(因为他已经可以被父组件改变了!!!别再让自己的表单改变它)
2.单项数据流
prop是单项绑定的:当父组件的属性变化时,可以传递给子组件,反过来则不会,这种设计是为了放在子组件无意间改变父组件状态
在子组件中,确实可以操作父组件传入的数据,但只能操作引用类型的数据,这跟JS数据存储原理有关,但是,非常不建议这么做
普通类型不允许这样改, 实际上引用类型也不能用 = 号重新赋值
3.子传父 //如何修改父组件数据
合理的想法:子组件应该把数据给父组件,父组件接收后自己修改自己的数据
那么,我们到底怎么修改父组件的数据呢?
① 在父组件中定义一个方法来修改自己的数据
methods: {
addTodo(titleText){
this.todos.push({
title:titleText,
done:false
})
}
},
②在子组件中发布一个事件,通知父组件
methods: {
handleKeyUpEnter(e){
this.$emit('wantYouAddTodo', e.target.value)
this.title=''
}
},
③在父组件使用子组件的标签上 用v-on订阅 子组件发布的自定义事件
事件名就是子组件发射的事件名
注意:虽然父元素的接收方法是带参数的,但v-on绑定的时候没有把参数写在指令中,系统会默认把参数传过去.
整个过程采用了发布/订阅的通信方式.请体会.
4. 综合练习
接下来我们再举例: 子组件点击某个按钮,并通知父组件他点击了哪一个
Document
按钮{{child_be_clicked}}被点了
5.父子组件互相访问
直接获取对象
(1)父访问子
父访问子有两种方式:
$children 返回父组件的子组件,一般不建议这样做 (很少用,一般只用于拿到所有子组件)
ref中(常用,多用于拿到特定组件,但必须加ref键值)
我们可以直接通过this.resf数组
甚至用this.$refs.aaa访问它
(2)子访问父
子访问父: $parent (很少用.用了这个,子组件就不够独立了)
(3) 子孙访问根组件
$root
四. 组件化思想拆分文件
1.原生Vue方法
index.html中没有任何标签,只留vue实例的入口标签,所有内容靠组件拼凑
Template • TodoMVC
根据入口