Vue 组件

组件是一个可复用的 Vue 实例且带有一个名字,所以要接收和 new Vue 相同的选项,例如 datamethodscomputed 以及生命钩子函数等。

注意:

  1. 组件是一个全新的 Vue 实例,组件内部的选项不会相互影响。但是 data: {} 除外,因为如果 data 是一个对象,就不会构成作用域,每个组件的 data 都是共用一个存储地址,会互相影响。这时需要使用 data() { return {} } 的形式来形成作用域。
  2. 组件必须只有一个根元素
  3. 由于 HTML 不区分大小写,会把所有字母解释为小写,所以组件名中不要使用驼峰命名,需要使用短横线分割命名(直接引用 vue.js 方式使用 Vue 会有此问题,使用脚手架没事,因为 webpack 会进行预处理)

全局注册组件

Vue.component('my-component', {
	data() {
		return {
			count = 0
		}
	},
	methods: {
		add() {
			this.count++
		}
	},
	template: '<button @click="add">点击了{{ count }}次</button>'
})

局部注册组件

let componentA = {
	data() {
		return {
			count = 0
		}
	},
	methods: {
		add() {
			this.count++
		}
	},
	template: '<button @click="add">点击了{{ count }}次</button>'
}

new Vue({
	el: '#app',
	components: {
		'my-component': componentA
	}
})
组件传值

父向子传值

使用子组件的 props 属性,prop 是可以在组件上自定义的一些 attribute ,当一个值传递给一个 prop 时,他就变成了组件实例上的一个 property ,我们可以通过 props 属性定义这些 prop ,可以像使用 data 中的 property 一样来使用 prop。

// 注册组件
Vue.component('myButton', {
    props: ['title'],
    data() {
        return {
            count: 0
        }
    },
    template: '<button @click=" count++ ">{{ title }}被点击了{{ count }}次</button>'
})

// 使用
<myButton title="按钮"></myButton>

HTML 中的 attribute 是不区分大小写的,因为浏览器会把所有的大写字符解释为小写,所以当我们通过驼峰命名法命名的 prop 在使用时,需要使用短横线分割命名:

// 注册组件
Vue.component('myButton', {
    props: ['postTitle'],
    data() {
        return {
            count: 0
        }
    },
    template: '<button @click=" count++ ">{{ postTitle }}被点击了{{ count }}次</button>'
})

// 使用
<myButton post-title="按钮"></myButton>

通常我们会使用字符串数组的形式罗列出 props

props: ['title', 'likes', 'author']

除此之外,我们还可以对 prop 进行验证

props: {
	// 验证数据类型
	title: String,
	// 多个类型可能
	likes: [Number, Boolean, Function],
	// 必填字符串
	author: {
		type: String,
		required: true
	},
	// 带有默认数字
	commentIds: {
		type: Number,
		deafult: 10
	},
	// 带有默认对象
	isPublished: {
		type: Object,
		// 对象或数组的默认值,必须从一个工厂函数中获取
		default() {
			return { message: 'Hello' }
		}
	},
	// 自定义验证函数
	callback: {
        validator(value) {
            // 这个值必须匹配下列字符串中的一个
            return ['success', 'warning', 'danger'].indexOf(value) !== -1
        }
    },
    contactsPromise: Promise // or any other constructor
}

在进行父子传值时,值得注意的是:父向子传值是一个单向数据流,即子组件中不能直接修改 prop 的值,这样做的目的是,防止修改值后会影响父组件的其他子组件的同名 prop 值。因为,每当父组件中的值发生变化时,子组件中的所有 prop 都会刷新为最新的值。

如果需要更改 prop 的值,推荐使用以下两种方法:

// 方法一:赋值为自己的属性
props: ['postTitle'],
data() {
    return {
        title: this.postTitle
    }
}

// 方法二:使用计算属性
props: ['postTitle'],
computed: {
    title() {
        return this.postTitle.trim()
    }
}

如果父组件传入了一个自定义 attribute ,但是子组件中的 props 中并没有声明,那么这个自定义的 attribute 和值将会自动放到子组件的根标签上。对于绝大多数的 HTML 定义的 attribute 来说,外部传入的值会替换掉子组件中原有的值,但是 classstyle 除外,他们会合子组件上的同名 attribute 进行合并。

