• 有问题请联系QQ:2374146085
  • 有问题请联系QQ:2374146085

Vue2 侦听器、计算属性以及组件

1年前 (2021-11-01) 1521次浏览 已收录 0个评论 扫描二维码

侦听器

watch侦听器可以监视数据变化。侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为函数名。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>watch侦听器</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="username">
    </div>
    <script src="./vue-2.6.12.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                username:''
            },
            watch:{
                //侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为函数名
               //新值在前,只要发生了改变,就会调用username这个韩顺
                username(newVal,oldVal){
                    console.log('值发生了改变',newVal,oldVal)
                }
            }
           //简写wtach:{username(newVal,oldVla){}}
        })
    </script>
</body>

</html>

immediate选项

上述写的方法(函数)格式的侦听器有缺点:

  • 无法在刚进入页面的时候自动触发
  • 如果侦听的是一个对象,对象中的属性发生了变化,不会触发侦听器。

此时可以使用对象格式的侦听器

  • 因为它可以通过immediate选项,让侦听器自动触发。
  • 可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象格式的侦听器</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="username">
    </div>
    <script src="./vue-2.6.12.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                username:'admin'
            },
            watch:{
                //定义对象格式的侦听器
                username:{
                    //只要username发生了改变,就触发handler的函数
                    handler(newVal,oldVal){
                        console.log('我被触发了')
                    },
                    //控制侦听器是否自动触发一次,它的默认值是false
                    immediate:true 
                }
            }
        })
    </script>
</body>

</html>
结果演示

deep选项深度监听

  • 可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>deep深度监听</title>
</head>

<body>
    <h1>开发者工具查看</h1>
    <div id="app">
        <input type="text" v-model="info.username">
    </div>
    <script src="./vue-2.6.12.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                //用户的信息对象
                info:{
                    username:'admin'
                }
            },
            watch: {
                //监听的是对象
                info:{
                    handler(newVal){
                        console.log('发生了改变',newVal.username)
                    },
                    //开启深度监听,只要对象中任何一个属性变化,都会触发对象的监听器
                    deep:true
                },

                //如果监听的是对象的子属性的变化,就用下面的写法
                'info.username'(newVal){
                    console.log('发生了改变:',newVal)
                }
            }
        })
    </script>
</body>

</html>
结果演示

计算属性computed

计算属性指的是通过一系列运算之后,最终得到的一个属性值,这个动态计算出来的属性值可以被插值表达式methods方法使用。

优点:

  • 提高代码复用
  • 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>计算属性</title>
    <script src="./vue-2.6.12.js"></script>
    <style>
        .box {
            width: 200px;
            height: 200px;
            border: 1px solid #ccc;
        }
    </style>
</head>

<body>
    <div id="app">
        <div>
            <span>R:</span>
            <input type="text" v-model.number="r">
        </div>
        <div>
            <span>G:</span>
            <input type="text" v-model.number="g">
        </div>
        <div>
            <span>B:</span>
            <input type="text" v-model.number="b">
        </div>
        <hr>

        <!-- 呈现颜色的div -->
        <!-- 在属性身上,v-bind: 是属性绑定 -->
        <!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 -->
        <!-- 下面的的样式对象中,只包含 backgroundColor 背景颜色 -->
        <div class="box" :style="{ backgroundColor: rgb }">
            {{rgb}}
        </div>
        <button @click="show">按钮</button>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                // 红色
                r: 0,
                // 绿色
                g: 0,
                // 蓝色
                b: 0
            },
            methods: {
                show() {
                    console.log(this.rgb)
                }
            },
            //计算属性定义到computed节点下,并且要定义成 方法格式
            computed: {
                //rgb作为一个计算属性,被定义成了方法格式,
                //最终要返回一个生成好的rgb(x,x,x)的字符串
                // (用的时候把结果当普通属性来用)
                rgb() {
                    return `rgb(${this.r},${this.g},${this.b})`
                }
            }
        });
    </script>
</body>

</html>
结果演示
计算属性无法开启异步任务去返回所需要的返回值,但是这种异步方式可以通过watch来实现:

watch:{
  firstname(val){
    setTimeout(()=>{
      this.fullname=val+this.lastname
    })
  },
   lastname(val){
    this.fullname=this.firstname+val
   }
}
}

computed与watch的区别

  • computed能完成的功能,watch都可以完成。
  • watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作。

