Vue核心知识点 - vue2 基础2:双向绑定、组件基础

重点说明:当前笔记内容侧重点为各个知识点的应用及需要注意的地方,而不是每个知识点的概念。概念相关请查看官网:https://cn.vuejs.org/v2/guide/installation.html vue入门教程。
代码及xmind源文件Gitee:https://gitee.com/zhiyaoyun/vue-project
代码下载地址:https://gitee.com/zhiyaoyun/vue-project.git

一、计算属性和侦听器

1 - 计算属性

  • 目的
    处理模板中需要复杂逻辑计算的属性,简化模板中的表达式。

  • 优点
    a. 减少模板中的计算逻辑
    b. 能够进行数据缓存,提高性能【和普通函数相比】
    c. 响应式数据

  • 使用

<h2>计算属性h2>
<p v-if="show">{{fullName}}p>
<h2>函数获取h2>
<p v-if="show">{{getFullName()}}p>

<script>
  el: "#app",
    data: {
      firstName: 'yun',
      lastName: 'Zhiyao',
      show: true
    },
    computed: {
      fullName() {
        console.log("计算属性")
        return this.firstName + this.lastName;
      }
    },
    methods: {
      getFullName() {
         console.log("函数获取")
        return this.firstName + this.lastName;
      }
    }
  })
script>

Vue核心知识点 - vue2 基础2:双向绑定、组件基础_第1张图片

2 - 侦听器

  • 目的: 执行一些复杂的逻辑,包括异步或者开销比较大的计算

  • 和计算属性相比:
    a. 更加的灵活、通用
    b. 计算属性执行不了的逻辑可以在侦听器中进行执行,反之则不可

  • 使用

<h2>侦听器h2>
<p>当前年龄:<input type="text" v-model="age">p>
<p>应该做的事情:{{doSomeThing}}p>
<p>应该做的事情:{{doSomeThing2}}p>

<script>
  let vm = new Vue({
    el: "#app",
    data: {
      age: 0,
      doSomeThing: '',
    },
    computed: {
      doSomeThing2(){
        // 无效,不可以在计算属性中使用异步操作
        setTimeout(()=>{
          return this.age + '岁的事情'
        },1000)
      }
    },
    watch: {
      age(newAge){
        setTimeout(()=>{
          this.doSomeThing = newAge + '岁的事情'
        },1000)
      }
    }
  })
script>

3 - 两者区别

  • 一般来说:计算属性可以做的事情,watch都可以做。反之则不行。
  • 根据业务需求,判断是需要计算属性还是watch。大多数情况下,能使用计算属性则使用计算属性。

二、双向绑定

1 - 概念

  • 什么是双向绑定:当数据发生变化的时候,视图也会随之修改。当视图放生改变的时候,数据也会修改
  • 主要应用的元素:input / textarea / select / 自定义组件

2 - v-model指令

  • 实现原理:为不同的元素监听的相对应的事件
    a. input / textarea : value属性 + input事件
    b. checkbox / radio:checked属性 + change 事件
    c. select :value属性 + change事件

  • 应用

<h1>使用v-modelh1>
<h3>message:{{message}}h3>
<input type="text" v-model="message">

<h1>v-model的实现原理h1>
<h3>message2:{{message2}} ; checkbox:{{checkbox}}h3>
<input type="text" :value="message2" @input="handleInput($event)">
<input type="checkbox"  @change="handleChange($event)">

<script>
 const vm = new Vue({
    el: "#app",
    data: {
      message: "你好",
      message2:'hello',
      checkbox:true,
    },
    methods:{
      handleInput(event){
        this.message2 = event.target.value
      },
      handleChange(event){
        console.log(event)
        this.checkbox = event.target.checked
      }
    }
  })
script>
  • 修饰符:.lazy / .number / .trim
<h1>修饰符h1>

<input type="text" v-model.lazy="message">

<input type="text" v-model.number="numberData">

<input type="text" v-model.trim="message">

