Vue3视图渲染技术(2)

发布时间:2023年12月21日

我是南城余!阿里云开发者平台专家博士证书获得者!

欢迎关注我的博客!一同成长!

一名从事运维开发的worker,记录分享学习。

专注于AI,运维开发,windows Linux 系统领域的分享!

本章节对应知识库

南城余 — Java全栈 · 语雀

本内容来自尚硅谷课程,此处在知识库做了个人理解
————————————————
在这里插入图片描述

6.4 双向绑定

单项绑定和双向绑定

  • 单向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变不会同步更新到响应式数据
  • 双向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据
    • 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行
    • v-model专门用于双向绑定表单标签的value属性,语法为 v-model:value='',可以简写为 v-model=''
    • v-model还可以用于各种不同类型的输入,<textarea><select> 元素。
<script type="module" setup>

  //引入模块
  import { reactive,ref} from 'vue' 
  let hbs = ref([]); //装爱好的值
  let user = reactive({username:null,password:null,introduce:null,pro:null})   
  function login(){
    alert(hbs.value);
    alert(JSON.stringify(user));
  }
  function clearx(){
    //user = {};// 这中写法会将数据变成非响应的,应该是user.username=""
    user.username=''
    user.password=''
    user.introduce=''
    user.pro=''
    hbs.value.splice(0,hbs.value.length);;
  }
</script>

<template>
  <div>
      账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br>
      密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br>
      爱好: 
        吃 <input type="checkbox" name="hbs" v-model="hbs" value=""><input type="checkbox" name="hbs" v-model="hbs" value=""><input type="checkbox" name="hbs" v-model="hbs" value=""><input type="checkbox" name="hbs" v-model="hbs" value="">
      <br>
      简介:<textarea v-model="user.introduce"></textarea>
      <br>
      籍贯:
          <select v-model="user.pro">
            <option value="1"></option>
            <option value="2"></option>
            <option value="3"></option>
            <option value="4"></option>
            <option value="5"></option>
            <option value="6"></option>
          </select> 
      <br>
      <button @click="login()">登录</button> 
      <button @click="clearx()">重置</button>
      <hr>
      显示爱好:{{ hbs }}
      <hr>
      显示用户信息:{{ user }}
  </div> 
</template> 

<style scoped>
</style>

6.5 属性计算

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:

<script type="module" setup>
  //引入模块
  import { reactive,computed} from 'vue'
  const author = reactive({
    name: 'John Doe',
    books: [
      'Vue 2 - Advanced Guide',
      'Vue 3 - Basic Guide',
      'Vue 4 - The Mystery'
    ]
  })
 
</script>

<template>
  <div>
    <p>{{author.name}} Has published books?:</p>
    <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
  </div>
</template> 

<style scoped>
</style>
  • 这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于 author.books。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。

因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:

<script type="module" setup>
  //引入模块
  import { reactive,computed} from 'vue'
  const author = reactive({
    name: 'John Doe',
    books: [
      'Vue 2 - Advanced Guide',
      'Vue 3 - Basic Guide',
      'Vue 4 - The Mystery'
    ]
  })
  // 一个计算属性 ref
  const publishedBooksMessage = computed(() => {
    console.log("publishedBooksMessage")
    return author.books.length > 0 ? 'Yes' : 'No'
  })
  // 一个函数
  let hasBooks = ()=>{
    console.log("hasBooks")
    return author.books.length > 0?'Yes':'no'
  }

</script>

<template>
  <div>
    <p>{{author.name}} Has published books?:</p>
    <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
    <span>{{ hasBooks() }}</span><!-- 调用方法,每个标签都会调用一次 -->
    <span>{{ hasBooks() }}</span>

    <p>{{author.name}} Has published books?:</p>
    <span>{{ publishedBooksMessage }}</span><!-- 属性计算,属性值不变时,多个个标签只会调用一次 -->
    <span>{{ publishedBooksMessage }}</span>
  </div>
</template> 

<style scoped>
</style>

  • 我们在这里定义了一个计算属性 publishedBooksMessagecomputed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

  • Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新。

计算属性缓存 vs 方法

  • 若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果!

6.6 数据监听器

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:

  • watch主要用于以下场景:
    • 当数据发生变化时需要执行相应的操作
    • 监听数据变化,当满足一定条件时触发相应操作
    • 在异步操作前或操作后需要执行相应的操作

监控响应式数据(watch):

<script type="module" setup>
  //引入模块
  import { ref,reactive,watch} from 'vue'
 
  let firstname=ref('')
  let lastname=reactive({name:''})
  let fullname=ref('')

  //监听一个ref响应式数据
  watch(firstname,(newValue,oldValue)=>{
    console.log(`${oldValue}变为${newValue}`)
    fullname.value=firstname.value+lastname.name
  })
  //监听reactive响应式数据的指定属性
  watch(()=>lastname.name,(newValue,oldValue)=>{
    console.log(`${oldValue}变为${newValue}`)
    fullname.value=firstname.value+lastname.name
  })
  //监听reactive响应式数据的所有属性(深度监视,一般不推荐)
  //deep:true 深度监视
  //immediate:true 深度监视在进入页面时立即执行一次
  watch(()=>lastname,(newValue,oldValue)=>{
    // 此时的newValue和oldValue一样,都是lastname
    console.log(newValue)
    console.log(oldValue)
    fullname.value=firstname.value+lastname.name
  },{deep:true,immediate:false})