this指向问题

  • 所有被Vue管理的函数,最好写成普通函数,这样this的志向才是vm或组件实例对象
  • 所有不被vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等,最好写成箭头函数,这样this的指向才是vm或组件实例对象。


绑定样式

绑定class

最终div会用到divcolor的样式和单项绑定的divcolor2属性的样式,是追加样式,不是覆盖的,适用于动态指定样式。
<div class="divcolor" :class="divcolor2" @click="change"></div>
最终div只会用到divcolor2的样式,是覆盖样式。
<div class="divcolor" class="divcolor2" @click="change"></div>

vue-cli

vue-cli简化了开发者基于webpaxk创建工程化的vue项目的过程。

创建vue项目的步骤

vue create name
  • Default ([Vue 2] babel, eslint)   自动创建vue2的项目
  • Default (Vue 3) ([Vue 3] babel, eslint)  同上
  • Manually select featureds 自定义创建项目(推荐选这个,可以自定义选择需要哪些配置)

使用空格来进行选择与取消

选择less作为预处理器

把给出的文件放到单独的配置文件里,而不是package里

Save this as a preset for future projects? (y/N) 是否把刚才创建的步骤存一个预设
最后生成的窗口不能操作,否则会冻结窗口,需要ctrl+c解冻
mac下修改安装仓库

vi ~/.vuerc

{
  "useTaobaoRegistry": false,
  "packageManager": "npm"
}

项目目录构成

assets文件夹

  • 存放项目中用到的静态资源文件,例如:css样式表、图片资源

compontnts文件夹

  • 存放开发者封装的、可复用的组件

main.js

  • 是项目的入口文件,整个项目的运行,要先执行main.js

App.vue

  • 项目的根组件,用户看到的页面就是它。用来编写待渲染的模版结构

index.html

  • 它需要预留一个el区域

main.js

  • 它把App.vue渲染到了index.html所预留的区域中

vue项目的运行流程

通过main.js把App.vue渲染到index.html的指定区域(<div id=”app”></div>)中。

简单体验

App.vue

<template>
<h1>
   zzl
</h1>
</template>

main.js

//导入vue包,得到Vue构造函数
import Vue from 'vue'
//导入App.vue根组件,将来可以把App.vue中的模版结构,
//渲染到html页面中
import App from './App.vue'

Vue.config.productionTip = false

//创建Vue的实例对象
new Vue({
  //el:'#app',
  // 把render函数指定的组件:App,渲染到html页面中
  //即用render指定的App结构替换掉el:'#app'所在的结构
  //render函数中,渲染的是哪个.vue组件,那么这个组件就是根组件
  render: h => h(App)
}).$mount('#app')//两种指定el的写法

index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <!-- app可以理解为el预留区域  -->
    <div id="app"></div>
    <!-- 下面注释的意思就是帮我们new了一个vue的构造函数放在了下面 -->
    <!-- built files will be auto injected -->
  </body>
</html>

vue组件

组件的三个组成部分

组件就是ul结构的复用,它有以下三个组成部分:

  • template->组件的模版结构
  • script->组件的javaScript行为
  • style->组件的样式

自定义一个组件test.vue

<template>
<div class="col">
  <h3>
   zzl----{{username}}
  </h3>
  <button @click="xiugainame">修改名字</button>
</div>
</template>
<script>
//默认导出,这是固定写法
export default{
    //vue组件中的data不能像之前那样指向对象,
    // vue组件中的data必须是一个函数,其他还是老样子。
    data(){
        //return出去一个数据对象
        return{
            username:'wxl'
        }
    },
    methods:{
        xiugainame(){
            //在组件中,this就表示当前组件的实例对象(组件的实例)
            this.username='ll'
        }
    },
    //当前组件的侦听器
    watch:{},
    //当前组件的计算属性
    commputed:{},
    //...
}
</script>
//<style lang='less'> 启用less语法
<style>
.col{
  color: rgb(153, 51, 51);
}
</style>
在template中只能有一个根元素,比如div,如果有需求需要平级出现两个,必须在根元素的外面再包一层根元素,即替换根元素。

组件之间的父子关系

  • 组件被封装好之后,彼此之间是相互独立的,不存在父子/兄弟关系。
  • 使用组件的时候,根据彼此的嵌套关系,形成了父子/兄弟关系。

使用组件的三个步骤

1.使用import语法导入需要的组件

import Left from '@/components/Left.vue' //@相当于webpack里的@,也是配置文件里指定好的目录,
//只不过vue已经帮我们自动指定到了src目录

