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

发布时间:2023-12-27 18:30

重点说明:当前笔记内容侧重点为各个知识点的应用及需要注意的地方,而不是每个知识点的概念。概念相关请查看官网: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核心知识点

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核心知识点

(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核心知识点

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号