子向父传值

通过 $emit() 在子组件中触发父组件中在子组件身上注册的事件,并传入数据

注意:$emit() 中的事件名不能含有大写字母,需要使用小写字母(直接引用 vue.js 方式使用 Vue 会有此问题,使用脚手架没事)

<!-- 父组件 -->
<template>
  <div>
    <p>父组件城市:{{ city }}</p>
    <button @click="changeCity">父组件按钮,点击切换至上海</button>

    <sonComponent @clickSon="clickSonBtn" :city="city"></sonComponent>
  </div>
</template>

<script>
import sonComponent from "@/components/son.vue";

export default {
  data() {
    return {
      city: "北京"
    };
  },
  methods: {
    changeCity() {
      this.city = "上海";
    },
    clickSonBtn(value) {
      console.log(value);
      this.city = value;
    }
  },
  components: {
    sonComponent
  }
};
</script>

<!-- 子组件 -->
<template>
  <div>
    <p>子组件城市:{{ city }}</p>
    <button @click="changeCity">子组件按钮,点击切换到广州</button>
  </div>
</template>

<script>
export default {
  props: ["city"],
  methods: {
    changeCity() {
      this.$emit("clickSon", "广州");
    }
  }
};
</script>
插槽

Vue 允许在组件的标签中间带入其他元素,但前提是要在组件中要定义 slot 插槽,如果组件中的 template 中没有一个 slot ,那么该组件的起始标签闭合标签中间的内容都会被舍弃。

<!-- 组件 my-link 的 template -->
<template>
	<a :href="url" class="nav-link">
		<slot></slot>
	</a>
</template>
<!-- 使用组件 -->
<my-link url="xxx">插槽内容</my-link>

编译时,【插槽内容】将会替换掉 slot 标签,此外,插槽内容和模板的其他地方一样,可以访问组件外部的 property ,但不能访问组件内部的作用域。因为,父级模板里的所有内容都是在父级作用域里编译的,子集模板里的内容都在子级作用域中编译。但是我们可以将组件内部的内容通过绑定 attribute 的方式传到父级,绑定到 slot 上的 attribute 被称为插槽 prop,在父级作用域中,可以通过带值的 v-slot 来定义包含所有 prop 的对象的名字。

<!-- 子组件 -->
<template>
	<div>
		<slot :sonName="name"></slot>
	</div>
</template>

<script>
export default {
	data() {
		name: '张三'
	}
}
</script>

<!-- 父组件 -->
<template>
	<div>
		<son-info>
			<template v-slot="sonInfo"> {{ sonInfo.sonName }} </template>  // 张三
		</son-info>
		<son-info> {{ name }} </son-info>  // 李四
	</div>
</template>

<script>
export default {
	data() {
		name: '李四'
	}
}
</script>

插槽中可以设置后备内容,即默认内容,例如:<slot> 默认内容 </slot> ,当组件起始标签和闭合标签之前没有值的时候,会展示后备内容

有时我们需要多个插槽,例如:

<div class="container">
    <header>
        <!-- 我们希望把页头放这里 -->
    </header>
    <main>
        <!-- 我们希望把主要内容放这里 -->
    </main>
    <footer>
        <!-- 我们希望把页脚放这里 -->
    </footer>
</div>

对于这种情况,我们需要给 slot 标签新增一个属性 name ,用来标记插槽的名字,没有 name 属性的 slot 有一个默认的名字 default,在使用插槽时,需要通过 v-slot 来指定要替换的插槽,缩写为 #

<!-- 子组件 -->
<div class="container">
    <header>
        <slot name="header"></slot>
    </header>
    <main>
        <slot></slot>
    </main>
    <footer>
        <slot name="footer"></slot>
    </footer>
</div>

<!-- 父组件 -->
<component>
    <template v-slot:header>
        <p>我来组成头部</p>
    </template>

    <p>我是main1</p>
    <p>我是main2</p>
  
    <template #footer>
        <p>我来组成尾部</p>
    </template>
</component>
Logo

前往低代码交流专区

更多推荐