3 - 自定义组件双向绑定

  • 自定义组件双向绑定的两个要素:value 属性值 / checked 属性值 + input 事件 / change事件
<p>{{total}}p>


<my-component v-model="total">my-component>

<my-component @input="handleInputCom">my-component>

Vue.component('my-component', {
    template: `<button @click="handleClick">+1button>`,
    data() {
      return {
        count: 0,
      }
    },
    methods: {
      handleClick() {
        this.count++;
        this.$emit("input", this.count)
      }
    }
})
  • 自定义输入组件
<h1>自定义组件使用-model2h1>
{{checkbox}}
<base-checkbox v-model="checkbox">base-checkbox>

Vue.component('base-checkbox', {
    model: {
      prop: 'checked',
      event: 'change'
    },
    props: {
      checked: Boolean
    },
    template: `
      <input
          type="checkbox"
          :checked="checked"
          @change="$emit('change', $event.target.checked)"
      >`
})

三、组件基础

组件在Vue中是一个非常重要的基础,主要有以下几点注意:

  • 组件就是一个可复用的Vue实例
  • 声明组件时,接受的配置项和根实例基本一致,除实例化根元素el之外
  • 在组件中,data属性必须是一个函数,返回一个全新的对象,避免各个复用组件之间数据相互影响。

1 - 注册组件

注册组件时,需要注意组件的命名方式,组件的命名有两种方式:

  • 短横线命名法:在使用时只能使用短横线的命名。
  • 首字母大写命名法:在使用组件时既可以使用短横线的命名,也可以使用首字符大写的命名。

(1)全局注册

  • 全局注册的组件名称具有唯一性,不可重复
  • 子组件在其他子组件中也可以直接调用
Vue.component('组件名称',{组件配置项})

(2)局部注册

  • 局部注册的子组件不能在其他子组件中直接使用,而是需要导入后再使用。
  • 局部注册的组件名称可以重复
  • 局部注册的方式:单文件组件 / let myComponent = {配置项}
  • 在模块中使用局部注册的组件时需要 import 导入到使用的组件中
import MyComponent from './components/MyComponent'

2 - 组件传值 props

(1)两种接受方式

  • 数组接受一串prop
// 使用数组的方式接受props
props: ['text','mystyle'],
  • 对象接受prop
// 使用对象的方式接受props
props:{
  text:{
    type:String
  },
    mystyle: {
      type:Object
    }
},

(2)props的特性

  • 单项数据流,只能从父元素流向子元素,不能在子元素中修改,以免影响到父元素。
  • 存在两种可能需要修改prop属性的情况

(a)prop只作为初始值,后续可能会在组件内部发生改变,但不会影响到父元素。这种情况下可以定义一个data属性接收prop传递过来的值,后续的改变在data属性上处理。

props:{
  text:{
    type:String
  },
    mystyle: {
      type:Object
    }
},
data(){
  return {
    buttonText:this.text
  }
},
template:``,
methods:{
  changeText(){
    this.buttonText = '自定义文字'
  }
}

(b)prop作为依赖源,当prop发生改变时,可能会对prop进行处理后响应显示。

// 接受到的prop属性
props:{
  text:{
    type:String
  },
},
// 使用计算属性处理prop值
computed:{
  buttonText2(){
    return this.text.split('').reverse().join('')
  }
},

(3)验证props

  • 内置类型验证:String / Number / Boolean / Symbol / Object / Array / Function / Date
  • 自定义类型验证:例如:Person - 判断是否是Person的实例
  • 自定义验证函数:使用 validator 函数自定义
// 验证类型
propA:Number,
// 可能会传递过来多种类型  
propB:[String,Number],
// 验证是否为自定义类型的实例
propC:Person,
// 使用type属性验证  
propD:{
  type:Number
},
// 使用默认值,当没有传值过来时使用该值  
propE:{
  type:String,
  default:"默认值"
},
// 验证必填项  
propF:{
  type:Number,
  required:true
},
// 自定义验证函数,validatoe函数返回一个布尔值
propG:{
  type:String,
  validator(value){
    return ['success', 'warning', 'danger'].indexOf(value) !== -1
  }
},

