1.父子通信扩展
1.1.-父访问子组件的访问方式 $children $refs(实例中加标签)
某些时候,父子组件进行调用某些方法,实现功能,这里就要用的父子组件的访问方式的修饰符,有时候我们需要父组件直接通过对象来访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
- 父组件访问子组件:使用$children或$refs(reference 引用意思)
其中¥children也是数组,子组件对象类型是数组类型是vuecomponent
- 子组件访问父组件:使用$parent
一般来说,我们也不会用¥children拿子组件东西(下标拿毕竟不安全,万一加其他组件,容易出错,而且除非是拿全部组件,才会用$children),我们用$refs,如果组件不加内容则为空.
$refs=》对象类型,默认是空对象,加ref=‘bbb’
必须在子组件使用时候,在父实例中加上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访问的是父组件
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>
作用域插槽-
作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:
父组件替换插槽的标签,但是内容由子组件来提供。
我们先提一个需求:子组件中包括一组数据,比如:pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
需要在多个界面进行展示:
- 某些界面是以水平方向一一展示的,
- 某些界面是以列表形式展示的,
- 某些界面直接展示一个数组
内容在子组件,比方说子组件按逗号分隔,希望父组件告诉我们如何展示,怎么办呢?利用slot作用域插槽就可以了,我们来看看子组件的定义:
<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了
3.前端模块化开发
客户端需要完成的事情越来越多,代码量也是与日俱增。为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。
但是这种维护方式,依然不能避免一些灾难性的问题。
但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。
闭包解决了变量引用冲突风险,但是造成了变量引用困难的问题。我们可以使用匿名函数来解决方面的重名问题,在aaa.js文件中,我们使用匿名函数
但是如果我们希望在main.js文件中,用到flag,应该如何处理呢?显然,另外一个文件中不容易使用,因为flag是一个局部变量。我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?
来看下对应的代码:我们做了什么事情呢?
- 非常简单,在匿名函数内部,定义一个对象。
- 给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
- 最后将这个对象返回,并且在外面使用了一个MoudleA接受。
接下来,我们在man.js中怎么使用呢?
- 我们只需要使用属于自己模块的属性和方法即可
这就是模块最基础的封装,事实上模块的封装还有很多高级的话题:
- 但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。
- 幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。
常见的模块化规范:
- CommonJS是规范,具体实现是node.js实现
- AMD
- CMD
- ES6的Modules
模块化最重要的两点是导入和导出,可以理解为导出一般是return obj',导入就是用的时候。下面写法是在node中才行,因为需要环境解析
CommonJS导出 moudle.exports CommonJS的导入 require('路径')
3.1. ES6里面的Modules模块化操作
ES6里面给予模块化开发提供了两个函数 import和export。
我们使用export指令导出了模块对外提供的接口,下面我们就可以通过import命令来加载对应的这个模块了
- export
导出变量(边导出边定义), 另外一种写法(先定义,后导出):
也可以输出函数或者输出类
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名,这个时候就可以使用export default
我们来到main.js中,这样使用就可以了,这里的myFunc是我自己命名的,你可以根据需要命名它对应的名字,切记default导入时不需要大括号,但有且仅有一个,否则出错。
另外,需要注意:export default在同一个模块中,不允许同时存在多个。
- import命令来加载对应的这个模块了
1.首先,我们在HTML代码中引入两个js文件,并且类型需要设置为module,表示我们这个js文件使用模块化思想开发,意味着这个js是个模块,单独模块具有单独的作用域,这样即便两个js文件中都有同名变量和函数都不存在命名冲突问题。
2.import指令用于导入模块中的内容,比如main.js的代码
如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦:
- 通过*可以导入模块中所有的export变量
- 但是通常情况下我们需要给*起一个别名,方便后续的使用
整体代码效果如下: