前端:Vue学习-2
- 1. vue的生命周期
- 2. 工程化开发和脚手架Vue CLI
- 2.1 组件化开发
- 2.2 scoped解决样式冲突
- 2.3 data是一个函数
- 2.4 组件通信
- 2.5 非父子通信- event bus事件,provide&inject
- 3.v-model原理->实现父子组件双向绑定
- 4. sync 修饰符->实现父子组件双向绑定
- 5. ref 和 $refs
- 6. Vue异步更新,$nextTick
1. vue的生命周期
Vue的生命周期,一个Vue实例从创建到销毁的整个过程。
生命周期的四个阶段为:创建、挂载、更新、销毁
- 创建阶段,把数据转化为响应式数据;
- 挂载阶段,渲染模板;
- 更新阶段,数据修改,更新视图;
- 销毁阶段
Vue生命周期函数(钩子函数)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue2</title>
<script src="../vue2.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model.number="count">
<h4>{{count}}</h4>
</div>
<script>
const app = new Vue({
el:'#app',
data:{
count:100
},
beforeCreate(){
console.log('before Create',this.count);
},
created(){
console.log('created',this.count);
},
// 创建阶段
beforeMount(){
console.log('beforeMount',document.querySelector('h4').innerText);
},
mounted(){
console.log('mounted', document.querySelector('h4').innerText);
},
// 挂载阶段
beforeUpdate(){
console.log('beforeUpdate', document.querySelector('h4').innerText);
},
updated(){
console.log('updated', document.querySelector('h4').innerText);
},
// 更新阶段
beforeDestroy(){
console.log('beforeDestory');
console.log('清除一些vue以外的资源占用,比如定时器等');
},
destroyed(){
console.log('destoryed');
}
//销毁阶段
})
</script>
</body>
</html>
2. 工程化开发和脚手架Vue CLI
安装脚手架的命令,前提是本地电脑上已经安装nodejs,安装命令为:
npm install @vue/cli -g
// 全局安装
vue --version
// 查看安装的版本
创建vue项目为:
vue create 项目名
启动项目命令为:
// 首先需要cd到当前项目目录下
cd 项目名
npm run serve // 启动项目
这个项目文件结构为:node_modules-第三方包目录,public-放html文件的目录,src-源代码目录,package.json-项目配置文件,jsconfig.json-js配置文件。。。src有个main.js文件,为项目的核心文件
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// vue实例化
new Vue({
// render: h => h(App),
render:(createElement)=>{
return createElement(App)
}
}).$mount('#app')
2.1 组件化开发
一个页面可以拆分为一个个组件,每个组件可以拥有自己独立的结构、样式、行为,结构下有且只能有一个根元素。使样式支持less语法,需要安装less,npm命令为:
npm install less-loader
组件注册:局部注册、全局注册
局部注册,在components目录下新建“.vue”文件,文件命名采用大驼峰命名。然后在这个新建的vue文件下输入组件的三个组成部分,分别为结构、样式和行为。在App.vue根组件引入这个组件,并进行组测,然后就可以在根组件中使用这个组件了。
<script>
import Com from './components/Com.vue'
export default {
name: 'App',
components: {
Com:Com
}
}
</script>
全局注册,在main.js中进行全局注册,==所有组件内均可以使用该组件 ==。
import Com from './components/Com.vue'
Vue.component('Com',Com)
// 进行全局注册,对应的组件名,组件对象
2.2 scoped解决样式冲突
写在组件中的样式会全局生效,因此会造成多个组件之间的样式冲突。可以给组件的样式标签添加scoped属性,可以让样式只作用于当前组件。
加上scoped属性。
<style scoped>
.com-1{
width: 200px;
height: 40px;
background-color: red;
}
</style>
实现原理:加上scoped之后,不同组件的标签元素上会自动添加上自定义属性“data-一个hash值”。
2.3 data是一个函数
一个组件的data选项必须使一个函数,可以保证每个组件实例,维护独立的一份数据对象。 每次创建新的组件实例,都会重新执行一次data函数,得到一个新对象。
Com组件:
<template>
<div class="com-1">
<button @click="fn(-1)">-1</button>
{{count}}
<button @click="fn(1)">+1</button>
</div>
</template>
<script>
export default {
name:'Com',
data(){
return{
count:1
}
},
methods:{
fn(p){
this.count += p;
}
}
}
</script>
<style scoped>
</style>
<template>
<div id="app">
<Com></Com>
<br>
<Com></Com>
<br>
<Com></Com>
</div>
</template>
2.4 组件通信
父、子组件通信,父组件通过props将数据传递给子组件。在父组件中给子组件添加动态属性,属性值为父组件中的数据。在子组件中使用props来接收父组件传递来的数据。如果在子组件中想要修改这个父组件中的数据,可以通过this.$emit(‘父组件中传递的动态函数名’,要修改的值)
父组件:
<template>
<div id="app">
<p>{{title}}</p>
<Com :count1="count" @changeCount="fn" @changeTitle="fn1"></Com>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
count:1,
title:'hello world!'
}
},
methods:{
fn(){
this.count ++;
},
fn1(newTitle){
this.title = newTitle;
}
}
}
</script>
<style>
</style>
子组件:
<template>
<div class="com-1">
<p>{{count1}}</p>
<button @click="fn1">修改</button><br>
<button @click="fn2">修改名称</button>
</div>
</template>
<script>
export default {
name:'Com',
props:['count1'],
methods:{
fn1(){
this.$emit('changeCount');
},
fn2(){
this.$emit('changeTitle','hello Vue!');
}
}
}
</script>
<style>
</style>
可以传递任意数量的prop,传递任意类型的prop。
props校验
校验有类型校验、非空校验、默认值、自定义校验
类型校验如下:
props:{
校验的属性名:类型
}
// 类型可以为Number、String、Boolean、
完整的校验为:
props:{
校验的属性名:{
type:类型,
required:true, // 是否必填
default:默认值,
validator(value){
// 逻辑
return 是否通过校验
// return true或false
}
}
}
prop和data的区别
共同点:都可以给组件提供数据
区别:data的数据是组件本身的,可以随便改;prop的数据是外部的,不能直接改,要遵循单向数据流。
2.5 非父子通信- event bus事件,provide&inject
event bus事件
适用于兄弟组件。新建工具js文件,js文件代码如下:
import Vue from 'vue'
const bus = new Vue();
export default bus
在BaseA组件接收方代码如下:
<template>
<div>
我是BaseA组件
<p>{{count}}</p>
</div>
</template>
<script>
import bus from '../utils/bus'
export default {
name:'BaseA',
data(){
return {
count:1
}
},
created(){
bus.$on('changeCount',(count)=>{
this.count = count;
})
}
}
</script>
<style scoped>
div{
width: 200px;
height: 100px;
border: 1px solid black;
}
</style>
BaseB组件发送方代码如下:
<template>
<div>
我是组件BaseB <br>
<button @click="fn">修改组件BaseA中的值</button>
</div>
</template>
<script>
import bus from '../utils/bus'
export default {
name:'BaseB',
methods:{
fn(){
bus.$emit('changeCount',12);
}
}
}
</script>
<style scoped>
div{
width: 200px;
height: 100px;
border: 1px solid black;
}
</style>
运行结果:
在组件接收方中通过bus.$on对事件进行监听,组件发送方通过bus.$emit使组件接收方中事件进行触发。这是一个一对多的关系,即一个发送方组件,可以有多个接收方组件进行监听。
provide&inject
跨层级共享数据,适用于祖孙组件,在祖先组件中使用provide进行数据共享,在子或孙组件中使用inject进行接收。需要注意的是如果数据是简单类型,那么为非响应式的;如果数据是复杂类型,那么为响应式的。
祖先组件代码如下:
import BaseS from './components/BaseS.vue'
export default {
name: 'App',
data(){
return{
color:'red',
person:{
name:'liuze'
}
}
},
components:{
BaseS
},
provide(){
return{
person:this.person,
color:this.color
}
},
methods:{
fn(){
this.color = 'blue';
this.person.name = 'lz';
}
}
}
孙子组件代码如下:
<template>
<div class="son-s">
孙子组件
<p>
{{color}}
{{person.name}}
</p>
</div>
</template>
<script>
export default {
name:'BaseSS',
inject:[
'color','person'
]
}
</script>
<style scoped>
.son-s{
border: 1px solid black;
width: 100px;
height: 100px;
}
</style>
运行结果:
3.v-model原理->实现父子组件双向绑定
v-model本身是一个语法糖,应用在输入框上,就是value属性和input事件的合写。
数据变,视图跟着变“:value”;视图变,数据跟着变“@input”。
<input type="text" :value="val" @input="val = $event.target.value">
父子组件不能直接使用v-model的,因为v-model是双向数据绑定,而子组件是无法直接修改父组件中的数据的。如果要使用v-model,那么在子组件中对父组件中数据进行修改就必须使用“:value”及“input”,即 props:{value}和this.$emit(‘input’,更新的数据)。
具体例子为:v-model原理:父组件、子组件使用v-model
4. sync 修饰符->实现父子组件双向绑定
可以实现子组件与父组件数据的双向绑定,简化代码。prop属性名,可以自定义,非固定为value。使用场景,封装弹框类的基础组件。本质上就是 :属性名和@update:属性名 合写;如下的弹框父子组件如下:
<template>
<div class="diag" v-show="visible">
<button @click="fn">X</button>
</div>
</template>
<script>
export default {
props:{
visible:Boolean
},
methods:{
fn(){
this.$emit('update:visible',false);
}
}
}
</script>
<style>
.diag{
width: 200px;
height: 100px;
border: 1px solid black;
background-color: red;
position: relative;
}
.diag button{
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
}
</style>
<template>
<div id="app">
<button @click="fn">取消修改</button>
<Diag :visible.sync="isShow" ></Diag>
</div>
</template>
<script>
import Diag from './components/Diag.vue'
export default {
name: 'App',
components:{
Diag
},
data(){
return{
isShow:false
}
},
methods:{
fn(){
this.isShow = true;
}
}
}
</script>
<style scoped>
</style>
运行结果:
5. ref 和 $refs
利用ref和$refs可以用于获取dom元素或组件实例,查找范围为当前组件内。(如果dom进行查找,当其他组件和当前组件有同一个类名时,并且其他组件在当前组件前面进行渲染,那么此时就会出现dom找到的标签元素为其他组件内的。)
- 获取dom,在目标标签上添加ref属性
- 适当时机,通过this.$refs.xxx,获取目标标签
<div ref="div_1">
</div>
this.$refs.div_1
// 获取上述这个div标签
<template>
<div id="app">
<div ref="div_1">哈哈</div>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
isShow:false
}
},
methods:{
fn(){
this.isShow = true;
}
},
mounted(){
console.log(this.$refs.div_1);
}
}
</script>
<style scoped>
</style>
运行结果:
如果要获取组件实例,直接在组件实例的标签上添加属性ref,之后就可以通过this.$refs.xxx获取这个组件,返回的是一个Vue组件对象,通过==this.$refs.xxx.组件方法()==调用这个组件下的方法。
6. Vue异步更新,$nextTick
下述是异步更新实例,因为Vue是异步更新的,所有点击编辑按钮之后,输入框并不会出现聚焦,所有需要使用到$nextTick
<template>
<div id="app">
<div v-if="isShow">
<span>{{v}}</span>
<button @click="edit">编辑</button>
</div>
<div v-else>
<input type="text" v-model="v" ref="inp">
<button @click="save">保存</button>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
isShow:true,
v:'北京'
}
},
methods:{
edit(){
this.isShow = false;
this.$nextTick(()=>{
this.$refs.inp.focus();
});
},
save(){
this.isShow = true;
}
}
}
</script>
<style scoped>
#app{
width: 100px;
height: 100px;
margin: 20px auto;
}
</style>
运行结果: