第七章 前端工程化(Vue3视图渲染技术)

六、Vue3视图渲染技术

6.1 模版语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。

  • 6.1.1 插值表达式和文本渲染
A- 插值表达式是什么?

插值表达式:

        最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}

B- 插值表达式特点
  • 插值表达式是 将数据渲染到元素的指定位置的手段之一

  • 插值表达式 不绝对依赖标签,其位置相对自由

  • 插值表达式中支持javascript的运算表达式

  • 插值表达式中也支持函数的调用

C- 插值表达式代码

<script setup type='module'>

let msg ="hello vue3"

let getMsg= ()=>{

    return 'hello vue3 message'

  }

let age = 19

let bee = '蜜 蜂'  

 // 购物车

const carts = [{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}];

//计算购物车总金额

  function compute(){

      let count = 0;

      for(let index in carts){

          count += carts[index].price*carts[index].number;

      }

      return count;

  }

</script>

<template>

  <div>

    <h1>{{msg}}</h1>

    msg的值为: {{ msg }} <br>

    getMsg返回的值为: {{ getMsg() }}  <br>

    是否成年: {{ age>=18?'true':'false' }} <br>

    反转: {{ bee.split(' ').reverse().join('-') }} <br>

    购物车总金额: {{ compute() }} <br/>

    购物车总金额: {{carts[0].price*carts[0].number + carts[1].price*carts[1].number}} <br>

  </div>

</template>

<style scoped>

</style>

D- 为了渲染双标中的文本,我们也可以选择使用 v-text 和 v-html 命令
  • v-*** 这种写法的方式使用的是vue的命令

  • v-***的命令必须依赖元素,并且要写在元素的开始标签中

  • v-***指令支持 ES6中的字符串模板

  • 插值表达式中支持 javascript的运算表达式

  • 插值表达式中也支持函数的调用

  • v-text 可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本

  • v-html 可以将数据渲染成双标签中间的文本,识别html元素结构的文本

E- v-text 和 v-html 代码<script setup type='module'>

  let msg ='hello vue3'

  let getMsg= ()=>{

    return msg

  }

  let age = 19

  let bee = '蜜 蜂'

  let redMsg ='<font color=\'red\'>msg</font>'

  let greenMsg =`<font color=\'green\'>${msg}</font>`

</script>

<template>

  <div>

    <span v-text='msg'></span> <br>

    <span v-text='redMsg'></span> <br>

    <span v-text='getMsg()'></span> <br>

    <span v-text='age>18?"成年":"未成年"'></span> <br>

    <span v-text='bee.split(" ").reverse().join("-")'></span> <br>

    <span v-html='msg'></span> <br>

    <span v-html='redMsg'></span> <br>

    <span v-html='greenMsg'></span> <br>

    <span v-html="`<font color='green'>${msg}</font>`"></span> <br>

  </div>

</template>

<style scoped>

</style>


  • 6.1.2 Attribute属性渲染
A- v-bind

想要渲染一个元素的 attribute,应该使用  v-bind 指令

  • 由于插值表达式不能直接放在标签的属性中,所有要渲染元素的属性就应该使用v-bind

  • v-bind可以用于渲染任何元素的属性 , 语法为 v-bind:属性名='数据名', 可以简写为 :

  属性名='数据名'


  • 6.1.3 事件的绑定

我们可以使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码 

  • 用法:v-on:click="handler" 或简写为  @click="handler"

  • vue中的 事件名=原生事件名 去掉on 前缀 如:  onClick --> click

  • handler的值可以是方法事件处理器,也可以是内联事件处理器

  • 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下

    • .once:只触发一次事件。[重点]

    • .prevent:阻止默认事件。[重点]

    • .stop:阻止事件冒泡。

    • .capture:使用事件捕获模式而不是冒泡模式。

    • .self:只在事件发送者自身触发时才触发事件。

<script setup type='module'>

  import {ref} from 'vue'

  // 响应式数据 当发生变化时,会自动更新 dom树

  let count=ref(0)

  let addCount= ()=>{

    count.value++

  }

  let incrCount= (event)=>{

    count.value++

    event.preventDefault();

    }

</script>

<template>

<div>

    <h1>count的值是:{{ count }}</h1>

    <!-- 方法事件处理器 -->

    <button v-on:click="addCount()">addCount</button> <br>

    <!-- 内联事件处理器 -->

    <button @click="count++">incrCount</button> <br>

    <!-- 事件修饰符 once 只绑定事件一次 -->

    <button @click.once="count++">addOnce</button> <br>

  </div>

