Vue3_02_optionsApi


后续会学习setup,这些api都不会再使用了,但是我们必须学会这些基础,才能更好的学习setup

v-on指令绑定事件

以前我们就写过@click=“”,其实@是v-on:的语法糖

1117

1118

基本使用

v-on不仅可以绑定click事件,原先学的dom事件都可以绑定,只是click事件比较常用。

<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>Document</title>
  <style>
    .box {
      width: 100px;
      height: 100px;
      background-color: orange;
      margin-top: 10px;
    }
  </style>
</head>
<body>

  <div id="app">
    <!-- 1.基本的写法 -->
    <div class="box" v-on:click="divClick"></div>

    <!-- 2.语法糖写法(重点掌握) -->
    <div class="box" @click="divClick"></div>

    <!-- 3.绑定的方法位置, 也可以写成一个表达式(不常用, 不推荐) -->
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
      <!-- 这里counter++就直接是点击这个按钮会发生的程序,而不是有一个counter++名字的函数>>
    <button @click="counter++">+1</button>

    <!-- 4.绑定其他方法(掌握) -->
    <div class="box" @mousemove="divMousemove"></div>

    <!-- 5.元素绑定多个事件(掌握) -->
    <div class="box" @click="divClick" @mousemove="divMousemove"></div>
    <!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> -->
    <!-- <div class="box" @="{ click: divClick, mousemove: divMousemove }"></div> -->
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data: function() {
        return {
          counter: 0
        }
      },
      methods: {
        divClick() {
          console.log("divClick")
        },
        increment() {
          this.counter++
        },
        divMousemove() {
          console.log("divMousemove")
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>
<button @click="increment">+1</button>
  <!-- 这里counter++就直接是点击这个按钮会发生的程序,而不是有一个counter++名字的函数>>
<button @click="counter++">+1</button>

如上,click里面不一定要绑定一个methods里的函数,也可以直接写表达式进去。(不建议,阅读性很差)

 <!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> -->
  <!-- <div class="box" @="{ click: divClick, mousemove: divMousemove }"></div> -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>

v-on可以同时绑定多个事件,如上代码,通常用第三种。

v-on的传递参数

1123

<body>

  <div id="app">
    <!-- 1.默认传递event对象 -->
    <button @click="btn1Click">按钮1</button>

    <!-- 2.只有自己的参数 -->
    <button @click="btn2Click('why', age)">按钮2</button>

    <!-- 3.自己的参数和event对象 -->
    <!-- 在模板中想要明确的获取event对象: $event -->
    <button @click="btn3Click('why', age, $event)">按钮3</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data: function() {
        return {
          message: "Hello Vue",
          age: 18
        }
      },
      methods: {
        // 1.默认参数: event对象
        // 总结: 如果在绑定事件的时候, 没有传递任何的参数, 那么event对象会被默认传递进来
        btn1Click(event) {
          console.log("btn1Click:", event)
        },

        // 2.明确参数:
        btn2Click(name, age) {
          console.log("btn2Click:", name, age)
        },

        // 3.明确参数+event对象
        btn3Click(name, age, event) {
          console.log("btn3Click:", name, age, event)
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

情况一:在事件绑定的时候不传入参数,也就是函数不加括号写东西进去,那么自动有一个默认参数event,里面包含了点击等各种信息。

<button @click="btn1Click">按钮1</button>
//前提是methods里面的函数写了一个形参如btn1Click(event),如果是btn1Click(),那么也是没有event能在方法中被使用。
  methods: {
        // 1.默认参数: event对象
        // 总结: 如果在绑定事件的时候, 没有传递任何的参数, 那么event对象会被默认传递进来
        btn1Click(event) {
          console.log("btn1Click:", event)
        }
      }

情况二:

<button @click="btn2Click('why', age)">按钮2</button>

传入参数,上面的语句,虽然加了括号,但是不会直接去执行btn2Click这个函数,vue已经对它进行过特殊处理了

只有当事件触发的时候,它会把参数传递给methods里面声明的函数,然后执行。

但是我们传入了参数,默认参数event就会消失。如果我们既要传递自己的参数又要默认参数event,就是情况三

情况三:

<button @click="btn3Click('why', age, $event)">按钮3</button>

event必须前面加一个$符号,就能传入默认参数,这是vue规定的,记住就好。

绑定事件v-on的修饰符用法

修饰符就可以自动帮我们完成一些操作,比如.stop就是阻止冒泡 .prevent就是阻止默认行为

<button @click.stop="btnClick">按钮</button>

条件渲染

v-if和v-else的demo

<body>

  <!-- 模板语法 -->
  <div id="app">
    <ul v-if="names.length > 0">
      <li v-for="item in names">{{item}}</li>
    </ul>

    <h2 v-else>当前names没有数据, 请求获取数据后展示</h2>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data: function() {
        return {
          names: []
        }
      },
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

如上,v-if=“”中的条件成立的时候,才渲染li,不成立的话渲染一行h2,其中v-if和if语句一样成立与否看里面语句返回的布尔值。

判断对象中的数据是否为空

上面代码用了v-if和v-else,其中数据是数组,我们可以通过数组.length来判断有没有数据,那么对象怎么判断呢

<div class="info" v-if="Object.keys(info).length">

我们可以通过获取此对象的key来判断有没有数据,它会返回一个数组

v-else-if

<body>

  <div id="app">
    <h1 v-if="score > 90">优秀</h1>
    <h2 v-else-if="score > 80">良好</h2>
    <h3 v-else-if="score >= 60">及格</h3>
    <h4 v-else>不及格</h4>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          score: 40
        }
      },
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

1124

也就是说,如果条件不成立,它不是通过上面dispaly;none来隐藏这个对象,它在浏览器中是根本就不会出现这个标签。

template元素的使用

1125

也就是说,我们以前写指令,比如v-if,如果我们需要让这个指令控制多个元素的显示,一般通过div包裹这些元素(vue2中都是这样做),但是这样就会在浏览器中多出一个没必要的div标签,增加了性能损耗

所以在vue3有一个template标签元素,专门用来包括多个标签写指令,但是在浏览器中不会渲染它,节省了性能

注意:你写了标签在template里面,那么必须给template写v-if等指令,否则不显示template内的内容。

而且template不能和v-show使用

小案例二维码的显示和隐藏

<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>Document</title>
  <style>
    img {
      width: 200px;
      height: 200px;
    }
  </style>
</head>
<body>

  <div id="app">
    <div>
      <button @click="toggle">切换</button>
    </div>
    
    <template v-if="isShowCode">
      <img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
    </template>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          isShowCode: true
        }
      },

      methods: {
        toggle() {
          this.isShowCode = !this.isShowCode
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

v-show的使用

1126

v-show和v-if作用一样,条件成立则显示元素。

1-v-show不成立的时候,浏览器中是由dom元素的,只是用display:none隐藏了,但是v-if就是直接消耗,标签直接无了

注意这里是display:none不是display:hidden。

hidden隐藏后dom元素标签还存在,只是东西不见了,但是还占标准流。

none是不占标准流了,也就是说none后其他元素会顶替它的位置

2-所以v-show不能使用template,因为template根本不会出现在浏览器中,那么v-show就无法通过给template来设置display来达到隐藏和显示效果,所以只能用div。

3-当一个元素需要经常判断显示隐藏就用v-show,因为如果用v-if就要来回创建销毁dom,会频繁引起回流重绘,导致性能降低。

模板语法二

v-for列表渲染

1127

<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>Document</title>
  <style>
    .item {
      margin-top: 5px;
      background-color: orange;
    }

    .item .title {
      color: red;
    }
  </style>
</head>
<body>

  <div id="app">
    <!-- 1.电影列表进行渲染 -->
    <h2>电影列表</h2>
    <ul>
      <li v-for="movie in movies">{{ movie }}</li>
    </ul>

    <!-- 2.电影列表同时有索引 -->
    <ul>
      <li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li>
    </ul>

    <!-- 3.遍历数组复杂数据 -->
    <h2>商品列表</h2>
    <div class="item" v-for="item in products">
      <h3 class="title">商品: {{item.name}}</h3>
      <span>价格: {{item.price}}</span>
      <p>秒杀: {{item.desc}}</p>
    </div>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          // 1.movies
          movies: ["星际穿越", "少年派", "大话西游", "哆啦A梦"],

          // 2.数组: 存放的是对象
          products: [
            { id: 110, name: "Macbook", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
            { id: 111, name: "iPhone", price: 8.8, desc: "9.9秒杀, 快来抢购!" },
            { id: 112, name: "小米电脑", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
          ]
        }
      },
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

1-基本使用

<ul>
     <li v-for="movie in movies">{{ movie }}</li>
   </ul>

谁用了v-for,谁就会被渲染出多个,所以这里是写到li里面而不是ul里面

2-不仅遍历数据,还遍历索引,用括号即可。

<ul>
     <li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li>
   </ul>

v-for渲染类型

v-for也可以遍历对象和字符串和数字

1128

1-遍历对象,只写一个参数就是值value,写两个第二个是key,第三个是index索引(遍历数组的话,第一个是值,第二个是索引,没有键)

2-也可以遍历字符串,如abcd遍历在li,第一个li就是a,第二个li是b……

3-遍历数字的话,如上图二的代码,就是遍历1,2,3,4,5。。。。10

4-索引是从0开始的。

注意:v-for是写在需要遍历的标签上,如上是写在li而不是ul里面。

数组更新的检测

1129

<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>Document</title>
</head>
<body>

  <div id="app">
    <ul>
      <li v-for="item in names">{{ item }}</li>
    </ul>
    <button @click="changeArray">修改数组</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          names: ["abc", "cba", "nba", "aaa", "ccc"]
        }
      },
      methods: {
        changeArray() {
          // 1.直接将数组修改为一个新的数组
          // this.names = ["why", "kobe"]

          // 2.通过一些数组的方法, 修改数组中的元素
          // this.names.push("why")
          // this.names.pop()
          // this.names.splice(2, 1, "why")
          // this.names.sort()
          // this.names.reverse()

          // 3.不修改原数组的方法是不能侦听(watch)
          const newNames = this.names.map(item => item + "why")
          this.names = newNames
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

也就是说,当我们使用v-for渲染一个数组中的内容,这个数组发生改变,那么渲染也会发生改变

但是如果是使用map这些方法,没有修改原数组,就不会被重新渲染

tips:

<li v-for="item in names">{{ item }}</li>

其中的in在vue3中新增了一个写法,就是把in写成of,效果和作用是一样的。

v-for的key属性

1130

所以说key是用于在虚拟dom的diff算法中,来识别新老vnode的

注意:一般在v-for遍历的标签中加key属性,它和下面语句的key不同
<li v-for="(item,key,index) in object">{{ item }}</li>
一个是标签属性,一个是遍历对象里面的属性名

1131

vnode并不是那个node环境,而是虚拟节点,它本质上是javascript对象

1132

当我们在template中写了div这些元素代码,首先会形成一个vnode,它本质是一个对象,然后浏览器解析这些vnode对象就会形成一个虚拟dom,然后虚拟dom通过diff虚拟dom算法会决定哪些dom需要修改,哪些不需要,再生成真实dom

VNode是javascript对象,VNode表示Virtual DOM(虚拟DOM)中的虚拟节点

为什么不直接创建真实dom

1-方便diff算法

1134

虚拟dom的diff会去比较新数据和老数据,遇到相同的就直接复用,不同的再重新生成虚拟dom,然后再生成真实dom

1136

过程一:如果不设置key属性

1137

1135

如果我们是插入一个数据,而且不设置key属性,那么插入前的数据会复用,但是插入后的地方入上图插入f,会把原来c的vdom给f(li元素的dom不变,但是修改内容),然后原来d的vdom再给c,然后会发现原来e的vdom没位置了,就会再生成一个vnode来接收e;

那么明显这种算法的效率很低,如果是追加数据,相较于初数据,就在后面添加还好,如果 是插入的话,那么插入位置,原来的vdom全部要发生改变。 所以我们需要设置key属性

过程二:设置了key属性

key一般是绑定id,因为key是不能变的,是用来识别前后两次数据内容是否一样,如果key会变,那么它毫无意义

如果我们设置了key属性,那么abcde都有key,当我们插入f,abcde都会直接复用,在b后面再生成一个f的vnode即可。 可以大大节约性能

1138

2-方便跨平台

1133

因为虚拟dom树由一个个vnode组成,所以说它本质是一个js对象,我们可以把这个对象通过不同的解析方式在不同平台解析,可以在浏览器形成我们现在学的虚拟dom,也可以渲染到移动端上,而且不是以h5形式而是以原生的iOS或者安卓组件,甚至vr设备上。

options Api

optionsApi就是data和methods和template那些东西

1139

computed计算属性使用

<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>Document</title>
</head>
<body>

  <div id="app">
    <!-- 插值语法表达式直接进行拼接 -->
    <!-- 1.拼接名字 -->
    <h2>{{ firstName + " " + lastName }}</h2>
    <h2>{{ firstName + " " + lastName }}</h2>
    <h2>{{ firstName + " " + lastName }}</h2>

    <!-- 2.显示分数等级 -->
    <h2>{{ score >= 60 ? '及格': '不及格' }}</h2>

    <!-- 3.反转单词显示文本 -->
      <!-- 通过split把字符串以空格分开转化为数组,通过reverse反转数组,再通过join把数组变成字符串以空格隔开-->
    <h2>{{ message.split(" ").reverse().join(" ") }}</h2>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          // 1.姓名
          firstName: "kobe",
          lastName: "bryant",

          // 2.分数: 及格/不及格
          score: 80,

          // 3.一串文本: 对文本中的单词进行反转显示
          message: "my name is why"
        }
      },
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

