3 vue组件
一般来说我们称为:html+css+js
写的小行代码是一个组件
1 组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
组件分为两种:
- 全局组件:可以在任意地方使用
- 局部组件:只在当前组件加载的时候才会加载局部组件
起步:局部组件
<div id="app">
<!-- 3.使用子组件-->
<App></App>
</div>
<script src="vue.js"></script>
<script>
// App组件 html+css+js
// 使用局部组件的打油诗:建子 挂子 用子
// 1.创建组件
const App = {
// 注意:在组件中这个data必须是一个函数
data(){
return {
msg: '我是App组件'
};
},
template: `
<div>
<h3>{{msg}}</h3>
<button @click="handleClick">按钮</button>
</div>
`,
methods: {
handleClick(){
this.msg = "学习局部组件";
}
},
}
new Vue({
el: '#app',
data: {},
components: {
// 2.挂载子组件
App,
}
})
</script>
起步:全局组件
<div id="app">
<!-- 3.使用子组件-->
<App></App>
</div>
<script src="../vue.js"></script>
<script>
// App组件 html+css+js
// 创建全局组件(第一个参数是组件名,第二个是配置)
// 只要创建全局组件,可以在任意地方使用
Vue.component('Vheader', {
template: `
<div>
我是导航组件
</div>
`
})
Vue.component('Vaside', {
template: `
<div>
我是侧边栏组件
</div>
`
})
const Vbtn = {
template: `
<button>按钮</button>
`
}
const Vcontent = {
data() {
return {}
},
template: `
<div>
我是内容组件
<Vbtn></Vbtn>
</div>
`,
components: {
Vbtn,
}
}
const App = {
// 注意:在组件中这个data必须是一个函数
data() {
return {};
},
components: {
Vcontent,
},
template: `
<div>
<Vheader></Vheader>
<div>
<Vaside/>
<Vcontent/>
</div>
</div>
`,
}
new Vue({
el: '#app',
data: {},
components: {
// 2.挂载子组件
App,
}
})
</script>
2 组件通信
1 父往子传数据
// 父传子数据(通过props):
// 1.在子组件中声明props接收在父组件挂载的属性
// 2.可以在子组件的template中任意使用
// 3.在父组件绑定自定义的属性
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
// 子组件
Vue.component('Child',{
template: `
<div>
<h3>我是一个子组件</h3>
<h4>{{childData}}</h4>
</div>
`,
props:['childData']
})
// 父组件
const App = {
data() {
return {
msg: '我是父组件传进来的值'
};
},
template: `
<div>
<Child :childData="msg"/>
</div>
`, // 传给子组件数据
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
2 子往父传数据
// 子往父传值
// 1.在父组件中子组件绑定自定义事件
// 2.在子组件中触发原生的事件,在事件函数通过this.$emit触发自定义事件
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
// 子组件
Vue.component('Child',{
template: `
<div>
<h3>我是一个子组件</h3>
<input type="text" @input="handleInput">
</div>
`,
methods:{
handleInput(e){
const val = e.target.value;
this.$emit('inputHandler',val);
}
}
})
// 父组件
const App = {
data() {
return {
newVal: '',
};
},
methods: {
input(newVal){
this.newVal = newVal;
}
},
template: `
<div>
<div class="father">数据:{{newVal}}</div>
<Child @inputHandler="input"/>
</div>
`,
}
new Vue({
el: '#app',
data: {},
components: {
// 2.挂载子组件
App,
}
})
</script>
3 平行事件
1.中央事件总线bus
:
两个子组件同指向一个父组件,那么两个子组件之间怎么通信呢?,
这个就用到了bus
const bus = new Vue();
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
const bus = new Vue();
// 中央事件总线 bus
// 组件中的this指向的是当前的组件
Vue.component('B',{
data(){
return {count: 0}
},
template:`
<div>{{count}}</div>
`,
// created当事件被创建出来时,就给于调用
created() {
// $on 绑定一个事件(指向当前的对象)
bus.$on('add',(n)=>{
this.count+=n;
})
}
})
Vue.component('A',{
data(){
return {}
},
template:`
<div>
<button @click="handleClick">加入购物车</button>
</div>
`,
methods: {
handleClick(){
// 触发绑定的函数 $emit 触发一个事件
bus.$emit('add',1);
}
}
})
const App = {
data(){
return {}
},
template: `
<div>
<A></A>
<B></B>
</div>
`
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
2.provide
和inject
父组件provide来提供变量,然后在子组件中通过inject来注入变量,无论组件嵌套多深,都可以取到值。
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
Vue.component('B',{
data(){
return {count: 0}
},
template:`
<div>{{msg}}</div>
`,
// 接收数据
inject:['msg'],
created() {
console.log(this.msg)
}
})
Vue.component('A',{
data(){
return {}
},
created() {
// 获取父组件的数据
console.log(this.$parent.title)
// 获取子组件的数据(可加[]因为可能不止一个子组件)
console.log(this.$children)
// 可查看所有的$值
console.log(this)
},
template:`
<div>
<B></B>
</div>
`,
})
const App = {
data(){
return {title:'老爹'}
},
template: `
<div>
<A></A>
</div>
`,
// 注入数据
provide(){
return {
msg: '老爹的数据'
}
}
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
3 插槽
1 匿名插槽
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
Vue.component('MBtn',{
template: `
<button>
<slot></slot>
</button>
`
})
const App = {
data() {
return {title: '老爹'}
},
template: `
<div>
<MBtn><a href="">登录</a></MBtn>
<MBtn>注册</MBtn>
</div>
`,
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
2 具名插槽
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
// 只要匹配到slot标签中的值 template中的内容就会被插入到这个槽中
Vue.component('MBtn',{
template: `
<button>
<slot name="login"></slot>
<slot name="submit"></slot>
</button>
`
})
const App = {
data() {
return {title: '老爹'}
},
template: `
<div>
<MBtn>
<template slot="login">
<a href="">登录</a>
</template>
</MBtn>
<MBtn>
<template slot="submit">
提交
</template>
</MBtn>
</div>
`,
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
3 作用域插槽
需求假设:已经开发了一个代办事项列表的组件,很多模块都在用
1.之前的数据格式和引用接口不变,正常显示
2.新功能模块增加对勾
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
const todoList = {
data(){
return {}
},
props: {
todos: Array,
defaultValue: [],
},
template: `
<ul>
<li v-for="item in todos" :key="item.id">
<slot :itemValue="item"></slot>
{{item.title}}
</li>
</ul>
`
}
const App = {
data() {
return {
todoList: [{
title: '大哥你好吗',
isComplate: true,
id: 1
},
{
title: '小弟我还行',
isComplate: false,
id: 2
},
{
title: '你在干什么',
isComplate: false,
id: 3
},
{
title: '抽烟喝酒烫头',
isComplate: true,
id: 4
}]
}
},
components: {
todoList,
},
template: `
<todoList :todos="todoList">
<template v-slot="data">
<input type="checkbox" v-model="data.itemValue.isComplate">
</template>
</todoList>
`
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
4 生命周期钩子
所有生命周期钩子的 this
上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()
)。因为箭头函数绑定了父级上下文,所以 this
不会指向预期的组件实例,并且this.fetchTodos
将会是 undefined。
生命周期下的方法:
beforeCreate # 在组件创建之前
created # 在组件创建之后
beforeMount # 在挂载之前(装载数据在DOM之前)
mounted # 在挂载之后(装载数据在DOM之后)
beforeUpdate # 在更新页面之前
updated # 在更新页面之后
activated # 借助keep-alive组件 激活(标签的创建销毁)
deactivated # 借助keep-alive组件 停用(标签的创建销毁)
beforeDestroy # 销毁之前(标签的创建销毁)
destroyed # 销毁之后(标签的创建销毁)
errorCaptured
示例:
<style>
.active{color: red}
</style>
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
Vue.component('Test',{
data(){
return {msg: '大帅逼'}
},
methods: {
handleClick(){
this.msg="alex";
this.isRed=true;
}
},
template: `
<div>
<button @click="handleClick">改变</button>
<h3 :class="{active: isRed}">{{msg}}</h3>
</div>
`,
beforeCreate() {
console.log('组件创建之前',this.$data);
},
created() {
// 非常重要的事情,在此时发送ajax 请求后端数据
console.log('组件创建之前',this.$data);
},
beforeMount() {
// 即将挂载,还没有挂载
console.log('组件创建之前',document.getElementById('app'));
},
mounted() {
// 挂载完成 也可发送ajax
console.log('DOM挂载完成',document.getElementById('app'));
},
beforeUpdate() {
// 获取页面更新之前的DOM
console.log('更新之前的DOM',document.getElementById('app').innerHTML);
},
updated() {
// 获取页面最新的DOM
console.log('更新之后的DOM',document.getElementById('app').innerHTML);
},
beforeDestroy() {
// 销毁之前方法
console.log('销毁之前')
},
destroyed() {
console.log('销毁完成')
},
activated() {
console.log('组件被激活了')
},
deactivated() {
console.log('组件被禁用了')
}
})
const App = {
data(){
return {isShow: true}
},
components: {},
methods: {
clickHandle(){
this.isShow = !this.isShow;
}
},
template: `
<div>
<keep-alive>
<Test v-if="isShow"></Test>
</keep-alive>
<button @click="clickHandle">改变生死</button>
</div>
`
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
5 异步组件加载
把相应的js
文件,引入到Vue
组件中。
Test.js
文件:
export default {
data() {
return {msg: '大帅逼'}
},
template: `
<h3>{{msg}}</h3>
`
}
html
文件:
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script type="module">
const App = {
data(){
return {isShow: false}
},
methods: {
asyncLoad(){
this.isShow = !this.isShow;
}
},
components: {
// 加载异步组件(需要一个函数加载)
Test: ()=>import('./Test.js')
},
template: `
<div>
<button @click="asyncLoad">异步加载</button>
<Test v-if="isShow"></Test>
</div>
`
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
6 refs的使用
获取DOM节点的操作。
<div id="app">
<App></App>
</div>
<script src="../vue.js"></script>
<script>
Vue.component('Test',{
data(){
return {msg: '大帅逼'}
},
template: `
<div>
<h3>{{msg}}</h3>
</div>
`
})
const App ={
data(){
return {}
},
mounted(){
// 1.如果给标签加ref,获取的就是真实的DOM节点
console.log(this.$refs.btn);
// 2.如果给子组件添加ref,获取的是当前子组件对象
console.log(this.$refs.test);
// 加载页面自动获取焦点
this.$refs.input.focus();
},
components: {},
template: `
<div>
<Test ref="test"></Test>
<input type="text" ref="input">
<button ref="btn">改变</button>
</div>
`
}
new Vue({
el: '#app',
data: {},
components: {
App,
}
})
</script>
补充:
$refs 获取组件内的元素
$parent 获取当前组件的父组件
$children 获取当前组件的子组件
$root 获取New Vue的实例化对象
$el 获取组件对象的DOM元素
7 nextTick
的使用
获取更新之后的DOM添加事件的特殊情况
$nextTick 是在下次DOM更新循环结束之后执行的延迟回调,在修改数据之后使用该方法,则可以在回调中获取更新之后的DOM
-
简单的使用:
<div id="app"> <h3>{{message}}</h3> </div> <script src="../vue.js"></script> <script> const vm = new Vue({ el: '#app', data: {message: '大帅逼'} }) vm.message = 'new Message'; // 当vm中的数据被重新赋值时,vm实例不会重新渲染 // console.log(vm.$el.textContent); // 通过nextTick可获取最终渲染后的数据(为了数据变化之后等待vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick在当前的回调函数中能获取最新的DOM) Vue.nextTick(()=>{ console.log(vm.$el.textContent); }) </script>
<div id="app"> </div> <script src="../vue.js"></script> <script> const App = { data(){ return{ isShow: false, } }, template:` <div> <input type='text' v-if='isShow' ref='fos'/> </div> `, mounted(){ this.isShow = true; console.log(this.$refs.fos); // focus() this.$nextTick(function(){ this.$refs.fos.focus(); }) } } new Vue({ el: '#app', data(){return{}}, template:`<App/>`, components:{App,} }) </script>
-
nextTick
的应用:需求: 在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的, 然后我在接口一返回数据就展示了这个浮层组件,展示的同时, 上报一些数据给后台(这些数据是父组件从接口拿的) 这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候, 这些数据还未更新到组件上去,上报失败
<div id="app"> <App></App> </div> <script src="../vue.js"></script> <script> const Pop = { data(){ return {isShow: false} }, props: { name: { type: String, default: '' } }, template: ` <div v-if="isShow"> {{name}} </div> `, methods: { show(){ this.isShow = true; // 弹窗组件展示 console.log(this.name); } } } const App = { data(){ return {name: ''} }, created(){ // 假设从后端获取的数据 setTimeout(()=>{ // 数据更新 this.name = '大帅逼'; this.$nextTick(()=>{ this.$refs.pop.show(); }) }, 1000) }, components: {Pop,}, template: `<pop ref="pop" :name="name"></pop>` } const vm = new Vue({ el: '#app', components: {App,} }) </script>
8 对象变更检测注意事项
由于Vue
不能检测对象属性的添加和删除,解决方法:
<div id="app">
<h3>{{user.name}},{{user.age}},{{user.phone}}</h3>
<button @click="handlerAdd">添加属性</button>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
user: {}
},
methods:{
handlerAdd(){
// Vue.set(object,key,value)添加响应式属性
// this.user.age = 20; // 错误添加
// this.$set(this.user,'age',20); // 添加一次
this.user = Object.assign({},this.user,{
age: 20,
phone: 12306
}) // 添加多个响应式属性
}
},
created(){
// 假设从后端获取的数据
setTimeout(()=>{
this.user = {name: "张三"}
},1250)
}
})
</script>
9 mixin
混入技术
mixin
主要是来分发Vue
组件中的可复用功能
<div id="app">
{{msg}}
</div>
<script src="../vue.js"></script>
<script>
// mixin主要是来分发Vue组件中的可复用功能
const MyMixin = {
data(){
return {msg: '123'}
},
created(){
this.sayHello();
},
methods: {
sayHello(){
console.log('hello mixin');
}
}
}
new Vue({
el: '#app',
data: {title: '大帅逼'},
created() {console.log(111);},
// 将MyMixin中的方法和属性混入到Vue实例中
mixins: [MyMixin],
})
</script>
-
应用:
假设:有模态框组件,提示框组件。他们看起来不一样,用法不一样,但是逻辑是一样的(切换
bool
值)
<div id="app">
</div>
<script src="../vue.js"></script>
<script>
/*
全局mixin的创建:使用时要小心,因为每个组件实例创建,它都谁被调用
Vue.mixin({...})
*/
// 局部的mixin
const toggleShow = {
data(){
return {isShow: false}
},
methods: {
toggleShow(){
this.isShow = !this.isShow;
}
}
}
// 模态框组件
const Model = {
template: `
<div v-if="isShow"><h3>模态框组件</h3></div>
`,
// 局部的mixin
mixin: [toggleShow],
}
// 提示框组件
const ToolTip = {
template: `
<div v-if="isShow"><h2>提示框组件</h2></div>
`,
// 局部的mixin
mixin: [toggleShow],
}
new Vue({
el: '#app',
template: `
<div>
<button @click="handlerModel">模态框</button>
<button @click="handlerToolTip">提示框</button>
<Model ref="model"></Model>
<ToolTip ref="toolTip"></ToolTip>
</div>
`,
components: {Model,ToolTip},
methods: {
handlerModel(){
this.$refs.model.toggleShow();
},
handlerToolTip(){
this.$refs.toolTip.toggleShow();
}
}
})
</script>
1 组件分类
-
通用组件
基础组件,大部分U|都是这种组件,比如表单布局弹窗等
-
业务组件
与需求挂钩,会被复用,比如抽奖,摇一摇等 -
页面组件
每个页面都是一个组件,不会复用
2 使用第三方组件
2-1 element-ui
官方网站:
https://element.eleme.cn/#/zh-CN/component/installation
比如vue
最流行的element,就是典型的通用组件,执行npm install element-ui
安装
- 完整导入
// 在mian.js中
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
-
按需导入
借助
babel-plugin-component
,我们可以只引入需要的组件,以达到减小项目体积的目的。首先,安装
babel-plugin-component:
npm install babel-plugin-component -D
然后,将
.babelrc
修改为:{ "presets": [["es2015",{"modules": false}]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
接下来,如果你只希望引入部分组件,比如Button和Select,那么需要在
main.js
中写入以下内容:import Vue from 'vue'; import {Button,Select} from 'element-ui'; import App from './App.vre'; Vue.component(Button.name, Button); Vue.component(Button.name, Select); /* 或者写 Vue.use(Button) Vue.use(Select) */ new Vue({ el: '#app', render: h=>h(App) });
-
在
vue-cli
中可以使用vue add element
安装安装之前注意提前提交当前工作内容,脚手架会覆盖若干文件
发现项目发生了变化,打开
App.vue
,ctrl+z
撤回 -
方式2
npm install element-ui 手动创建文件和配置
在终端输入,可快速生成相应文件 vue add element
1 Fully import :完全引入 Import on demand :按需引入
2 是否会写css的变量:No
3 开发语言:zh-CN
2-2 axios
3 自定义组件
-
FormElement.vue
<template> <div> <h3>element表单</h3> <!-- 自己的组件 --> {{ruleForm}} <m-form-item label="用户名" prop="name"> <m-input v-model="ruleForm.name"></m-input> </m-form-item> <m-form-item label="密码" prop="pwd"> <m-input type="password" v-model="ruleForm.pwd"></m-input> </m-form-item> </div> </template> <script> import MInput from './Input.vue' import MFormItem from './FormItem.vue' export default { name: "FormElement", data(){ return{ ruleForm: { name: '', pwd: '', }, } }, components: { MInput, MFormItem } } </script> <style scoped> </style>
-
input
组件<template> <div> <input :type="type" :value="inputVal" @input="handleInput"> </div> </template> <script> export default { name: 'InputData', props: { type: { type: String, default: 'text' }, value: { type: String, default: '', } }, data() { return { inputVal: this.value } }, methods: { handleInput(e){ // 赋值 // 实现双向数据绑定 this.inputVal = e.target.value; this.$emit('input', this.inputVal); // 通知父组件值的更新 } }, } </script> <style> </style>
-
FormItem
组件<template> <div> <label v-if="label">{{label}}</label> <!-- 槽的作用:会把子组件插入到slot标签中 --> <slot></slot> <!-- 显示错误的校验信息 --> <p v-if="valdateStatus==='error'" class="error">{{errorMessage}}</p> </div> </template> <script> // 0.label和prop实行绑定 // 1.获取当前输入框的规则 // 2.如果输入框rule不匹配显示错误信息 // 3.Input组件中涌入输入内容时,通知FormItem做校验 // 4.使用async-validator做校验 export default { data() { return { valdateStatus: '', // 校验的状态 errorMessage: '', // 显示错误信息 } }, props: { label: { type: String, default: '' }, prop: { type: String, default: '' } } } </script> <style> .error{ color: red; } </style>
-
From
组件<template> <div> <slot></slot> </div> </template> <script> export default { name: "FormData", provide(){ return { form: this } }, props: { model: { type: Object, required: true }, rules: { type: Object } } } </script> <style> </style>