(4)非 prop 的 Attribute

  • 如果在调用组件时,传递过来的属性没有使用prop接受,则其余的属性都会被直接继承到组件的根元素上
// 子组件中定义的模板
template:`<div><button :style="mystyle" @click="changeText">{{buttonText2}}button>div>`
props:{
  text:{
  	type:String
  },
  mystyle: {
 	 	type:[String , Number]
  },
}  

// 在调用子组件时  
<base-button :text="text" :mystyle="mystyle" disabled data-name="button"/>
  
// 渲染结果
<div disabled="disabled" data-name="button">
  <button style="background: red;">钮按击点button>
div>  
  • 指定元素继承非prop属性:目的是可以像使用原生元素一样更好的使用自定义组件

(a)使用属性:inheritAttrs:false 先禁止根节点继承属性,使用该属性后,没有被接受的属性值则直接被忽略

// 子组件中定义的模板
template:`<div><button :style="mystyle" @click="changeText">{{buttonText2}}button>div>`
props:{
  text:{
  	type:String
  },
  mystyle: {
 	 	type:[String , Number]
  },
}  
inheritAttrs:false

// 渲染结果
<div>
  <button style="background: red;">钮按击点button>
div> 

(b)指定元素继承未被接受的组件:使用 v-bind = “$attrs”

template:`<div><button v-bind="$attrs" :style="mystyle" @click="changeText">{{buttonText2}}button>div>`,

// 渲染结果
<div>
  <button disabled="disabled" data-name="button" style="background: red;">钮按击点button>
div>

(5)对组件内已有属性的处理

  • 般情况下如果在调用组件时,传入的属性在组件内部已经存在,则直接覆盖组件内属性。
  • 针对 class 和 style 属性例外,这两个属性是合并处理

3 - 组件通信

(1)组件通信的场景及常用方法

  • 父子组件传值
    • props
    • $emit / $om
    • $parent / $children / $refs
  • 兄弟组件传值 / 跨层级组件传值
    • provide / inject
    • $attrs / $listeners
    • vuex

(2)父子组件传值

  • props
    具体使用方法同 3-2 组件传值

  • $emit / $on: $emit用于子组件向父组件传值

// 子组件 Header.vue
<template>
	<div>
		<h1 @click="changeTitle">{{ title }}h1> //绑定一个点击事件
	div>
template>
<script>
export default {
	name: 'header',
  data() {
    return {
    		title:"hellow"
     }
   },
  methods:{
    changeTitle() {
      // 通过自定义事件,将自己的参数值传递给父组件
      this.$emit("titleChanged","子向父组件传值");
     }
   }
 }
script>

// 父组件接收值
<template>
  <div id="app">
     // 与子组件titleChanged 自定义事件保持一致
     // updateTitle($event)接受传递过来的参数值
    <header @titleChanged="updateTitle">header>
    <h2>{{ title }}h2>
  div>
template>
<script>
  import Header from "./components/Header"
  export default {
    name: 'Parent',
    data(){
      return{
        title:"传递的是一个值"
      }
    },
    methods:{
      //声明这个函数
      updateTitle(e){ 
        this.title = e;
      }
    },
    components:{
      "app-header":Header,
    }
  }
script>

  • $parent / $children / $refs: 直接访问父子组件的组件实例,从而可以调用组件实例的数据和方法。

缺点:无法跨层级使用。$refs 只在组件渲染完成之后可以使用,而且是非响应式的。
说明:在日常开发过程中,应该避免直接使用这几个方法,这种方法使得组件之间的耦合性比较强。且数据流向不明确,代码不好维护。

// 模板
<div id="app">
  <h1>子组件1h1>
  <child ref="child1">child>
  <h1>子组件2h1>
  <child ref="child2">child>
  <h1>子组件3h1>
  <child ref="child3">child>