2.使用components节点注册私有子组件

export default{
  components:{
    Left
  }
}

3.以标签的形式使用刚才注册的组件

<div>
  <Left><Left>
</div>

具体请看App.vue代码

<template>
<div class="rootbox">
  <h1>App根组件</h1>
  <hr>
<div class="box">
  <!-- 渲染left组件和right组件 -->
  
  <!-- 3.以标签的形式使用刚才注册的组件 -->
  <left></left>
  <right></right>
</div>
</div>
</template>
<script>
//1.导入需要使用的组件
import left from '@/components/left.vue'
import right from '@/components/right.vue'
export default{
  // data(){}
  //...

  //2.使用components节点注册私有子组件
  // 在组件App的components节点下,注册了组件left与right,
  // 则组件left与right只能用在组件App中,不能被用在其他组件中  
components:{
    'left': left,//键值相同,可以省略 
     right
  }
}
</script>

<style>
.rootbox{
  background-color: rgb(167, 167, 167);
  margin: 10px;
}
.box{
  background-color: red;
  margin: 10px;
}
</style>

注册全局组件

在vue项目的main.js入口文件中,通过Vue.component()方法,可以注册全局组件,一次注册,到处使用。

//导入需要被全局注册的那个组件
import count from '@/components/count.vue'
// biaoqian就是注册后的标签名
Vue.component('biaoqian',count)

注册后可以在任意组件中使用全局组件,比如下面。

<template>
<div>
  <h3>
   left子组件
  </h3>
  <hr>
<!-- 以标签的形式使用刚才注册的组件 -->
  <biaoqian></biaoqian>
</div>
</template>
<script>

</script>

props

props是组件的自定义属性,在封装通用(全局)组件的时候,合理使用它可以提高组件的复用性

另外props是只读的,需要用一个值来接收。

语法:

在count.vue中的自定义属性

export default{
    //props是自定义属性,允许使用者通过自定义属性,
    //为当前组件指定初始值
    //props:['自定义属性A','自定义属性B'],
    props:['init']
    data(){
          return{
              count:this.init
                }
          }
}

指定初始值:

在left.vue指定初始值

  <!-- 这个传的是字符串 -->
  <biaoqian init="9"></biaoqian>
  <!-- 因为v-bind里写的是js语句,
  所以此时去掉引号9就变成了数字 -->
  <biaoqian :init="9"></biaoqian>

props的default默认值

//改成对象的格式
props:{
        init:{
            //如果外界使用count组件的时候,
            // 没有传递init属性,则默认值生效,即init=2
            default:2
        }
    },

props的type值类型

props:{
        init:{
            default:2,
            
            // 用type属性定义属性的值类型
            // 如果传递过来的值不符合此类型,则会在终端报错
            type:Number
        }
    },

props的required必填项

props:{
        init:{
            default:2,
            
            
            //必填项校验
            // 用这个组件,必须传值,否则报错
            requitred:true
        }
    },
就算有默认值的条件下不传值,一样会报错。

组件之间的样式冲突

冲突原因

默认情况下,写在.vue组件中的样式会在全局生效,导致组件之间样式冲突的根本原因:

  • 单页面程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的。
  • 每个组件中的样式都会影响整个index.html页面中的DOM元素。

使用scoped解决冲突

<style lang="less" scoped>
h3{
  color: blue;
}
</style>

在样式中加入scoped后,会自动给每个标签加上属性选择器。但是scoped还会有一个缺点,就是在嵌套的父组件中直接改子组件包含的标签样式,此时在父组件中包含子组件标签的样式不会发生改变,因为在父组件中修改子组件标签的样式,会生成一个包含这个属性的标签选择器,比如h5[data-v-xxx],但是包含这个属性的标签选择器在子组件(源组件)的标签中找不到对应的标签(这个子组件的标签中没有这个属性h5[data-v-xxx])。

使用deep解决在父组件中改造组件的样式

// 不加deep之前:h5[data-v-xxx]
//表示包括这个属性的标签选择器
// 加deep之后:[data-v-3c83f0b7] h5
// 表示这个属性选择器的后代选择器h5
/deep/ h5{
  color: yellow;
}

只要父节点包含这个属性,就会选中这个h5。

当使用第三方组件库的时候,如果需要修改第三方组件默认样式的需求,就可以用deep

渣渣龙, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Vue2 侦听器、计算属性以及组件
喜欢 (0)

您必须 登录 才能发表评论!