Vue学习(四)计算属性与侦听器,条件渲染

计算属性与侦听器

在模板中放入太多的逻辑会让模板过重且难以维护,例如:

<div id="app">
        {{msg.split('').reverse().join('')}}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 msg 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性

基础例子
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <style></style>
  </head>
  <body>
    <div id="app">
        {{reverseMsg}}
    </div>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          msg: "你好",
        },
        computed: {
            // 计算属性的 getter
            reverseMsg:function(){
                // `this` 指向 app 实例
                return this.msg.split('').reverse().join('')
            }
        },
      });
    </script>
  </body>
</html>

这里声明了一个计算属性reverseMsg,我们提供的函数用作propertyapp.reverseMsg的getter函数

console.log(app.reverseMsg) // => 'olleH'
app.msg = 'Goodbye'
console.log(app.reverseMsg) // => 'eybdooG'

app.reverseMsg的值取决于app.msg的值。可以像绑定普通property一样在模板中绑定计算属性。Vue知道app.reverseMsg依赖于app.msg,所以当后者更新,前者也会更新。

计算属性缓存 vs 方法

可以注意到通过表达式中调用方法可以达到同样的效果

<p>Reversed message: "{{ reverseMsg() }}"</p>
// 在组件中
methods: {
  reverseMsg: function () {
    return this.msg.split('').reverse().join('')
  }
}

两种方式的最终结果是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。意味着只要msg还未改变,那么多次访问计算属性会立即返回之前计算的结果。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}
计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})
计算属性的setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

侦听器

Vue通过watch选项提供了一个通用的方法来响应数据的变化,当需要在数据变化时执行异步或开销较大的操作时,这个方法最有用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="watch-example">
      <p>
        Ask a yes/no question:
        <input v-model="question" />
      </p>
      <p>{{ answer }}</p>
    </div>
    <script>
      var watchExampleVM = new Vue({
        el: "#watch-example",
        data: {
          question: "",
          answer: "I cannot give you an answer until you ask a question!",
        },
        watch: {
          // 如果 `question` 发生改变,这个函数就会运行
          question: function (newQuestion, oldQuestion) {
            this.answer = "Waiting for you to stop typing...";
            this.debouncedGetAnswer();
          },
        },
        created: function () {
          // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
          // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
          // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
          // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
          // 请参考:https://lodash.com/docs#debounce
          this.debouncedGetAnswer = _.debounce(this.getAnswer, 500);
        },
        methods: {
          getAnswer: function () {
            if (this.question.indexOf("?") === -1) {
              this.answer = "Questions usually contain a question mark. ;-)";
              return;
            }
            this.answer = "Thinking...";
            var vm = this;
            axios
              .get("https://yesno.wtf/api")
              .then(function (response) {
                vm.answer = _.capitalize(response.data.answer);
              })
              .catch(function (error) {
                vm.answer = "Error! Could not reach the API. " + error;
              });
          },
        },
      });
    </script>
  </body>
</html>

在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Class与Style绑定

绑定 HTML Class

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

对象语法

可以传给v-bind:class一个对象,动态地切换class:

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示active这个class存在与否将取决于数据propertyisActive的真值。

可以在对象中传入更多字段来动态切换多个class。此外,v-bind:class指令也可以与普通的class attribute共存。当有如下模板:

<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>

和如下data:

data: {
  isActive: true,
  hasError: false
}

结果渲染为:

<div class="static active"></div>

isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 "static active text-danger"

绑定的数据对象不必内联定义在模板里:

<div v-bind:class="classObject"></div>
data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}

渲染的结果和上面一样。我们可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:

<div v-bind:class="classObject"></div>
data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
数组语法

我们可以把一个数组传给v-bind:class,以应用一个列表:

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

渲染为:

<div class="active text-danger"></div>

如果你也想根据条件切换列表中的 class,可以用三元表达式:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

这样写将始终添加 errorClass,但是只有在 isActive 是 truthy时才添加 activeClass

不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>
用在组件上

当一个自定义组件上使用classproperty时,这些class将被添加到该组件的根元素上面,这个元素上已经存在的class不会被覆盖。

例如,如果你声明了这个组件:

Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

在使用它的时候添加一些class:

<my-component class="baz boo"></my-component>

HTML将被渲染为:

<p class="foo bar baz boo">Hi</p>

对于带数据绑定 class 也同样适用:

<my-component v-bind:class="{ active: isActive }"></my-component>

isActive 为 truthy[1] 时,HTML 将被渲染成为:

<p class="foo bar active">Hi</p>
绑定内联样式
对象语法

v-bind:style的对象语法像CSS,但其实是一个JavaScript对象。CSS property名可以用驼峰式或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

同样的,对象语法常常结合返回对象的计算属性使用。

数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>
自动添加前缀

v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。

多重值

从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

条件渲染

v-if

v-if指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

<h1 v-if="awesome">Vue is awesome!</h1>

也可以用 v-else 添加一个“else 块”:

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<template>元素上使用v-if条件渲染分组:

因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

      <template v-if="ok">
        <h1>Title</h1>
        <p>Paragraph 1</p>
        <p>Paragraph 2</p>
      </template>
v-else

你可以使用 v-else 指令来表示 v-if 的“else 块”:

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

v-else-if

v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

类似于 v-elsev-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。

用key管理可复用的元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input> 不会被替换掉——仅仅是替换了它的 placeholder

这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key attribute 即可:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

注意,<label> 元素仍然会被高效地复用,因为它们没有添加 key attribute。

v-show

另一个用于根据条件展示元素的选项是v-show指令。用法大致一样:

<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独一无二的VV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值