1140

方法一:抽取方法到methods中

<body>

  <div id="app">
    <!-- 插值语法表达式直接进行拼接 -->
    <!-- 1.拼接名字 -->
    <h2>{{ getFullname() }}</h2>
    <h2>{{ getFullname() }}</h2>
    <h2>{{ getFullname() }}</h2>

    <!-- 2.显示分数等级 -->
    <h2>{{ getScoreLevel() }}</h2>

    <!-- 3.反转单词显示文本 -->
    <h2>{{ reverseMessage() }}</h2>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          // 1.姓名
          firstName: "kobe",
          lastName: "bryant",

          // 2.分数: 及格/不及格
          score: 80,

          // 3.一串文本: 对文本中的单词进行反转显示
          message: "my name is why"
        }
      },
      methods: {
        getFullname() {
          return this.firstName + " " + this.lastName
        },
        getScoreLevel() {
          return this.score >= 60 ? "及格": "不及格"
        },
        reverseMessage() {
          return this.message.split(" ").reverse().join(" ")
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

split是把字符串按照设置字符分割为数组;join是把数组变成字符串,通过设置的字符分隔。

​ return this.message.split(“ “).reverse().join(“ “)

为什么要转换为数组?

因为reserve方法是给数组用的,字符串用不了

方法二:使用计算属性computed

响应式数据通俗说,就是用了data里面的数据; computed也属于optionsApi

computed里面和methods一样也可以直接通过this来使用data里面返回的变量(因为vue底层通过bind等重定向了this)

1141

<body>

  <div id="app">
    <!-- 1.methods -->
    <h2>{{ getFullname() }}</h2>
    <h2>{{ getFullname() }}</h2>
    <h2>{{ getFullname() }}</h2>

    <!-- 2.computed -->
    <h2>{{ fullname }}</h2>
    <h2>{{ fullname }}</h2>
    <h2>{{ fullname }}</h2>

    <!-- 修改name值 -->
    <button @click="changeLastname">修改lastname</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          firstName: "kobe",
          lastName: "bryant"
        }
      },
      methods: {
        getFullname() {
          console.log("getFullname-----")
          return this.firstName + " " + this.lastName
        },
        changeLastname() {
          this.lastName = "why"
        }
      },
      computed: {
        fullname() {
          console.log("computed fullname-----")
          return this.firstName + " " + this.lastName
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

我们乍一看,好像和写在methods里面的方法没什么区别呀?

1-写在computed可以更易读,数据处理就在conputed,触发事件就在methods

2-computed写在大胡子语法里面,不需要加() 。

computed和methods区别

computed和methods最大的区别是,computed是有缓存的。

1142

也就是说,如果我们用methods中的函数来显示数据,不同的地方用了多少次这个数据,这个函数就会被调用多少次。

但是如果用computed那么只要一个地方使用了,运行一次函数,其他地方再使用,将不会再运行函数,而是直接用缓存去显示(vue底层自己的优化),只有数据发生变化,才会再次调用函数。

computed的set和get

下面是计算属性的完整写法,一般开发很少这样写。

<body>

  <div id="app">
    <h2>{{ fullname }}</h2>

    <button @click="setFullname">设置fullname</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          firstname: "coder",
          lastname: "why"
        }
      },
      computed: {
        // 语法糖的写法
        // fullname() {
        //   return this.firstname + " " + this.lastname
        // },
        
        // 完整的写法:
        fullname: {
          get: function() {
            return this.firstname + " " + this.lastname
          },
          set: function(value) {
            const names = value.split(" ")
            this.firstname = names[0]
            this.lastname = names[1]
          }
        }
      },
      methods: {
        setFullname() {
          this.fullname = "kobe bryant"
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>

完整写法下,其实是写成一个对象,如上代码,当我们通过this.fullname给他赋值的时候,这个值实际上去到了set方法的value中(在运行的时候,只执行get一遍,当我们触发上面代码的设置fullname值的事件,会执行一遍set,再执行一遍get)

如下代码,如果computed没有set属性,那么 this.fullname就是get返回的值

this.fullname = "kobe bryant"

如果我们人为设置了this.fullname属性的值,那么这个值就会变成set属性对应函数的参数传递到set内(set属性对应的函数的语句是自己编写的,我们如果自己不写set,也就不会去设置fullname属性的值)

在实际开发中,我们都不用set,所以get和set都可以省略成一个语法糖的形式,如fullname(){return this.firstname + “ “ + this.lastname}

为什么在使用的时候可以直接写fullname使用,不需要加括号调用呢?,如

<h2>{{ fullname }}</h2>

可能是get方法有内置的一些机制。(现在我们记住就好了,后续会研究底层)

为什么computed里面的“函数”在插值语法里面不要想methods函数一样写小括号呢?

因为我们开发中一般不设置set,那么其实就只有一个get属性对应的函数,js对象的高级写法就可以省略成语法糖的样子,只是进行了两重省略。平时对象的高级写法只省略了一次。

侦听器watch选项使用

1143

<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>Document</title>
</head>
<body>

  <div id="app">
    <h2>{{message}}</h2>
    <button @click="changeMessage">修改message</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // Proxy -> Reflect
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          message: "Hello Vue",
          info: { name: "why", age: 18 }
        }
      },
      methods: {
        changeMessage() {
          this.message = "你好啊, 李银河!"
          this.info = { name: "kobe" }
        }
      },
      watch: {
        // 1.默认有两个参数: newValue/oldValue
        message(newValue, oldValue) {
          console.log("message数据发生了变化:", newValue, oldValue)
        },
        info(newValue, oldValue) {
          // 2.如果是对象类型, 那么拿到的是代理对象
          // console.log("info数据发生了变化:", newValue, oldValue)
          // console.log(newValue.name, oldValue.name)

          // 3.获取原生对象
          // console.log({ ...newValue })
          console.log(Vue.toRaw(newValue))
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

1-如上代码,比如监听data中的message,我们就在watch对象中写一个message(){},你侦听哪个数据a就把函数写成什么名字; 别问为什么,底层就是这样设置的,得去研究源码。

当message发生改变,那么就会自动执行watch中的message()函数

2-如果我们要获取到数据改变的数据和当前最新的数据,可以写两个参数,newValue和oldValue(随便命名,第一个是新数据,第二个是老数据)

watch中的immediate和deep

<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>Document</title>
</head>
<body>

  <div id="app">
    <h2>{{ info.name }}</h2>
    <button @click="changeInfo">修改info</button>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          info: { name: "why", age: 18 }
        }
      },
      methods: {
        changeInfo() {
          // 1.创建一个新对象, 赋值给info
          // this.info = { name: "kobe" }

          // 2.直接修改原对象某一个属性
          this.info.name = "kobe"
        }
      },
      watch: {
        // 默认watch监听不会进行深度监听
        // info(newValue, oldValue) {
        //   console.log("侦听到info改变:", newValue, oldValue)
        // }

        // 进行深度监听
        info: {
          handler(newValue, oldValue) {
            console.log("侦听到info改变:", newValue, oldValue)
            console.log(newValue === oldValue)
          },
          // 监听器选项:
          // info进行深度监听
          deep: true,
          // 第一次渲染直接执行一次监听器
          immediate: true
        },
          //下面是vue2写法,了解一下
        "info.name": function(newValue, oldValue) {
          console.log("name发生改变:", newValue, oldValue)
        }
      }
    })

    // 2.挂载app
    app.mount("#app") 
  </script>