</template>

<style scoped>

</style>


6.2 响应式基础
  • 6.2.1 响应式需求案例
A- 需求

需求:实现 + - 按钮,实现数字加一减一

B- 代码

<script setup type='module'>

let counter = 0;

    function show(){

        alert(counter);

    }

</script>

<template>

<div>

    <button @click="counter--">-</button>

    {{ counter }}

    <button @click="counter++">+</button>

    <hr>

    <!-- 此案例,我们发现counter值,会改变,但是页面不改变! 默认Vue3的数据是非响应式的!-->

    <button @click="show()">显示counter值</button>

</div>

</template>

<style scoped>

</style>

  • 6.2.2 响应式实现关键字ref

ref 可以将一个基本类型的数据(如字符串,数字等)转换为一个响应式对象。 ref 只能包裹单一元素

<script setup type='module'>

    /* 从vue中引入ref方法 */

    import {ref} from 'vue'

    let counter = ref(0);

    function show(){

        alert(counter.value);

    }

    /* 函数中要操作ref处理过的数据,需要通过.value形式 */

    let decr = () =>{

      counter.value--;

    }

    let incr = () =>{

      counter.value++;

    }

</script>

<template>

  <div>

    <button @click="counter--">-</button>

    <button @click="decr()">-</button>

    {{ counter }}

    <button @click="counter++">+</button>

    <button @click="incr()">+</button>

    <hr>

    <button @click="show()">显示counter值</button>

   </div>

</template>

<style scoped>

</style>

在上面的例子中,我们使用 ref 包裹了一个数字,在代码中给这个数字加 1 后,视图也会跟着动态更新。需要注意的是,由于使用了 ref,因此需要在访问该对象时使用 .value 来获取其实际值。


  • 6.2.3 响应式实现关键字reactive

我们可以使用 reactive() 函数创建一个响应式对象或数组:

<script setup type='module'>

     /* 从vue中引入reactive方法 */

    import {ref,reactive} from 'vue'

    let data = reactive({

      counter:0

    })

    function show(){

        alert(data.counter);

    }

    /* 函数中要操作reactive处理过的数据,需要通过 对象名.属性名的方式 */

    let decr = () =>{

      data.counter--;

    }

    let incr = () =>{

      data.counter++;

    }

</script>

<template>

  <div>

    <button @click="data.counter--">-</button>

    <button @click="decr()">-</button>

    {{ data.counter }}

    <button @click="data.counter++">+</button>

    <button @click="incr()">+</button>

    <hr>

    <button @click="show()">显示counter值</button>

   </div>

</template>

<style scoped>

</style>


  • 对比ref和reactive:
  • 使用 ref 适用于以下开发场景:

    • 包装基本类型数据ref 主要用于包装基本类型数据(如字符串、数字等),即只有一个值的数据,如果你想监听这个值的变化,用 ref 最为方便。在组件中使用时也很常见。

    • 访问方式简单:ref 对象在访问时与普通的基本类型值没有太大区别,只需要通过 .value 访问其实际值即可。

  • 使用 reactive 适用于以下开发场景:

    • 包装复杂对象reactive 可以将一个普通对象转化为响应式对象,这样在数据变化时会自动更新界面,特别适用于处理复杂对象或者数据结构。

    • 需要递归监听的属性:使用 reactive 可以递归追踪所有响应式对象内部的变化,从而保证界面的自动更新。

  • 综上所述,ref 适用与简单情形下的数据双向绑定,对于只有一个字符等基本类型数据或自定义组件等情况,建议可以使用 ref;而对于对象、函数等较为复杂的数据结构,以及需要递归监听的属性变化,建议使用 reactive。当然,在实际项目中根据需求灵活选择也是十分必要的。


  • 6.2.4 扩展响应式关键字 toRefs 和 toRef

toRef 基于 reactive响应式对象 上的一个属性,创建一个对应的 ref响应式数据。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。toRefs将一个响应式对象多个属性转换为一个多个ref数据,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

案例:响应显示reactive对象属性