</script>

<template>
  <div>
    全名:{{fullname}} <br>
    姓氏:<input type="text" v-model="firstname"> <br>
    名字:<input type="text" v-model="lastname.name" > <br>
  </div>
</template> 

<style scoped>
</style>

监控响应式数据(watchEffect):

  • watchEffect默认监听所有的响应式数据
<script type="module" setup>
  //引入模块
  import { ref,reactive,watch, watchEffect} from 'vue'
 
  let firstname=ref('')
  let lastname=reactive({name:''})
  let fullname=ref('')

  //监听所有响应式数据
  watchEffect(()=>{
    //直接在内部使用监听属性即可!不用外部声明
    //也不需要,即时回调设置!默认初始化就加载!
    console.log(firstname.value)
    console.log(lastname.name)
    fullname.value=`${firstname.value}${lastname.name}`
  })
</script>

<template>
  <div>
    全名:{{fullname}} <br>
    姓氏:<input type="text" v-model="firstname"> <br>
    名字:<input type="text" v-model="lastname.name" > <br>
  </div>
</template> 

<style scoped>
</style>

watch vs. watchEffect

  • watchwatchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
    • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
    • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

6.7. Vue生命周期

6.7.1 生命周期简介

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码!

  • 周期图解:

  • 常见钩子函数
    • onMounted() 注册一个回调函数,在组件挂载完成后执行。
    • onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
    • onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用。
    • onBeforeMount() 注册一个钩子,在组件被挂载之前被调用。
    • onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
    • onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用。
6.7.2 生命周期案例
<script setup>

    import {ref,onUpdated,onMounted,onBeforeUpdate} from 'vue'
    
    let message =ref('hello')
   
    // 挂载完毕生命周期
    onMounted(()=>{
      console.log('-----------onMounted---------')
      let span1 =document.getElementById("span1")
      console.log(span1.innerText)
    })
    // 更新前生命周期
    onBeforeUpdate(()=>{
      console.log('-----------onBeforeUpdate---------')
      console.log(message.value)
      let span1 =document.getElementById("span1")
      console.log(span1.innerText)
    })
    // 更新完成生命周期
    onUpdated(()=>{
      console.log('-----------onUpdated---------')
      let span1 =document.getElementById("span1")
      console.log(span1.innerText)
    })
</script>

<template>
  <div>
    <span id="span1" v-text="message"></span> <br>
    <input type="text" v-model="message">
  </div>
  
</template>

<style scoped>
</style>

6.8 Vue组件

6.8.1 组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。组件就是实现应用中局部功能代码和资源的集合!在实际应用中,组件常常被组织成层层嵌套的树状结构:

  • 这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。

传统方式编写应用:

组件方式编写应用:

  • 组件化:对js/css/html统一封装,这是VUE中的概念

  • 模块化:对js的统一封装,这是ES6中的概念

  • 组件化中,对js部分代码的处理使用ES6中的模块化

6.8.2 组件化入门案例

案例需求: 创建一个页面,包含头部和菜单以及内容显示区域,每个区域使用独立组建!

1 准备vue项目

npm create vite
cd vite项目
npm install

2 安装相关依赖

npm install sass
npm install bootstrap

3 创建子组件 在src/components文件下 vscode需要安装Vetur插件,这样vue文件有快捷提示

  • Header.vue
<script setup type="module">
</script>

<template>
    <div>
        欢迎: xx <a href="#">退出登录</a>
    </div>
</template>

<style>
</style>
  • Navigator.vue
<script setup type="module">
</script>

<template>
    <!-- 推荐写一个根标签-->
    <div>
       <ul>
          <li>学员管理</li>
          <li>图书管理</li>
          <li>请假管理</li>
          <li>考试管理</li>
          <li>讲师管理</li>
       </ul>
    </div>
</template>

<style>
</style>
  • Content.vue
<script setup type="module">
</script>

<template>
    <div>
        展示的主要内容!
    </div>
</template>

<style>
</style>
  • App.vue 入口组件App引入组件
<script setup>
    import Header  from './components/Header.vue'
    import Navigator  from './components/Navigator.vue'
    import Content  from './components/Content.vue'
</script>

<template>
  <div>
     <Header class="header"></Header>
     <Navigator class="navigator"></Navigator>
     <Content class="content"></Content>
  </div>
</template>

<style scoped>
    .header{
       height: 80px;
       border: 1px solid red;
    }

    .navigator{
      width: 15%;
      height: 800px;
      display: inline-block;
      border: 1px blue solid;
      float: left;
    }

    .content{
      width: 83%;
      height: 800px;
      display: inline-block;
      border: 1px goldenrod solid;
      float: right;
    }

