Vue.js学习笔记第二部分组件化
因为hexo会试图渲染页面的mastache语法 所以每个mastache语法间加入空格
vue版本:v2.6.12
什么是组件化
Vue组件化思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何应用都会被抽象成一个组件树
组件化思想应用:
- 充分利用组件
- 尽可能将页面拆分成小的可复用组件
- 方便组织和管理代码,扩展性强
注册组件基本步骤
- 创建组件构造器
- 注册组件
- 使用组件
组件基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <body> <div id="app"> <my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> </div> <script src="../../js/vue.js"></script> <script> const cpnC=Vue.extend({ template: ` <div> <h2>标题</h2> <p>内容</p> <p>内容2</p> </div> ` }); Vue.component('my-cpn',cpnC);
const app=new Vue({ el: '#app', data: { message: '你好啊' } }); </script> </body>
|
全局组件和局部组件
全局组件可以在多个vue实例下使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <body> <div id="app"> <my-cpn></my-cpn> <cpn></cpn> </div>
<div id="appb"> <my-cpn></my-cpn> </div> <script src="../../js/vue.js"></script> <script> const cpnC=Vue.extend({ template: ` <div> <h2>标题</h2> <p>内容</p> <p>内容2</p> </div> ` }); Vue.component('my-cpn',cpnC);
const app=new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: cpnC } });
const app2=new Vue({ el: '#appb' }); </script> </body>
|
父组件和子组件
基本创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <div id="app"> <cpn2></cpn2> </div> <script src="../../js/vue.js"></script> <script> const cpnC1=Vue.extend({ template: ` <div> <h2>标题</h2> <p>内容</p> </div> ` }); const cpnC2=Vue.extend({ template: ` <div> <h2>标题2</h2> <p>内容2</p> <cpn1><cpn1> </div> `, components: { cpn1: cpnC1 } }); const app=new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn2: cpnC2 } }); </script>
|
注册组件语法糖
全局
1 2 3 4 5 6 7 8
| Vue.component('cpn1',{ template: ` <div> <h2>标题</h2> <p>内容</p> </div> ` });
|
局部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const app=new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template: ` <div> <h2>标题</h2> <p>内容</p> </div> ` } } });
|
简单组件模板分离写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <div id="app"> <cpn></cpn> </div>
<script type="text/x-template" id="cpn"> <div> <h2>标题</h2> <p>内容</p> </div> </script>
<template id="cpn"> <div> <h2>标题</h2> <p>内容</p> </div> </template>
<script src="../../js/vue.js"></script> <script> Vue.component('cpn',{ template: '#cpn' });
const app=new Vue({ el: '#app', data: { message: '你好啊' } }); </script>
|
组件数据存放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <div id="app"> <cpn></cpn> </div>
<template id="cpn"> <div> <h2>{{title}}</h2> <p>内容</p> </div> </template>
<script src="../../js/vue.js"></script> <script> Vue.component('cpn',{ template: '#cpn', data(){ return { title: 'abc' } } });
const app=new Vue({ el: '#app', data: { message: '你好啊' } }); </script>
|
组件对象也有data属性 也有method等属性
为什么组件的data属性是函数,而vue实例data属性是对象
调用组件的时候,每次返回一个新的对象给新组件,避免组件共用数据
父子组件通信-父传子props(properties缩写)
请求数据一般是最外面的组件请求数据传递给里面的小组件。
父组件通过prop向子组件传递数据,子组件通过事件向父组件通信
props基本用法(父向子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <div id="app"> <cpn v-bind:sonmovies="movies" :sonmessage="message"></cpn> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div>
<template id="cpn"> <div> <h2>子组件</h2> <p>{{sonmovies}}</p> <p>{{sonmessage}}</p> </div> </template>
<script src="../../js/vue.js"></script> <script> const cpn={ template: '#cpn', props: ['sonmovies','sonmessage'] } const app=new Vue({ el: '#app', data: { movies: ['电影1','电影2','电影3'], message: '你好啊' }, components: { cpn } }); </script>
|
props属性除数组之外也可以使用对象,当需要对props进行类型等验证时,就需要对象写法
验证支持哪些数据类型:
- String
- Number
- Boolean
- Array
- Objecy
- Date
- Function
- Symbol
当我们有自定义构造函数时,验证也支持自定义类型
1 2 3 4
| props:{ cmovies: Array, cmessage: String }
|
默认值
1 2 3 4
| sonmessage:{ type: String, default: '默认值' }
|
对象和数组默认值必须由一个工厂函数
1 2 3 4 5 6
| sonmovies: { type: Array, default: function(){ return [] } }
|
必须传值,不传报错
1 2 3
| sonmessage:{ required: true }
|
自定义验证
1 2 3 4 5 6 7
| sonmovies: { type: Array, validator: function(value){ return ['success','warning','danger'].indexOf(value) !==-1 } }
|
自定义类型
1 2 3 4 5 6 7 8 9
| function Person(firstName,lastName){ this.firstName=firstName; this.lastName=lastName; } Vue.component('blog-post',{ props:{ author: Person } })
|
vue在dom中不支持驼峰命名:驼峰的地方用-分割 例如:cInfo在v-bind中写成c-info
子组件向父组件传递数据(自定义事件)
按钮点击时向父组件传递一些信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <div id="app"> <cpn @itemclick="cpnClick"></cpn> </div>
<template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)"> {{item.name}} </button> </div> </template>
<script src="../../../js/vue.js"></script> <script> const cpn={ template: '#cpn', data(){ return { categories: [ {id: 'aaa',name: '热门推荐'}, {id: 'bbb',name: '手机数码'}, {id: 'ccc',name: '家用家电'}, {id: 'ddd',name: '电脑办公'} ] } }, methods: { btnClick(item){ this.$emit('itemclick',item) } }
}
const app=new Vue({ el: '#app', data: {
}, components: { cpn }, methods: { cpnClick(){ console.log('cpnClick',item); } } }); </script>
|
以上仅学习使用,真实开发实践不会这样写(真实开发实践到使用脚手架部分)
子组件双向绑定父组件的值
子组件通过绑定子组件data函数返回的值来双向绑定父组件中的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <div id="app"> <cpn :number1="num1" :number2="num2"> </div>
<template id="cpn"> <div> <h2>{{number1}}</h2> <input type="text" v-model="number1"> <h2>{{number2}}</h2> <input type="text" v-model="number2"> <hr> <h2>{{dnumber1}}</h2> <input type="text" v-model="dnumber1"> <h2>{{dnumber2}}</h2> <input type="text" v-model="dnumber2"> </div> </template>
<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const app=new Vue({ el: '#app', data: { num1: 1, num2: 0 }, components: { cpn: { template:'#cpn', props: { number1: Number, number2: Number }, data(){ return { dnumber1: this.number1, dnumber2: this.number2 } } } } }); </script>
|
父访问子-children-refs
$children
父组件访问子组件数据并操作
this.$children是一个数组类型,它包含所有子组件对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> <button @click="btnClick">按钮</button> </div>
<template id="cpn"> <div> 我是子组件 </div> </template> <script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const app=new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn', methods: { showMessage(){ console.log('showMessage'); } }, data() { return { name: '子组件name' } } } }, methods: { btnClick(){ console.log(this.$children); this.$children[0].showMessage(); console.log(this.$children[0].name); for(let c of this.$children){ console.log(c.name); c.showMessage() } } } }); </script>
|
实际开发不会通过$children获取数据,而是使用$refs属性
$refs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <div id="app"> <cpn></cpn> <cpn></cpn> <cpn ref='aaa'></cpn> <button @click="btnClick">按钮</button> </div>
<template id="cpn"> <div> 我是子组件 </div> </template> <script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const app=new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn', methods: { showMessage(){ console.log('showMessage'); } }, data() { return { name: '子组件name' } } } }, methods: { btnClick(){ console.log(this.$refs.aaa); } } }); </script>
|
子访问父-parent-root
- 用的少,不建议使用,使用后子组件缺乏独立性,耦合度过高,仅作了解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <div id="app"> <cpn></cpn> </div>
<template id="cpn"> <div> <ccpn></ccpn> </div> </template>
<template id="ccpn"> <div> 我是子组件 <button @click='btnClick'>按钮</button> </div> </template>
<script src="../../js/vue.js" type="text/javascript" charset="utf-8"></script> <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); console.log(this.$root); } } } } } } }); </script>
|
插槽slot
为什么使用slot
- slot翻译为插槽:
- 生活中的插槽:usb扩展插槽,插板店员插槽
- 插槽的目的是让我们原来的设备具有更多的扩展性
- 比如电脑的usb我们可以插入u盘、硬盘、手机、等设备
- 组件的插槽:
- 组件的插槽也是为了让我们封装的组件更加具有扩展性
- 让使用者可以决定组件内部的一些内容到底展示什么
- 例子:移动网站中的导航栏
- 移动开发中,几乎每个页面都有导航栏
- 导航栏我们必然会封装成一个插件,比如nav-bar组件
- 一旦有了这个组件,我们就可以在多个页面复用了。
- 但是,每个页面的导航是不一样的,需要预留空间显示其他不同组件
- 这个时候就可以用slot标签预留位置
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <div id="app"> <cpn><button type="button">按钮</button></cpn> <cpn><span>哈哈哈</span></cpn> <cpn></cpn> <cpn></cpn> </div> <template id="cpn"> <div> <h2>组件</h2> <p>内容</p> <slot><p>默认值</p></slot> </div> </template>
<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const app=new Vue({ el: '#app', date: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }); </script>
|
具名插槽slot
多个插槽情况下,通过给子组件模板中的slot插槽写上name属性,做到分别控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <div id="app"> <cpn><span slot="left">标题</span></cpn> <cpn>aaa</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>
<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> const app=new Vue({ el: '#app', date: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }); </script>
|
编译作用域
根组件中的模板用到变量会去根组件找,子组件template模板使用到变量会去子组件找
作用域插槽
父组件替换插槽标签,但内容由子组件来提供
需求:
- 子组件中包括一组数据,比如:pLanguages:[‘JavaScript’,’Python’,’Swift’,’Go’,’C++’]
- 需要再多个界面进行展示:
- 某些界面是以水平方向展示
- 某些是列表形式
- 某些直接展现数组
- 内容在子组件,希望父组件告诉我们如何展示
模块化开发
JavaScript原始功能
网页开发早期,js制作作为一种脚本语言,做一些简单表单验证或动画
随着ajax异步请求出现,慢慢形成前后端分离
- 客户端需求越来越多,代码量与日俱增
- 为了应对代码量剧增,通常将代码组织在多个js文件
- 但这种维护方式,依然不能避免一些灾难性问题。
比如全局变量同名的问题
- js最初设定时只有两种作用域,导致变量很容易出现冲突,必须用闭包来进行函数封装,解决冲突问题。
- 引出了一个解决办法:闭包。匿名闭包能解决原因是创建了一个作用域,作用域里调用到不到作用域外的变量
模块化es5,es6有专门的模块化改进,使用一个对象作为模块出口
1 2 3 4 5 6 7 8
| var moduleA = (function(){ var obj = {} obj.flag=flag; obj.sum=sum; return obj; })()
|
通过模块对象引用模块的变量,解决变量冲突和复用问题
前端发展至今,已有很多人规定了前端模块化开发的规范及实现方案,我们只需要遵守规范,现在一般在别人规范好的模块化规范上进行开发
常见模块化规范:
- CommonJS(nodejs按此规范实现)、AMD、CMD、也有ES6的Modules
稍微了解CommoJS
模块化两个核心:导出和导入
CommonJS的导出:
1 2 3 4 5 6 7 8 9
| module.exports = { flag: true, test(a, b) { return a + b }, demo(a, b) { return a * b } }
|
CommonJS的导入
1 2 3 4 5 6 7 8
| let{test,demo,flag} = require('moduleA');
let _mA = require('mouduleA'); let test = _mA.test; let demo = _mA.demo; let flag = _mA.flag;
|
ES6模块化实现
导出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export { flag,sum }
export var num1=1000; export var height = 1.88;
export function mul(num1,num2){ return num1+num2 } export class Class{ run(){ console.log('run'); } }
const address = '北京'; export default address;
|
导入:
1 2 3 4 5 6 7 8 9 10 11
| import {flag} from "./aaa.js"; inport {Class} from "./aaa.js"; const p = new Class();
import addr from "./aaa.js";
import * as aaa from "./aaa.js";
console.log(aaa.flag);
|