14 小程序-组件通信修饰符、插槽、前端模块化开发

1.父子通信扩展

1.1.-父访问子组件的访问方式 $children  $refs(实例中加标签)

        某些时候,父子组件进行调用某些方法,实现功能,这里就要用的父子组件的访问方式的修饰符,有时候我们需要父组件直接通过对象来访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

  • 父组件访问子组件:使用$children或$refs(reference 引用意思)

         其中¥children也是数组,子组件对象类型是数组类型是vuecomponent

  • 子组件访问父组件:使用$parent

image

image

     一般来说,我们也不会用¥children拿子组件东西(下标拿毕竟不安全,万一加其他组件,容易出错,而且除非是拿全部组件,才会用$children),我们用$refs,如果组件不加内容则为空.

      $refs=》对象类型,默认是空对象,加ref=‘bbb’

image

      必须在子组件使用时候,在父实例中加上ref=‘XXX’,
然后父组件监听事件中给予使用this.$refs
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnclick">按钮</button>
</div>

<template id="cpn">
<h2>我是子组件</h2>
</template>
<script>
const app=new Vue({
el:'#app',
data:{

},
methods:{
btnclick(){
console.log(this.$children);
console.log('---------')
console.log(this.$refs.aaa.name);

}
},
components:{
cpn:{
template:'#cpn',
methods:{
showMessage222(){
console.log('showMessage222')
}
},
data(){
return{
name:'我是子组件的name'
}
}
}
}
})
</script>

1.2.-子访问父组件的访问方式 parent  root

      当然一般很少这么使用,因为子组件还是建议相对独立,否则耦合性太高

<div id="app">
    <cpn></cpn>

</div>
<template id="cpn">
    <div>
        <h2>我是cpn组件</h2>
        <ccpn></ccpn>
    </div>
</template>
<template id="ccpn">
    <div>
        <div>我是子组件</div>
        <button @click="btnclick()">按钮</button>
    </div>
</template>
<script>
    const app=new Vue({
        el:'#app',
        data:{
            message:'你好啊'
        },
        components:{
            cpn:{
                template:'#cpn',
                data(){
                    return {
                        name:'我是cpn组件的name'

                    }
                },
                components: {
                    ccpn:{
                        template:'#ccpn',
                        methods:{
                            btnclick(){
                                //    访问父组件
                                console.log(this.$parent);
                                console.log(this.$parent.name);
                            }
                        }
                    }
                }

            },

        }

    })


</script>

     可以使用this.$root,直接访问vue实例,因为$parent访问的是父组件

image

    this.¥root.message也就可以访问到“你好啊”

2. 插槽slot 使用

2.1 插槽slot基本使用

slot翻译为插槽:

  • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
  • 插槽的目的是让我们原来的设备具备更多的扩展性。
  • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。

组件的插槽:

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  • 让使用者可以决定组件内部的一些内容到底展示什么。

栗子:移动网站中的导航栏。

  • 移动开发中,几乎每个页面都有导航栏。
  • 导航栏我们必然会封装成一个插件,比如nav-bar组件。
  • 一旦有了这个组件,我们就可以在多个页面中复用了。

但是,每个页面的导航是一样的吗?No,我以京东M站为例

<template id="cpn">
    <div>
        <h2>我是组件</h2>
        <p>我是组件,哈哈哈</p>
        <slot></slot>
    </div>
</template>

      如何使用呢?

最基本的使用如下:引用的组件内部进行标签替换。

<div id="app">
    <cpn><button>按钮</button></cpn>
    <cpn><span>哈哈</span></cpn>
    <cpn><i>合伙</i></cpn>
    <cpn><button>按钮</button></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是组件</h2>
        <p>我是组件,哈哈哈</p>
        <slot></slot>
    </div>
</template>

       当然如果说某个插槽非常常用,比方说按钮,那么我们给予模板template添加默认插槽:则效果与上面一样

<div id="app">
    <cpn></cpn>
    <cpn><span>哈哈</span></cpn>
    <cpn><i>合伙</i></cpn>
    <cpn></cpn>
</div>
<template id="cpn">
    <div>
        <h2>我是组件</h2>
        <p>我是组件,哈哈哈</p>
        <slot><button>按钮</button></slot>
    </div>
</template>

2.2 插槽slot-具名插槽

   具名插槽,意思是给每个slot添加个名字,那么使用它时,如果没有名字,进行修改插槽,那么则默认给没有名字的插槽进行变化

</head>
<body>
<div id="app">
    <cpn><button>按钮</button></cpn>
</div>
<template id="cpn">
    <div>
        <slot name="left"><span>左边</span></slot>
        <slot name="center"><span>中间</span></slot>
        <slot name="right"><span>右边</span></slot>
        <slot>哈哈哈</slot>
    </div>
</template>

     那么给予名字时候,不能用name,而是用slot=‘left’名字即可

<div id="app">
    <cpn><button slot="left">按钮</button></cpn>
</div>
<template id="cpn">
    <div>
        <slot name="left"><span>左边</span></slot>
        <slot name="center"><span>中间</span></slot>
        <slot name="right"><span>右边</span></slot>
        <slot>哈哈哈</slot>
    </div>
</template>

2.3 编译作用域与插槽作用域

在真正学习插槽之前,我们需要先理解一个概念:编译作用域。官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:

我们来考虑下面的代码是否最终是可以渲染出来的:

  • <my-cpn v-show="isShow"></my-cpn>中,我们使用了isShow属性。
  • isShow属性包含在组件中,也包含在Vue实例中。