div>

<script>
  Vue.component('Child',{
    template:`
{{message}}{{parentData}}
`
, data(){ return { message:'子组件', } }, mounted(){ let parentMessage = this.$parent.parentMessage; console.log("从父组件实例中获取数据") console.log(parentMessage); }, computed:{ parentData(){ return this.$parent.parentMessage } } }) const vm = new Vue({ el:"#app", data:{ parentMessage:'父组件' }, mounted() { let childMessage = this.$children[0].message; console.log("从子组件实例中获取数据") console.log(childMessage); let childHref = this.$refs; console.log(childHref) }, })
script>

Vue核心知识点 - vue2 基础2:双向绑定、组件基础_第2张图片

(3)兄弟组件 / 跨层级组件传值

  • provide / inject

作用:允许一个祖先组件向其所有子孙后代注入一个依赖,不 论 组件层次有多深,并在起上下游关系成立的时间里始终生效。祖先组件中通过 provider 来 提供变量,然后在子孙组件中通过 inject 来注入变量
优点:主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系

// 基本的使用:这种情况下数据是不响应的
//父组件.vue
export default {
	provide: {
		color: 'red' 
}
// 子组件.vue
export default {
  inject: ['color'],
  mounted () {
  	console.log(this.color) //输出red
  }
}

// 响应式方法1:直接传递整个父组件的实例过去,这样当父组件的值改变时,子组件接受到的数据也会改变
//父组件.vue
export default {
	provide(){
  	return parent:this
  }
}
// 子组件.vue
export default {
  inject: ['parent'],
  mounted () {
  	console.log(this.parent.color) //输出red
  }
}

// 响应方法2:使用2.6+版本以后的 Vue.observable
//父组件.vue
export default {
	provide(){
    this.theme = Vue.observable({
      color:'red'
    })
    return {
      theme:this.theme
    }
  }
}
// 子组件.vue
export default {
  inject: ['theme'],
  mounted () {
  	console.log(this.theme.color) //输出red
  }
}
  • $attrs / $listeners

** a t t r s : ∗ ∗ 包 含 了 父 作 用 域 中 不 被 p r o p 所 识 别 ( 且 获 取 ) 的 特 性 绑 定 ( c l a s s 和 s t y l e 除 外 ) 。 当 一 个 组 件 没 有 声 明 任 何 p r o p 时 , 这 里 会 包 含 所 有 父 作 用 域 的 绑 定 ( c l a s s 和 s t y l e 除 外 ) , 并 且 可 以 通 过 v − b i n d = " attrs:** 包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个 组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=" attrs:prop()(classstyle)prop(classstyle)vbind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
**KaTeX parse error: Unexpected character: '' at position 60: …n 事件监听器。它可以通过 v̲on="listeners" 传入内部组件

具体使用方式可查看3-2 prop传值中

4 - slot 内容分发

slot:插槽,需要动态的向组件中传递内容时,可以使用插槽传递。根据业务使用场景,插槽主要分为以下几类:

  • 默认插槽
  • 具名插槽
  • 作用域插槽:作用域插槽编译时作用域和组件调用同级,所以不能访问组件内部的作用域。

(1)默认插槽

  • 作用:需要动态的向子组件中传递元素,并且没有位置区别时
  • 说明:v-slot 指定不能放在原生元素上,只能放在 template 标签上
  • 使用:
const vm = new Vue({
  el:"#app",
  data:{
    message:'这是一个父组件',
  },
})
  
<!--在子组件中使用slot标签占位-->
Vue.component('ChildMiddle',{
    template:`
      

{{message}}

`
, data(){ return { message:'默认插槽', } }, }) // 使用子组件时 <h1>父组件信息</h1> <p>{{message}}</p> // 直接插入信息 <child-middle> 你好,默认插槽 </child-middle>

(2)具名插槽

  • 使用场景:需要传递多个元素进去,并且每一个元素有自己指定的位置
  • 在组件内部,使用name属性标志插槽的位置,在调用组件时,使用v-slot传入不同命名的插槽 v-slot:header

v-slot:footer,v-slot的缩写 #

/具名插槽
Vue.component('Child2',{
  template:`
    

{{message}}

组件内容 组件内容2
`
, data(){ return { message:'具名插槽', } }, }) // 调用组件时 <child2> <template v-slot:header> <div>header</div> </template> <template v-slot:default> <div>default</div> </template> <template #footer> <div>使用缩写footer</div> </template> </child2>

(3)作用域插槽

  • 使用场景:当需要在插槽中使用组件内部的数据时,使用作用域插槽,将组件内部的数据传递给插槽。
  • 在 slot 标签上直接使用v-bind绑定属性
// 作用域插槽,传递了info数据和user数据
Vue.component('Child3',{
  template:`
  

{{message}}

组件内容 组件内容2
`
, data(){ return { message:'作用域插槽', info:'info属性子组件信息', user:{ name:'slot', version:2.6 } } }, }) // 调用子组件时,接收到的是props对象,可以单独一个命名,也可以自己把需要的字段解构出来 <child3> <template #header="{info}"> <div>{{info}}</div> </template> <p>default</p> <template #footer="{user}"> <div>{{user.name}} - {{user.version}}</div> </template> </child3>

5 - 高阶组件

(1) 递归组件

  • 递归组件即在组件内部自己调用自己
  • 特别说明:要设置一个调用条件,避免陷入死循环
  Vue.component('ChildCom', {
    name: 'child-com',
    template: `
      

{{ message }}+{{count}}

`
, props:['count'], data() { return { message: '递归组件', } }, }) const vm = new Vue({ el:"#app", data:{ message:'这是一个父组件', count:0 }, })

(2) 内联模板

  • 内联模板即在全局注册组价时,不需要写 template 属性。在调用组件时直接声明 inline-template 即可。这样就会把内部的元素不作为内容分发,而是作为模板编译。
// 定义的内联模板组件
Vue.component('ChildCom2', {
  name: 'child-com2',
  data() {
    return {
      msg: '内联模板',
    }
  },
})

// 使用内联模板组件
<child-com2 inline-template>
  <div>
    <div>里面内容使用内联模板渲染</div>
    <p>{{msg}}</p>
	</div>
</child-com2>

(3) 动态组件

  • vue.js 提供了特殊的元素用来动态的显示组件,通过使用is属性来判断当前需要展示的组件是哪一个。
// 定义多个组件
Vue.component('comA', {
  template: `
组件A
`
, }) Vue.component('comB', { template: `
组件B
`
, }) Vue.component('comC', { template: `
组件C
`
, }) // 定义父组件 const vm = new Vue({ el: "#app", data: { currentView:'comA', }, methods:{ changeCom(comName){ this.currentView = comName; } } }) // 动态展示各个组件 <component :is="currentView"></component> <button @click="changeCom('comA')">comA</button> <button @click="changeCom('comB')">comB</button> <button @click="changeCom('comC')">comC</button> <button @click="changeCom('comD')">comD</button>
  • 使用 使失活的组件保持状态值
<keep-alive>
    <component :is="currentView">component>
keep-alive>

(4) 异步组件

  • 根据需求动态的加载组件:Vue.js 允许将一个组件定义为一个工厂函数,动态的解析组件。vue.js只在组件需要的时候渲染触发工厂函数,并把结果缓存起来,用于后期的再次渲染。
// 定义一个异步组件
Vue.component('comE',function (resolve,reject){
  window.setTimeout(function (){
    resolve( {
      template: `
{{message}}
`
, data(){ return { message:'异步组件,5000后渲染' } } }) },5000) }) // 使用异步组件 <com-e></com-e>

6 - 组件总结

Vue核心知识点 - vue2 基础2:双向绑定、组件基础_第3张图片

你可能感兴趣的:(由浅入深,Vue.js,vue,双向绑定,组件,组件化)