</body>
</html>

1-如上的监听info,如果我们不需要配置什么的,直接写info(){}即可,这是完整写法的语法糖

2-但是不配置,默认其实是浅监听,也就是说当上面代码info对象没有重新赋值,只是修改了里面的某个属性,是监听不到的,我们需要通过完整写法,设置deep:true

3-完整写法中有一个handler函数(不写完整写法,那么这个handler函数就通过对象的高级写法省略了,如果写完整写法,那么这个函数必须叫handler这是vue底层设置的,其实handler也是一个对象的高级写法,已经省略一次了),immediate配置是让第一次渲染直接执行一次监听器

监听器的其他写法

了解即可,很少很少使用

1144

1145

阶段性综合案例

1146

这种综合案例和我们实际开发中的项目都是一样的,我们需要先写出页面,然后再去考虑逻辑,添加事件等等

注意我们要去使用vue,不要自己习惯性的去操作dom

<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>Document</title>
  <style>
    table {
      border-collapse: collapse;
      /* text-align: center; */
    }

    thead {
      background-color: #f5f5f5;
    }

    th, td {
      border: 1px solid #aaa;
      padding: 8px 16px;
    }

    .active {
      background-color: skyblue;
    }
  </style>
</head>
<body>

  <div id="app">
    <!-- 1.搭建界面内容 -->
    <template v-if="books.length">
      <table>
        <thead>
          <tr>
            <th>序号</th>
            <th>书籍名称</th>
            <th>出版日期</th>
            <th>价格</th>
            <th>购买数量</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <!-- 注意这里是如何绑定id的,key也是一个属性,所以需要v-bind -->
          <tr v-for="(item, index) in books" 
              :key="item.id"
              @click="rowClick(index)"
              :class="{ active: index === currentIndex }">
            <td>{{ index + 1 }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.date }}</td>
            <td>{{ formatPrice(item.price) }}</td>
            <td>
              <button :disabled="item.count <= 1" @click="decrement(index, item)">-</button>
              {{ item.count }}
              <button @click="increment(index, item)">+</button>
            </td>
            <td>
              <button @click="removeBook(index, item)">移除</button>
            </td>
          </tr>
        </tbody>
      </table>
  
      <h2>总价: {{ formatPrice(totalPrice) }}</h2>
    </template>

    <template v-else>
      <h1>购物车为空, 请添加喜欢的书籍~</h1>
      <p>商场中有大量的IT类的书籍, 请选择添加学习, 注意保护好自己的头发!</p>
    </template>
  </div>
  
  <script src="../lib/vue.js"></script>
  <script src="./data/data.js"></script>
  <script>
    // 1.创建app
    const app = Vue.createApp({
      // data: option api
      data() {
        return {
          books: books,
          currentIndex: 0
        }
      },
      // computed
      computed: {
        totalPrice() {
          // 1.直接遍历books
          // let price = 0
          // for (const item of this.books) {
          //   price += item.price * item.count
          // }
          // return price

          // 2.reduce(自己决定,遍历方法很多种,选你最熟练的)
          return this.books.reduce((preValue, item) => {
            return preValue + item.price * item.count
          }, 0)
        }
      },
      methods: {
        //这里是格式化价格,就是把所有价格前加一个¥
        //我们也可以直接把¥写在template里面,但是需要写很多个
        //当某一天这个¥要变成$,那么就更难改了,如果我们封装一个方法,那么就只需要改下面一处
        formatPrice(price) {
          return "¥" + price
        },

        // 监听-和+操作
        decrement(index, item) {
          console.log("点击-")
          // this.books[index].count--
          item.count--
        },
        increment(index, item) {
          console.log("点击+:", index)
          // this.books[index].count++
          item.count++
        },
        removeBook(index, item) {
          this.books.splice(index, 1)
        },
        rowClick(index) {
          this.currentIndex = index
        }
      }
    })

    // 2.挂载app
    app.mount("#app")
  </script>
</body>
</html>

data.js

const books = [
  {
    id: 1,
    name: '《算法导论》',
    date: '2006-9',
    price: 85.00,
    count: 1
  },
  {
    id: 2,
    name: '《UNIX编程艺术》',
    date: '2006-2',
    price: 59.00,
    count: 1
  },
  {
    id: 3,
    name: '《编程珠玑》',
    date: '2008-10',
    price: 39.00,
    count: 1
  },
  {
    id: 4,
    name: '《代码大全》',
    date: '2006-3',
    price: 128.00,
    count: 1
  },
  {
    id: 5,
    name: '《你不知道JavaScript》',
    date: '2014-8',
    price: 88.00,
    count: 1
  },
]


文章作者: 瑾年
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 周东奇 !
免责声明: 本站所发布的一切内容,包括但不限于IT技术资源,网络攻防教程及相应程序等文章仅限用于学习和研究目的:不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。本站部分信息与工具来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如有侵权请邮件(jinnian770@gmail.com)与我们联系处理。
  目录