答案:最终可以渲染出来,也就是使用的是Vue实例的属性。

为什么呢?

  • 官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
  • 而我们在使用<my-cpn v-show="isShow"></my-cpn>的时候,整个组件的使用过程是相当于在父组件中出现的。
  • 那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
  • 因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。


<div id="app">
<!--    这里v-show使用实例的,当成最简单的变量,去vue实例作用域找-->
    <cpn v-show="isShow"></cpn>
<!--    这里item也是去vue实例里面找-->
    <cpn v-for="item in names"></cpn>
</div>

<template id="cpn">
    <div>
        <h2>我是子组件</h2>
        <p>我是子组件,哈哈哈</p>
<!--        这里用的是子组件的作用域isShow-->
        <button v-show="isShow">按钮</button>
    </div>
</template>
<script>
    const app=new Vue({
        el:'#app',
        data:{
            message:'你好啊',
            isShow:true
        },
        components:{
            cpn:{
                template:'#cpn',
                data(){
                    return{
                        isShow:false
                    }
                }
            }
        }
    })
</script>

image

        作用域插槽-

作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:

父组件替换插槽的标签,但是内容由子组件来提供。

我们先提一个需求:子组件中包括一组数据,比如:pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']

需要在多个界面进行展示:

  • 某些界面是以水平方向一一展示的,
  • 某些界面是以列表形式展示的,
  • 某些界面直接展示一个数组

        内容在子组件,比方说子组件按逗号分隔,希望父组件告诉我们如何展示,怎么办呢?利用slot作用域插槽就可以了,我们来看看子组件的定义:

image


<body>
<div id="app">
    <cpn></cpn>
    <cpn>
<!--        目的 获取子组件的planguages  vue2.5以前版本-->
        <template slot-scope="slot">
            <span v-for="item in slot.data">{{item}}-</span>
        </template>
    </cpn>
    <cpn>
        <!--        目的 获取子组件的planguages  vue2.5以前版本-->
        <template slot-scope="slot">
<!--            join的作用是将数组转为字符串,每个数组元素以某种方式拼接-->
            <span >{{slot.data.join('=')}}</span>
        </template>
    </cpn>
</div>

<template id="cpn">
    <div>
        <slot v-bind:data="planguages">
            <ul>
                <li v-for="item in planguages"> {{item}}</li>
            </ul>
        </slot>
    </div>
</template>

<script>
    const app=new Vue({
        el:'#app',
        data:{
            message:'你好啊'
        },
        components:{
            cpn:{
                template:'#cpn',
                data(){
                    return{
                        planguages:['JavaScript', 'Python', 'Swift', 'Go', 'C++']
                    }
                }
            }
        }
    })
</script>

在父组件使用我们的子组件时,从子组件中拿到数据:

  • 我们通过<template slot-scope="slotProps">获取到slotProps属性
  • 在通过slotProps.data就可以获取到刚才我们传入的data了

image

3.前端模块化开发

      客户端需要完成的事情越来越多,代码量也是与日俱增。为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。

但是这种维护方式,依然不能避免一些灾难性的问题。

       但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。

      闭包解决了变量引用冲突风险,但是造成了变量引用困难的问题。我们可以使用匿名函数来解决方面的重名问题,在aaa.js文件中,我们使用匿名函数

image

       但是如果我们希望在main.js文件中,用到flag,应该如何处理呢?显然,另外一个文件中不容易使用,因为flag是一个局部变量。我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?

来看下对应的代码:我们做了什么事情呢?

  • 非常简单,在匿名函数内部,定义一个对象。
  • 给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
  • 最后将这个对象返回,并且在外面使用了一个MoudleA接受。

image

         接下来,我们在man.js中怎么使用呢?

  • 我们只需要使用属于自己模块的属性和方法即可

这就是模块最基础的封装,事实上模块的封装还有很多高级的话题:

  • 但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。
  • 幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。

常见的模块化规范:

  • CommonJS是规范,具体实现是node.js实现
  • AMD
  • CMD
  • ES6的Modules

    模块化最重要的两点是导入和导出,可以理解为导出一般是return  obj',导入就是用的时候。下面写法是在node中才行,因为需要环境解析

  CommonJS导出  moudle.exports        CommonJS的导入  require('路径')

imageimage

3.1. ES6里面的Modules模块化操作

       ES6里面给予模块化开发提供了两个函数 import和export。 

       我们使用export指令导出了模块对外提供的接口,下面我们就可以通过import命令来加载对应的这个模块了

  • export

导出变量(边导出边定义),      另外一种写法(先定义,后导出):

imageimage

也可以输出函数或者输出类

imageimage

       某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名,这个时候就可以使用export default

image

      我们来到main.js中,这样使用就可以了,这里的myFunc是我自己命名的,你可以根据需要命名它对应的名字,切记default导入时不需要大括号,但有且仅有一个,否则出错。

image

    另外,需要注意:export default在同一个模块中,不允许同时存在多个。

  • import命令来加载对应的这个模块了

1.首先,我们在HTML代码中引入两个js文件,并且类型需要设置为module,表示我们这个js文件使用模块化思想开发,意味着这个js是个模块,单独模块具有单独的作用域,这样即便两个js文件中都有同名变量和函数都不存在命名冲突问题。

image

2.import指令用于导入模块中的内容,比如main.js的代码

image

如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦:

  • 通过*可以导入模块中所有的export变量
  • 但是通常情况下我们需要给*起一个别名,方便后续的使用

image

整体代码效果如下:

image








posted @ 2020-11-25 09:51  芒果侠  阅读(406)  评论(0编辑  收藏  举报