前言
没有用脚手架构建项目的组件化开发,都是在html中导入vue.js,来进行开发的
(如下)
用一个html文件做组件化开发:
<body>
<div id="root">
<leftmenu></leftmenu> <!-- 使用组件标签 -->
</div>
</body>
<script src="./vue.js"></script>
<script type="text/javascript">
Vue.config.productionTip = false
// Vue.component('Aside',aside)
const leftmenu = Vue.extend({ // 一个组件
template:`<div>
<h2 style="border:1px solid black;width:10%">{{msg}}</h2>
</div>`,
data() {
return {
msg:'我是子组件',
}
},
})
new Vue({
el:'#root',
data:{
// father:'父组件'
},
components:{ //注册组件
leftmenu
}
})
</script>
效果:
通过npm下载vue的脚手架之后,用脚手架创建一个vue项目↓
在项目中进行组件化开发,这才是完整的Vue项目。
其中App.vue是主体,components文件夹中的School.vue和Student.vue都是组件。
其中assets文件夹是存放静态资源的。
其中main.js:
import Vue from 'vue'
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
new Vue({
render: h => h(App), //渲染解析项目中App.vue文件的内容
}).$mount('#app')
其中new Vue({…}).$mount(‘#app’) 相当于
public中 index.html 文件各行代码的含义
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- BASE_URL 是public的路径,↓配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题,找package.json中的"name": "vue_demo"作为网站标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 如果浏览器不支持js时,noscript标签中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器↓ -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
组件文件的命名规范:首字母大写,后面驼峰命名
ref、refs实现父子组件通信
在App.vue中的 <子组件标签 ref=“xxx”> …</子组件标签> 中加ref类似于一个id,在方法中可以通过this.$refs.xxx 来获取这个子组件的实例对象VueComponent (vc),
如果是加在 <普通标签 ref=“xxx”> … 中 ,则获取的是这个普通标签的DOM
this.$refs可以输出页面中所有加了ref 的标签的集合 ,是对象形式
例 :App.vue中
<template>
<div>
<button ref="btn"> 按钮</button>
<school ref="sch"></school>
<student ref="stu" ></student>
</div>
</template>
<script>
import school from "./components/School";
import student from "./components/Student";
export default {
components: {
school,
student
},
mounted(){
console.log(this.$refs);
},
};
</script>
只要获取了子组件的实例对象,就能实现父子组件间的通信等操作。
如下例:
父组件App.vue:
<template>
<div>
<school ref="sch"></school>
</div>
</template>
<script>
import school from "./components/School";
export default {
components: {
school
},
methods:{
// 自定义事件↓
mymethod(arg){
console.log(arg);
}
}
mounted(){
// 自定义事件shijian 在子组件school的$emit中能触发,触发时调用这边的mymethod方法
this.$refs.sch.$on('shijian',this.mymethod)
/* this.$refs.sch.$on('shijian', (arg) =>{ //同上,只是把mymethod写成回调函数
console.log(this);
}) */
},
};
</script>
子组件school.vue:
<template>
<div class="school" >
<h1 @click="showAddress">学校地址:{{address}}</h1>
</div>
</template>
<script>
export default {
name:"MySchool",
data(){
return{
address:'东莞'
}
},
methods: {
showAddress(){
this.$emit('shijian',this.address) //第一个参数对应父组件中设置好的$refs.ref名.$on,第二个参数是传递给父组件的实参
}
},
</script>
此时点击学校地址,子组件就会($ emit)调用父组件($on)注册好的’shijian’中的函数,打印内容:
此例子就实现了子组件的参数传给父组件的效果,即子传父。
另外,如果上面父组件中的this.mymethod 函数用回调函数的形式书写,普通函数里面的this 会指向子组件实例对象vc ,
如this.$refs.子组件.$on('shijian',function (arg) { console.log(this);})
这个this是子组件的实例对象 (VueComponent),
而如果这个函数用箭头函数的方式写,this则是指向父组件的实例对象。
this.$refs.子组件.$on('shijian',(arg) =>{ console.log(this); })
这个this是当前函数外层的实例对象(也就是父组件)
prop、props父传子
例子
父组件:App.vue
<template>
<div>
<student ref="stu" name="abc" sex="男" age=18></student>
</div>
</template>
。。。
子组件student.vue:
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
name:'MyStudent',
data(){
return{
// 这里变量的命名不能和props中的相同!!!
Myage:this.age // 可以得到props中的age,props先编译,在组件的实例对象vc中有了age了,然后可以用this.调用,赋值给本地data数据
}
},
// 接收父组件传过来的参数
//方式一 props:['name','age','sex'] 不限制数据类型的写法
/* 方式二 props:{ // 限制数据类型的写法
name:String,
age:Number,
sex:String
}*/
// 方式三 配置 (用的多)
props:{
name:{
type:String, //数据类型 字符串
required:true, // 是否必须要传过来有值;属性是否是必须的
},
age:{
type:Number, //数据类型 数值
default:18 // 默认值
},
sex:{
type:String, //数据类型 字符串
...
}
}
}
</script>
子组件接收了参数后不建议修改其值,会报错(修改 传过来的对象.xxx 不报错但不推荐)。
因为props是只读的,Vue底层会监测用户对props 的修改,如果进行了修改,就会发出警告,若业务需要修改值,建议复制props的内容到本地data中的数据,然后修改data中的数据即可。
如果子组件没有用props接收,那他的实例对象vc里面也会有接收,在this.$attrs中,虽然可以调用到,缺点是不能限制其类型,是否必须,默认值等。
子传父
方式一 父子共用函数,通过参数传值
父组件:
定义一个方法,需要参数,把这个方法传给子组件 <子组件标签 :方法名=“方法名” > </子组件标签>
App.vue:
<template>
<div class="total"><
<school :zichuanfu="zichuanfu" ></school> <!-- 函数传给子组件-->
</div>
</template>
<script>
import school from "./components/School";
export default {
components: {
school,
},
methods: {
zichuanfu(arg){
console.log("子传父传过来的参数:",arg);
},
</script>
子组件
props :[‘方法名’]接收这个方法,调用的时候传参 方法名( 子组件的某个要传给父组件的实参 )
School.vue:
<template>
<div>
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<button @click="zichuanfu(address)">子传父</button>
<!-- 点击时调用props传过来的函数zichuanfu,传入一个实参,就能把这个参数数据传给父组件了 -->
</div>
</template>
<script>
export default {
name:"MySchool",
data(){
return{
name:'DGCC',
address:'东莞'
}
},
props:["zichuanfu"] //注意点:双引号
}
</script>
回到父组件
方法名 ( 参数 ){ 这个参数就是从子组件传过来的变量 }
methods: {
zichuanfu(arg){
console.log("子传父传过来的参数:",arg);
},
//此函数被执行,接收子组件传过来的参数,打印这个参数
效果:
方式二 自定义事件
父组件中:
<子组件标签 v-on:自定义事件名=“触发函数名” />
v-on: 可以写成@,这就类似于@keyup,@click这些js自带的事件,只是这一次@后面的名称是我们自己定义的,所以叫自定义事件。
<student v-on:zidingyi="zidingyi" />
shijian后面加.once 则该自定义 事件只能触发一次
子组件中:
在某个函数中触发自定义事件, 方法中写 this.$emit.(‘自定义事件名’,参数) ,这样就会去触发自定义事件了
data(){
return{
age:18
}
},
methods:{
sendAge(){
this.$emit('zidingyi',this.age)} //'zidingyi'是自定义事件名,this.age是传过去的参数
}
父组件中:
在methods中写一个触发函数,接收形参,这个形参就是子组件传过来的值,由此实现了子传父
// 自定义事件触发的函数↓
zidingyi(args){ //可以有多个参数a,b,c,a,...params
console.log(args);
}
组件实例对象在销毁之后,全部的自定义事件将自动解绑失效,在父组件销毁后,子组件的自定义事件都失效;某一个子组件销毁,该子组件自身的自定义事件失效。
当然,也可以在不销毁组件实例对象的情况下,手动解绑自定义事件:
在 某个方法() { 。。。} 中解绑自定义事件,如
unbind() {
this.$off('zidingyi')
},
综上例子:
父组件App.vue:
<template>
<div>
<Test @zidingyi="zidingyi"></Test>
</div>
</template>
<script>
import Test from "./components/Test.vue";
export default {
components: {
Test,
},
methods: {
zidingyi(arg){
console.log("自定义事件zidingyi被调用,参数是:",arg);
},
}
</script>
子组件Test.vue:
<template>
<div>
<button @click="emitZidingyi" >触发自定义事件</button>
<button @click="unbind">手动解绑自定义事件</button>
</div>
</template>
<script>
export default {
name:"Test",
data(){
return{
testData:'test'
}
},
methods:{
emitZidingyi(){
this.$emit('zidingyi',this.testData)
},
unbind() {
this.$off('zidingyi')
},
}
}
</script>
效果:
解绑的写法有三个:
- this.$off(‘事件名’) 解绑一个自定义事件
- this.$off([‘事件1’,‘事件2’]) 解绑多个自定义事件
- this.$off() 解绑所有自定义事件
.native
在父组件文件中,给子组件标签加@click
,子组件默认会认为是自定义事件,此时,当你点击子组件的时候,是不会触发点击事件的,只有 @click.native
才能保持原样(也就是js的点击事件),而不被子组件解析成自定义事件。
小总结:
通过对子组件标签上绑定的自定义事件 和 给子组件标签上的ref,在this.$ refs.xxx.$on中绑定的事件,在子组件中,他们都是用this. $emit(‘事件名’,参数 ) 来调用的。
mixins混入
在main.js同级目录下,建一个mixin.js文件:默认暴露
export default {
methods: {
showName() {
console.log(this.name);
}
},
}
在子组件中使用:
<template>
<div class="school" >
<h1 @click="showName">学校名称:{{name}}</h1>
</div>
</template>
<script>
import hunru from '../mixin' ;
export default {
name:"MySchool",
data(){
return{
name:'DGCC',
}
},
mixins:[hunru]
// mixins:[hunru , 可以有多个 用逗号分开], // 注意 是mixins 有s!!!
}
</script>
上面例子,在子组件中看不到showName方法,但是点击时可以调用,因为导入的hunru来自mixin.js文件,mixin.js中写好了showName方法,此时在子组件中调用的是mixin.js中写好的方法。
但是,如果在子组件文件中也写了showName方法,则会覆盖mixin.js里面用的东西。
mixin.js中的变量也可以共同使用:
但是如果在mixin.js中写了 生命周期的函数,则不覆盖,两边都执行。
上面是局部混入,下面介绍全局混入,但是因为其有弊端,所以用的少。
在App.vue中写了全局混入,那其他子组件可以不用导入。默认会导入 引用
其他父子组件通信方式还有 全局事件总线,消息订阅与发布等