3.Vue组件开发

认识组件化

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
组件化是Vue.js中的重要思想。它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树。

注册组件

组件的使用分成三个步骤:创建组件构造器(在vue2.x中取消)、注册组件、使用组件。
创建组件的流程.png

组件其他补充

全局组件和局部组件

当我们通过调用Vue.component()注册组件时,组件的注册是全局的。这意味着该组件可以在任意Vue示例下使用。

/* 两个都被渲染出来 */
//哈哈哈 //哈哈哈

如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

/*app2 没有被渲染出来*/

父组件和子组件

组件和组件之间存在层级关系。而其中一种非常重要的关系就是父子组件的关系。

注意:无法在Vue实例中直接使用子组件cpn2。

注册组件语法糖

在上面注册组件的方式,可能会有些繁琐。Vue为了简化这个过程,提供了注册的语法糖。主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。

模板的分离写法

通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
Vue提供了两种方案来定义HTML模块内容:使用

组件数据存放

组件是一个单独功能模块的封装:这个模块有属于自己的HTML模板,也应该有属性自己的数据data。组件对象也有一个data属性,这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据。
为什么data在组件中必须是一个函数?
首先,如果不是一个函数,Vue直接就会报错。
其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。返回函数,目的是让每一个组件都有一个属于自己的状态。
在根组件data不需要为函数是因为根组件只有一个。

说明代码:

//示例1
function foo() {
  return {
    name: "tom",
    age: 22
  }
}
let obj1 = foo();
let obj2 = foo();
obj1.name = 'Jack';
console.log(obj2.name);//tom

//示例2
let obj = {
  name: "tom",
  age: 22
};
function bar() {
  return obj;
}
let obj1 = bar();
let obj2 = bar();
obj1.name = "Jack";
console.log(obj2.name);//Jack

父子组件通信

子组件是不能引用父组件或者Vue实例的数据的。如何进行父子组件间的通信呢?Vue官方提到:
在父组件中通过props向子组件传递数据;
在子组件中通过事件向父组件发送消息。
3.Vue组件开发_第1张图片

父级向子级传递

在子组件中,使用选项props来声明需要从父级接收到的数据。
因为v-bind不支持驼峰标识,如果在子组件props使用驼峰标识(cMess),那么在使用子组件的绑定时应使用(c-mess)。
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。(在实际开发中使用对象的形式较多)
3.Vue组件开发_第2张图片
除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。StringNumberBooleanArrayObjectDatFunctionSymbol。验证也支持自定义的类型。

子级向父级传递

当子组件需要向父组件传递数据时,就要用到自定义事件了。之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
在子组件中,通过$emit()来触发事件。在父组件中,通过v-on来监听子组件事件。
在当页面应用,子组件向父组件通信中所传递的事件名称不能是驼峰式。若要修改子组件中通过父子件传来的值,不应该在子组件中直接操作,而应通过父组件。
3.Vue组件开发_第3张图片

父子组件的访问

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children$refs。子组件访问父组件:使用$parent

$children

先来看下$children的访问。this.$children是一个数组类型,它包含所有子组件对象。
3.Vue组件开发_第4张图片
我们这里通过一个遍历,取出所有子组件的message状态。
$children的缺陷:
通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs

$refs

$refs的使用:
$refsref指令通常是一起使用的。首先,我们通过ref给某一个子组件绑定一个特定的ID。其次,通过this.$refs.ID就可以访问到该组件了。

$parent

如果想在子组件中直接访问父组件,可以通过$parent。
注意事项:
尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。

此外,还可以通过$root访问根组件。

非父子组件通信

使用Vuex

监听属性watch

可以通过 watch 来响应数据的变化。
里面的函数名必须为data中定义的属性,里面可以传两个值:newValue,oldValue。

watch 默认是浅监听。(只监听表层的变化。)对于引用类型(对象数组)的浅监听:只能监听自身一层,他的子层及以下改变监听不到。watch 如何是深度监听?在属性中设置:deep: true
注意:watch 监听引用类型时,是拿不到oldVal。因为指针相同,新值旧值指向同一个堆的地址。此时已经指向了新的val。

data() {
    return {
        msg: "这是首页",
        counter: 0,
        info:{
            city:"beijing"
        }
    }
},
watch: {
    //值类型,可正常拿到oldVal 和 newVal
    counter: function (newVal, oldVal) {
        alert('计数器值的变化 :' + oldVal + ' 变为 ' + newVal + '!');
    },
    //引用类型,拿不到oldVal,因为指针相同,指向同一个堆的地址。此时已经指向了新的val
    info: {
        handler(oval, nval) {
            console.log("watch info", oval, nval);
        },
        deep: true //深度监听
    }
}

插槽slot

使用插槽的原因

组件的插槽:
组件的插槽也是为了让我们封装的组件更加具有扩展性。让使用者可以决定组件内部的一些内容到底展示什么。
例子:移动网站中的导航栏。移动开发中,几乎每个页面都有导航栏。导航栏我们必然会封装成一个插件,比如nav-bar组件。一旦有了这个组件,我们就可以在多个页面中复用了。但是,每个页面的导航并不是一样的?
如何封装这类组件
抽取共性,保留不同。
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。是搜索框,还是文字,还是菜单。由调用者自己来决定。

slot基本使用

在子组件中,使用特殊的元素就可以为子组件开启一个插槽。该插槽插入什么内容取决于父组件如何使用。
1.插槽的基本使用
2.插槽的默认值 button
3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素。

3.Vue组件开发_第5张图片

具名插槽slot

当子组件的功能复杂时,子组件的插槽可能并非是一个。比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
这个时候,我们就需要给插槽起一个名字。
如何使用具名插槽呢?
非常简单,只要给slot元素一个name属性即可给出一个案例:这里我们先不对导航组件做非常复杂的封装,先了解具名插槽的用法。

haha

//左边 //haha //右边

作用域插槽

编译作用域

先看一个例子:考虑下面的代码是否最终是可以渲染出来的:
3.Vue组件开发_第6张图片
答案:最终可以渲染出来,也就是使用的是Vue实例的属性。
解释:官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。而在使用的时候,整个组件的使用过程是相当于在父组件中出现的。那么他的作用域就是父组件,使用的属性也是属于父组件的属性。因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。

作用域插槽

父组件对子组件展示数据的方式不满意,他要以自己方式展示,就需要从子组件中获取数据。父组件替换插槽的标签,但是内容由子组件来提供。
3.Vue组件开发_第7张图片

说明:
子组件中:data也可以其他名称,但是不能使用驼峰式。
在vue2.5.x以上的版本中,