<script setup type='module'>

    /* 从vue中引入reactive方法 */

    import {ref,reactive,toRef,toRefs} from 'vue'

    let data = reactive({

      counter:0,

      name:"test"

    })

    // 将一个reactive响应式对象中的某个属性转换成一个ref响应式对象

    let ct =toRef(data,'counter');

    // 将一个reactive响应式对象中的多个属性转换成多个ref响应式对象

    let {counter,name} = toRefs(data)

    function show(){

        alert(data.counter);

        // 获取ref的响应对象,需要通过.value属性

        alert(counter.value);

        alert(name.value)

    }

    /* 函数中要操作ref处理过的数据,需要通过.value形式 */

    let decr = () =>{

      data.counter--;

    }

    let incr = () =>{

      /* ref响应式数据,要通过.value属性访问 */

      counter.value++;

    }

</script>

<template>

  <div>

    <button @click="data.counter--">-</button>

    <button @click="decr()">-</button>

    {{ data.counter }}

    &amp;

    {{ ct }}

    <button @click="data.counter++">+</button>

    <button @click="incr()">+</button>

    <hr>

    <button @click="show()">显示counter值</button>

   </div>

</template>

<style scoped>

</style>


6.3 条件和列表渲染
6.3.1 条件渲染
  • A- v-if 条件渲染
  • v-if='表达式'只会在指令的表达式返回真值时才被渲染

  • 也可以使用 v-elsev-if 添加一个“else 区块”。

  • 一个 v-else 元素必须跟在一个 v-if 元素后面,否则它将不会被识别。

<script setup type='module'>

    import {ref} from 'vue'

    let awesome = ref(true)

</script>

<template>

  <div>

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

    <h1 v-else>Oh no 😢</h1>

    <button @click="awesome = !awesome">Toggle</button>

  </div>

</template>

<style scoped>

</style>

  • B- v-show条件渲染扩展
  • 另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样:

  • 不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。

  • v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。

<script type="module" setup>

    import {ref} from 'vue'

    let awesome = ref(true)

</script>

<template>

  <div>

    <h1 id="ha"  v-show="awesome">Vue is awesome!</h1>

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

    <h1 id="hc"  v-else>Oh no 😢</h1>

    <button @click="awesome = !awesome">Toggle</button>

  </div>

</template>

<style scoped>

</style>

  • C- v-if vs v-show
  • v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。

  • v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

  • 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。

  • 总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。


6.3.2 列表渲染
  • A- v-for

我们可以使用 v-for 指令基于一个数组来渲染一个列表。

  • v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

  • v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。

  • B- 代码演示

<script type="module" setup>
    import {ref,reactive} from 'vue'
    let parentMessage= ref('产品')
    let items =reactive([
      {
        id:'item1',
        message:"薯片"
      },
      {
        id:'item2',
        message:"可乐"
      }
    ])
</script>

<template>
  <div>
    <ul>
      <!-- :key不写也可以 -->
      <li v-for='item in items' :key='item.id'>
        {{ item.message }}
      </li>
    </ul>

    <ul>
      <!-- index表示索引,当然不是非得使用index这个单词 -->
      <li v-for="(item, index) in items" :key="index">
        {{ parentMessage }} - {{ index }} - {{ item.message }}
      </li>
    </ul>
   
  </div>
</template> 

<style scoped>
</style>

  • C- 案例:实现购物车显示和删除购物项

<script type="module" setup>

    //引入模块
    import { reactive} from 'vue'
    //准备购物车数据,设置成响应数据
    const carts = reactive([{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}])

    //计算购物车总金额
    function compute(){
      let count = 0;
      for(let index in carts){
        count += carts[index].price*carts[index].number;
      }
      return count;
    }
    //删除购物项方法
    function removeCart(index){
      carts.splice(index,1);
    }
    
</script>

<template>
    <div>
        <table>
           <thead>
               <tr>
                  <th>序号</th>
                  <th>商品名</th>
                  <th>价格</th>
                  <th>数量</th>
                  <th>小计</th>
                  <th>操作</th>
               </tr>
           </thead>
           <tbody v-if="carts.length > 0">
               <!-- 有数据显示-->
               <tr v-for="cart,index in carts" :key="index">
                  <th>{{ index+1 }}</th>
                  <th>{{ cart.name }}</th>
                  <th>{{ cart.price + '元' }}</th>
                  <th>{{ cart.number }}</th>
                  <th>{{ cart.price*cart.number  + '元'}}</th>
                  <th> <button @click="removeCart(index)">删除</button> </th>
               </tr>
           </tbody>
           <tbody v-else>
               <!-- 没有数据显示-->
               <tr>
                  <td colspan="6">购物车没有数据!</td>
               </tr>
           </tbody>
        </table>
        购物车总金额: {{ compute() }} 元
    </div>
</template> 

<style scoped>
</style>

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