</style>

4 启动测试

npm run dev
6.8.3 组件之间传递数据
6.8.3.1 父传子

Vue3 中父组件向子组件传值可以通过 props 进行,具体操作如下:

  1. 首先,在父组件中定义需要传递给子组件的值,接着,在父组件的模板中引入子组件,同时在引入子组件的标签中添加 props 属性并为其设置需要传递的值。

  2. 在 Vue3 中,父组件通过 props 传递给子组件的值是响应式的。也就是说,如果在父组件中的传递的值发生了改变,子组件中的值也会相应地更新。

  • 父组件代码:App.vue
<script setup>

  import Son from './components/Son.vue'
  import {ref,reactive,toRefs} from 'vue'

  let message = ref('parent data!')
  let title = ref(42)

  function changeMessage(){
    message.value = '修改数据!'
    title.value++
  }
</script>

<template>
  <div>
    <h2>{{ message }}</h2>
    <hr>
    <!-- 使用子组件,并且传递数据! -->
    <Son :message="message" :title="title"></Son>
    <hr>
    <button @click="changeMessage">点击更新</button>
  </div>
</template>

<style scoped>
</style>
  • 子组件代码:Son.vue
<script setup type="module">
    import {ref,isRef,defineProps} from 'vue'
    //声明父组件传递属性值
    defineProps({
        message:String ,
        title:Number
    })
</script>

<template>
    <div>
    <div>{{ message }}</div>
    <div>{{ title }}</div>
    </div>
</template>

<style>
</style>
6.8.3.2 子传父
  • 父组件: App.vue
<script setup>
    import Son from './components/Son.vue'
    import {ref} from 'vue'

    let pdata = ref('')

    const padd = (data) => {
        console.log('2222');
        pdata.value =data;
    }

    //自定义接收,子组件传递数据方法! 参数为数据!
    const psub = (data) => {
        console.log('11111');
        pdata.value = data;
    }
</script>

<template>
    <div>
        <!-- 声明@事件名应该等于子模块对应事件名!调用方法可以是当前自定义!-->
        <Son @add="padd" @sub="psub"></Son>
        <hr>
        {{ pdata }}
    </div>
</template>



<style>
</style>
  • 子组件:Son.vue
<script setup>

    import {ref,defineEmits} from 'vue'

    //1.定义要发送给父组件的方法,可以1或者多个
    let emites = defineEmits(['add','sub']);

    let data = ref(1);

    function sendMsgToParent(){
        console.log('-------son--------');

        //2.出发父组件对应的方法,调用defineEmites对应的属性
        emites('add','add data!'+data.value)
        emites('sub','sub data!'+data.value)

        data.value ++;
    }
</script>

<template>
    <div>
      <button @click="sendMsgToParent">发送消息给父组件</button>
    </div>
</template>
  
6.8.3.3 兄弟传参
  • Navigator.vue: 发送数据到App.vue
<script setup type="module">
    import {defineEmits} from 'vue'
    const emits = defineEmits(['sendMenu']);
    //触发事件,向父容器发送数据
    function send(data){
        emits('sendMenu',data);
    }
</script>

<template>
    <!-- 推荐写一个根标签-->
    <div>
       <ul>
          <li @click="send('学员管理')">学员管理</li>
          <li @click="send('图书管理')">图书管理</li>
          <li @click="send('请假管理')">请假管理</li>
          <li @click="send('考试管理')">考试管理</li>
          <li @click="send('讲师管理')">讲师管理</li>
       </ul>
    </div>
</template>

<style>

</style>
  • App.vue: 发送数据到Content.vue
<script setup>
  
  import Header  from './components/Header.vue'
  import Navigator  from './components/Navigator.vue'
  import Content  from './components/Content.vue'

  import {ref} from "vue"
  //定义接受navigator传递参数
  var navigator_menu = ref('ceshi');

  const receiver = (data) =>{
    navigator_menu.value = data;
  }
</script>

<template>
  <div>
      <hr>
      {{ navigator_menu }}
      <hr>
     <Header class="header"></Header>
     <Navigator @sendMenu="receiver" class="navigator"></Navigator>
     <!-- 向子组件传递数据-->
     <Content class="content" :message="navigator_menu"></Content>
    </div>
</template>

<style scoped>
    .header{
       height: 80px;
       border: 1px solid red;
    }

    .navigator{
      width: 15%;
      height: 800px;
      display: inline-block;
      border: 1px blue solid;
      float: left;
    }

    .content{
      width: 83%;
      height: 800px;
      display: inline-block;
      border: 1px goldenrod solid;
      float: right;
    }

</style>
  • Content.vue
<script setup type="module">
    defineProps({
        message:String
    })
</script>

<template>
    
    <div>
        展示的主要内容!
        <hr>
        {{ message }}
    </div>
</template>

<style>
</style>
文章来源:https://blog.csdn.net/m0_63031112/article/details/135099575
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。