• vue重修004上部


    版权声明

    • 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。
    • 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的内容可能会随着时间的推移而过时或需要更新。
    • 若您是黑马程序员或相关权利人,如有任何侵犯版权的地方,请您及时联系我,我将立即予以删除或进行必要的修改。
    • 对于其他读者,请在阅读本博客内容时保持遵守相关法律法规和道德准则,谨慎参考,并自行承担因此产生的风险和责任。本博客中的部分观点和意见仅代表我个人,不代表黑马程序员的立场。

    组件的三大组成部分

    • 结构、样式、逻辑
      在这里插入图片描述

    scoped解决样式冲突

    • 默认情况:写在组件中的样式会 全局生效,很容易造成多个组件之间的样式冲突问题。
    1. 全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
    2. 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件

    scoped原理

    1. 当前组件内标签都被添加data-v-hash值 的属性
    2. css选择器都被添加 [data-v-hash值] 的属性选择器
    • 最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
      在这里插入图片描述

    2.代码演示

    • BaseOne.vue
    <template>
      <div class="base-one">
        BaseOne
      </div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style scoped>
    /* 
      1.style中的样式 默认是作用到全局的
      2.加上scoped可以让样式变成局部样式
    
      组件都应该有独立的样式,推荐加scoped(原理)
      -----------------------------------------------------
      scoped原理:
      1.给当前组件模板的所有元素,都会添加上一个自定义属性
      data-v-hash值
      data-v-5f6a9d56  用于区分开不通的组件
      2.css选择器后面,被自动处理,添加上了属性选择器
      div[data-v-5f6a9d56]
    */
    div{
      border: 3px solid blue;
      margin: 30px;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • BaseTwo.vue
    <template>
      <div class="base-one">
        BaseTwo
      </div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style scoped>
     div{
      border: 3px solid red;
      margin: 30px;
     }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • App.vue
    <template>
      <div id="app">
        <BaseOne></BaseOne>
        <BaseTwo></BaseTwo>
      </div>
    </template>
    
    <script>
    import BaseOne from './components/BaseOne'
    import BaseTwo from './components/BaseTwo'
    export default {
      name: 'App',
      components: {
        BaseOne,
        BaseTwo
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    组件data函数

    说明

    • 在之前的基础阶段的练习中,data使用的是对象的写法,这是因为方便基础练习,全局只有唯一一个data对象
    • data对象写法
    Vue.component('my-component', {
      data: {
        message: 'Hello, Vue!'
      },
      template: '
    {{ message }}
    '
    })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • data函数写法
    Vue.component('my-component', {
      data: function () {
        return {
          count: 0
        }
      },
      template: '
    {{ count }}
    '
    })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 在Vue.js中,一个组件的data选项必须是一个函数,而不是一个对象。为了确保每个组件实例都能维护独立的数据对象,避免数据共享和潜在的问题。
    • 每次创建一个新的组件实例时,Vue会调用这个函数来返回一个新的数据对象,以保证实例之间的数据隔离。
      在这里插入图片描述
    Vue.component('my-component', {
      data() {
        return {
          message: 'Hello, Vue!'
        }
      },
      template: '<div>{{ message }}</div>'
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    演示

    • BaseCount.vue
      <template>
        <div class="base-count">
          <button @click="count--">-</button>
          <span>{{ count }}</span>
          <button @click="count++">+</button>
        </div>
      </template>
      
      <script>
      export default {
        data: function () {
          return {
            count: 100,
          }
        },
      }
      </script>
      
      <style>
      .base-count {
        margin: 20px;
      }
      </style>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • App.vue
    <template>
      <div class="app">
        <baseCount></baseCount>
        <baseCount></baseCount>
        <baseCount></baseCount>
      </div>
    </template>
    
    <script>
    import baseCount from './components/BaseCount'
    export default {
      components: {
        baseCount,
      },
    }
    </script>
    <style>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    组件通信

    • 组件通信,就是指组件与组件之间的数据传递
    • 组件的数据是独立的,无法直接访问其他组件的数据。想使用其他组件的数据,就需要组件通信
      在这里插入图片描述

    组件关系分类

    在这里插入图片描述

    通信解决方案

    在这里插入图片描述

    父子通信流程

    1. 父组件通过 props 将数据传递给子组件
    2. 子组件利用 $emit 通知父组件修改更新
    • 原因:再vue设定中数据是有属主的,只有创建数据其的属主组件可以修改该数据
      在这里插入图片描述

    • 父向子传值步骤:

    1. 给子组件以添加属性的方式传值
    2. 子组件内部通过props接收
    3. 模板中直接使用 props接收的值
      在这里插入图片描述
    • 父组件代码
    <template>
      <div class="app" style="border: 3px solid #000; margin: 10px">
        我是APP组件
        <!-- 1.给组件标签,添加属性方式 赋值 -->
        <Son :title="myTitle"></Son>
      </div>
    </template>
    
    <script>
    import Son from './components/Son.vue'
    export default {
      name: 'App',
      data() {
        return {
          myTitle: '学前端',
        }
      },
      components: {
        Son,
      },
    }
    </script>
    
    <style>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 子组件代码
    <template>
      <div class="son" style="border:3px solid #000;margin:10px">
        <!-- 3.直接使用props的值 -->
        我是Son组件 {{title}}
      </div>
    </template>
    
    <script>
    export default {
      name: 'Son-Child',
      // 2.通过props来接受
      props:['title']
    }
    </script>
    
    <style>
    
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    子向父通信代

    1. $emit触发事件,给父组件发送消息通知
    2. 父组件监听$emit触发的事件
    3. 提供处理函数,在函数的形参中获取传过来的参数
      在这里插入图片描述

    • 子组件代码
    <template>
      <div class="son" style="border: 3px solid #000; margin: 10px">
        我是Son组件 {{ title }}
        <button @click="changeFn">修改title</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Son-Child',
      props: ['title'],
      methods: {
        changeFn() {
          // 通过this.$emit() 向父组件发送通知
          this.$emit('changTitle','son')
        },
      },
    }
    </script>
    
    <style>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 父组件代码
    <template>
      <div class="app" style="border: 3px solid #000; margin: 10px">
        我是APP组件
        <!-- 2.父组件对子组件的消息进行监听 -->
        <Son :title="myTitle" @changTitle="handleChange"></Son>
      </div>
    </template>
    
    <script>
    import Son from './components/Son.vue'
    export default {
      name: 'App',
      data() {
        return {
          myTitle: 'father',
        }
      },
      components: {
        Son,
      },
      methods: {
        // 3.提供处理函数,提供逻辑
        handleChange(newTitle) {
          this.myTitle = newTitle
        },
      },
    }
    </script>
    
    <style>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    props详解

    • 在Vue.js中,props(属性)是一种用于从父组件向子组件传递数据的机制。
    • props 定义:组件上 注册的一些 自定义属性
    • props 作用:向子组件传递数据
    • 特点
      1. 可以 传递 任意数量 的prop
      2. 可以 传递 任意类型 的prop

    在这里插入图片描述

    props校验

    • 作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
    • 语法:
      ① 类型校验
      ② 非空校验
      ③ 默认值
      ④ 自定义校验
    • 基础写法:
      在这里插入图片描述
    • 完整写法
      在这里插入图片描述
    • default和required一般不同时写(因为当时必填项时,肯定是有值的)
    • default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
    props: {
        w: {
          type: Number,
          required: true,
          default: 0,
          validator(val) {
            // console.log(val)
            if (val >= 100 || val <= 0) {
              console.error('传入的范围必须是0-100之间')
              return false
            } else {
              return true
            }
          },
        },
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    props&data、单向数据流

    • 共同点:都可以给组件提供数据

    • 区别

      • data 的数据是自己的 → 随便改
      • prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
    • 单向数据流:父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的
      在这里插入图片描述

    • 父组件代码

    <template>
      <div class="app">
        <BaseCount :count="count" @changeCount="handleChange"></BaseCount>
      </div>
    </template>
    
    <script>
    import BaseCount from './components/BaseCount.vue'
    export default {
      components:{
        BaseCount
      },
      data(){
        return {
          count:100
        }
      },
      methods:{
        handleChange(newVal){
          this.count = newVal
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 子组件代码
    <template>
      <div class="base-count">
        <button @click="handleSub">-</button>
        <span>{{ count }}</span>
        <button @click="handleAdd">+</button>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        count: {
          type: Number,
        },
      },
      methods: {
        handleSub() {
          this.$emit('changeCount', this.count - 1)
        },
        handleAdd() {
          this.$emit('changeCount', this.count + 1)
        },
      },
    }
    </script>
    
    <style>
    .base-count {
      margin: 20px;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    小黑记事本(组件版)

    基础组件结构

    在这里插入图片描述

    需求和实现思路

    需求说明:
    ① 拆分基础组件
    ② 渲染待办任务
    ③ 添加任务
    ④ 删除任务
    ⑤ 底部合计 和 清空功能
    ⑥ 持久化存储


    • 列表渲染思路分析:
    1. 提供数据:提供在公共的父组件 App.vue
    2. 通过父传子,将数据传递给TodoMain
    3. 利用v-for进行渲染

    • 添加功能思路分析:
    1. 收集表单数据 v-model
    2. 监听时间 (回车+点击 都要进行添加)
    3. 子传父,将任务名称传递给父组件App.vue
    4. 父组件接受到数据后 进行添加 unshift(自己的数据自己负责)

    • 删除功能思路分析:
    1. 监听时间(监听删除的点击)携带id
    2. 子传父,将删除的id传递给父组件App.vue
    3. 进行删除 filter (自己的数据自己负责)

    • 底部功能及持久化存储思路分析:
    1. 底部合计:父组件传递list到底部组件 —>展示合计
    2. 清空功能:监听事件 —> 子组件通知父组件 —>父组件清空
    3. 持久化存储:watch监听数据变化,持久化到本地

    完整代码

    App.vue

    <template>
      <!-- 主体区域 -->
      <section id="app">
        <TodoHeader @add="handleAdd"></TodoHeader>
        <TodoBody :list="list" @del="handleDel"></TodoBody>
        <TodoFoot :len="list.length" @clear="handleClear"></TodoFoot>
      </section>
    </template>
    
    <script>
    import TodoHeader from "@/components/TodoHeader";
    import TodoBody from "@/components/TodoBody";
    import TodoFoot from "@/components/TodoFoot";
    
    
    // 渲染功能:
    // 1.提供数据: 提供在公共的父组件 App.vue
    // 2.通过父传子,将数据传递给TodoMain
    // 3.利用 v-for渲染
    
    // 添加功能:
    // 1.手机表单数据  v-model
    // 2.监听事件(回车+点击都要添加)
    // 3.子传父,讲任务名称传递给父组件 App.vue
    // 4.进行添加 unshift(自己的数据自己负责)
    // 5.清空文本框输入的内容
    // 6.对输入的空数据 进行判断
    
    // 删除功能
    // 1.监听事件(监听删除的点击) 携带id
    // 2.子传父,讲删除的id传递给父组件的App.vue
    // 3.进行删除filter(自己的数据 自己负责)
    
    // 底部合计:父传子  传list.length 渲染
    // 清空功能:子传父  通知父组件 → 父组件进行更新
    // 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
    
    export default {
      components:{
        TodoHeader,
        TodoBody,
        TodoFoot
      },
      data () {
        return {
          //优先从本地存储读取
          list: JSON.parse(localStorage.getItem('list')) || [
            {id:1, name: "干饭"},
            {id:2, name: "干饭"},
            {id:3, name: "干饭"}
          ]
        }
      },
      methods:{
        handleAdd(newVal) {
          //console.log(newVal)
          this.list.unshift({
            id: +new Date(),
            name: newVal
          }
          )
        },
        handleDel(id) {
          this.list=this.list.filter(item=> item.id!==id)
        },
        handleClear() {
          this.list=[]
        }
      },
      watch:{
        list:{
          deep: true,
          handle(newValue) {
            localStorage.setItem('list',JSON.stringify(newValue))
          }
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    TodoHeader.vue

    <template>
      <!-- 输入框 -->
      <header class="header">
        <h1>小黑记事本</h1>
        <input placeholder="请输入任务" class="new-todo"  v-model="todoname" @keyup.enter="handleAdd"/>
        <button class="add" @click="handleAdd">添加任务</button>
      </header>
    
    </template>
    
    <script>
    export default {
      name: "TodoHeader",
      data() {
        return{
          todoname: ''
        }
      },
      methods:{
        handleAdd() {
          if(this.todoname.trim()===''){
            alert("任务名称为空")
            return
          }
          this.$emit("add",this.todoname)
          this.todoname=''
        }
      }
    }
    
    </script>
    
    <style scoped>
    
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    TodoBody.vue

    <template>
      <!-- 列表区域 -->
      <section class="main">
        <ul v-for="(item,index) in list" :key="item.id" class="todo-list">
          <li class="todo">
            <div class="view">
              <span class="index">{{index+1}}.</span> <label>{{item.name}}</label>
              <button class="destroy" @click="handeleDel(item.id)"></button>
            </div>
          </li>
        </ul>
      </section>
    </template>
    
    <script>
    export default {
      name: "TodoBody",
      props: {
        list: {
          type: Array
        }
      },
      methods:{
        handeleDel(id) {
          this.$emit('del',id)
        }
      }
    }
    </script>
    
    <style scoped>
    
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    TodoFoot.vue

    <template>
      <!-- 统计和清空 -->
      <footer class="footer">
        <!-- 统计 -->
        <span class="todo-count">合 计:<strong> {{len}} </strong></span>
        <!-- 清空 -->
        <button class="clear-completed" @click="clear">
          清空任务
        </button>
      </footer>
    
    </template>
    
    <script>
    export default {
      name: "TodoFoot",
      props:{
        len: Number
      },
      methods:{
        clear() {
          this.$emit('clear')
        }
      }
    }
    </script>
    
    <style scoped>
    
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  • 相关阅读:
    Java基础进阶多线程-四种创建方式
    VRP基础及操作
    判断是否相等 hasChanged
    数据库设计与数据库范式
    计算机网络第4章(网际层)----总结1
    伦敦金今日走势怎么做?
    【Vue】实现商品列表的无限加载
    接口测试入门必会知识总结(学习笔记)
    并行多核体系结构基础 Yan Solihin 第4章 针对链式数据结构的并行 摘录
    ffmpeg视频截取时DTS乱序导致无法截取的问题
  • 原文地址:https://blog.csdn.net/yang2330648064/article/details/133513195