• Web前端-Vue2+Vue3基础入门到实战项目-Day4(组件的三大组成部分, 组件通信, 案例-组件版小黑记事本, 进阶语法)


    组件的三大组成部分(结构/样式/逻辑)

    scoped样式冲突

    • 全局样式: 默认的style样式, 会作用于全局
    • 局部样式: 加上scoped属性的style样式, 只会作用于当前组件
    • scoped原理:
      1. 给当前组件模板的所有元素, 添加一个自定义属性
        data-v-hash值: 根据hash值区分不同的组件
      2. css选择器后面, 被自动处理, 添加上了属性选择器
        div[data-v-hash]
    <template>
      <div class="base-one">
        BaseOne
      div>
    template>
    
    <script>
    export default {
    
    }
    script>
    
    <style scoped>
    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

    data是一个函数

    • data必须是一个函数 -> 保证每个组件实例, 维护独立的一个数据对象
    • 每次创建新的组件实例, 都会新执行一次data函数, 得到一个新对象
    <template>
      <div class="base-count">
        <button @click="count--">-button>
        <span>{{ count }}span>
        <button @click="count++">+button>
      div>
    template>
    
    <script>
    export default {
      data() {
        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

    组件通信

    组件通信语法

    • 组件关系和对应的通信方案
      • 父子关系: props, $emit
      • 非父子关系: provide, injecteventbus
      • 通用方案: vuex
    • 父子通信方案的核心流程
      • 父传子props:
        1. 父中给子添加属性传值
        2. 子props接收
        3. 使用
      • 子传父$emit
        1. 子$emit发送消息
        2. 父中给子添加消息监听
        3. 父中实现处理函数

    父传子

    <template>
      <div class="app" style="border: 3px solid #000; margin: 10px">
        我是APP组件
        
        <Son :title="myTitle">Son>
      div>
    template>
    
    <script>
    import Son from "./components/Son.vue"
    export default {
      name: "App",
      components: {
        Son,
      },
      data() {
        return {
          myTitle: "学前端,就来黑马程序员",
        }
      },
    }
    script>
    
    <style>
    style>
    
    
    <template>
      <div class="son" style="border:3px solid #000;margin:10px">
        
        我是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
    • 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

    子传父

    <template>
      <div class="app" style="border: 3px solid #000; margin: 10px">
        我是APP组件
        
        <Son :title="myTitle" @changeTitle="handleChange">Son>
      div>
    template>
    
    <script>
    import Son from "./components/Son.vue"
    export default {
      name: "App",
      components: {
        Son,
      },
      data() {
        return {
          myTitle: "学前端,就来黑马程序员",
        }
      },
      methods: {
        // 3. 提供处理函数, 提供逻辑
        handleChange(newTitle){
          this.myTitle = newTitle
        }
      }
    }
    script>
    
    <style>
    style>
    
    
    <template>
      <div class="son" style="border:3px solid #000;margin:10px">
        我是Son组件 {{title}}
        <button @click="changeFn">修改titlebutton>
      div>
    template>
    
    <script>
    export default {
      name: 'Son-Child',
      props: ['title'],
      methods: {
        changeFn(){
          // 1. 通过$emit, 向父组件发送消息通知
          this.$emit('changeTitle', "传智教育")
        }
      }
    }
    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

    props详解

    什么是props

    • 定义: 组件上注册的一些自定义属性
    • 作用: 向子组件传递数据
    • 特点:
      • 可以传递任意数量的prop
      • 可以传递任意类型的prop

    父组件

    <template>
      <div class="app">
        <UserInfo
          :username="username"
          :age="age"
          :isSingle="isSingle"
          :car="car"
          :hobby="hobby"
        >UserInfo>
      div>
    template>
    
    <script>
    import UserInfo from './components/UserInfo.vue'
    export default {
      data() {
        return {
          username: '小帅',
          age: 28,
          isSingle: true,
          car: {
            brand: '宝马',
          },
          hobby: ['篮球', '足球', '羽毛球'],
        }
      },
      components: {
        UserInfo,
      },
    }
    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

    子组件

    <template>
      <div class="userinfo">
        <h3>我是个人信息组件h3>
        <div>姓名:{{username}} div>
        <div>年龄:{{age}} div>
        <div>是否单身:{{isSingle ? '是' : '否'}} div>
        <div>座驾:{{car.brand}} div>
        <div>兴趣爱好:{{hobby.join(', ')}} div>
      div>
    template>
    
    <script>
    export default {
      props: ['username', 'age', 'isSingle', 'car', 'hobby']
    }
    script>
    
    <style>
    .userinfo {
      width: 300px;
      border: 3px solid #000;
      padding: 20px;
    }
    .userinfo > div {
      margin: 20px 10px;
    }
    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

    props检验

    • 作用: 为组件的prop指定验证要求, 不符合要求, 控制台会有错误提示
    • 语法
      1. 类型检验
      2. 非空检验
      3. 默认值
      4. 自定义检验

    父组件

    <template>
      <div class="app">
        <BaseProgress :w="width">BaseProgress>
      div>
    template>
    
    <script>
    import BaseProgress from './components/BaseProgress.vue'
    export default {
      data() {
        return {
          width: 23,
        }
      },
      components: {
        BaseProgress,
      },
    }
    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="base-progress">
        <div class="inner" :style="{ width: w + '%' }">
          <span>{{ w }}%span>
        div>
      div>
    template>
    
    <script>
    export default {
      // props: ["w"],
      // 1.基础写法(类型校验)
      // props: {
      //   w: Number // Number String Boolean Array Object
      // }
      // 2.完整写法(类型、是否必填、默认值、自定义校验)
      props: {
        w: {
          type: Number,
          // required: true
          default: 0,
          validator (value) {
            if(value >= 0 && value <= 100){
              return true
            }
            console.error('传入的prop w, 必须是0-100的数字')
            return false
          }
        }
      }
    }
    script>
    
    <style scoped>
    .base-progress {
      height: 26px;
      width: 400px;
      border-radius: 15px;
      background-color: #272425;
      border: 3px solid #272425;
      box-sizing: border-box;
      margin-bottom: 30px;
    }
    .inner {
      position: relative;
      background: #379bff;
      border-radius: 15px;
      height: 25px;
      box-sizing: border-box;
      left: -3px;
      top: -2px;
    }
    .inner span {
      position: absolute;
      right: 0;
      top: 26px;
    }
    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

    props与data的区别

    • 共同点: 都可以给组件提供数据
    • 区别:
      • data的数据是自己的 -> 随便改
      • prop的数据是外部的 -> 不能直接改, 要遵循单向数据流
    • 单向数据流: 父级prop的数据更新, 会向下流动, 影响子组件. 这个数据流动是单向的.

    父组件

    <template>
      <div class="app">
        <BaseCount 
          @changeCount="handleChange"
          :count="count">
        BaseCount>
      div>
    template>
    
    <script>
    import BaseCount from './components/BaseCount.vue'
    export default {
      components:{
        BaseCount
      },
      data(){
        return {
          count:100
        }
      },
      methods:{
        handleChange(value){
          this.count = value
        }
      }
    }
    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

    子组件

    <template>
      <div class="base-count">
        <button @click="handleSub">-button>
        <span>{{ count }}span>
        <button @click="handleAdd">+button>
      div>
    template>
    
    <script>
    export default {
      // 1.自己的数据随便修改  (谁的数据 谁负责)
      // data () {
      //   return {
      //     count: 100,
      //   }
      // },
      // 2.外部传过来的数据 不能随便修改
    
      // 单向数据流: 父组件的prop更新, 会单向向下流动, 影响到子组件.
      props: {
        count: Number
      },
      methods: {
        handleAdd(){
          this.$emit('changeCount', this.count+1)
        },
        handleSub(){
          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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    非父子(扩展)

    事件总线 (event bus)

    • 作用: 非父子组件之间, 进行简易消息传递(复杂场景 -> vuex)
    • 语法:
      1. 创建一个都能访问的事件总线(空vue实例) -> utils/EventBus.js
      import Vue from 'vue'
      const Bus  =  new Vue()
      export default Bus
      
      • 1
      • 2
      • 3
      1. A组件(接受方), 监听Bus实例的事件
      created() {
        Bus.$on('sendMsg', (msg) => {
          // console.log(msg)
          this.msg = msg
        })
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      1. B组件(发送方), 触发Bus实例的事件
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
      
      • 1

    provide - inject

    • 作用: 跨层级共享数据
    • 语法:
      1. 父组件provide提供数据
      provide() {
        return {
          // 简单类型 是非响应式的
          color: this.color,
          // 复杂类型 是响应式的
          userInfo: this.userInfo,
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      1. 子/孙组件 inject 取值使用
      <script>
      export default {
        inject: ['color', 'userInfo'],
      }
      </script>
      
      • 1
      • 2
      • 3
      • 4
      • 5

    案例 - 小黑记事本(组件版)

    核心步骤

    1. 拆分基础组件
      新建组件 -> 拆分存放结构 -> 导入注册使用
    2. 渲染待办任务
      提供数据(公共父组件) -> 父传子传递list -> v-for渲染
    3. 添加任务
      收集数据v-model -> 监听事件 -> 子传父传递任务 -> 父组件unshift
    4. 删除任务
      监听删除id -> 子传父传递id -> 父组件filter删除
    5. 底部合计和清空功能
      底部合计: 父传子list -> 合计展示
      清空功能: 监听点击 -> 子传父通知父组件 -> 父组件清空
    6. 持久化存储: watch监视数据变化, 持久化到本地

    App.vue

    <template>
      
      <section id="app">
        <TodoHeaderVue @add="handleAdd">TodoHeaderVue>
        <TodoMainVue @del="handleDel" :list="list">TodoMainVue>
        <TodoFooterVue @clear="handleClear" :list="list">TodoFooterVue>
      section>
    template>
    
    <script>
    import TodoHeaderVue from './components/TodoHeader.vue'
    import TodoMainVue from './components/TodoMain.vue'
    import TodoFooterVue from './components/TodoFooter.vue'
    
    // 渲染功能:
    // 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
    
    // 底部合计: 父传子list -> 渲染
    // 清空功能: 子传父 通知父组件 -> 父组件进行清空
    // 持久化存储: watch深度监视list的变化 -> 往本地存储 -> 进入页面优先读取本地存储
    export default {
      data () {
        return {
          list: JSON.parse(localStorage.getItem('list')) || [
            {id: 1, name: '打篮球'},
            {id: 2, name: '看电影'},
            {id: 3, name: '逛街'},
          ]
        }
      },
      methods: {
        handleAdd(todoName){
          this.list.unshift({
            id: +new Date(),
            name: todoName
          })
        },
        handleDel(id){
          this.list = this.list.filter(item => item.id!==id)
        },
        handleClear(){
          this.list = []
        }
      },
      watch: {
        list: {
          deep: true,
          handler(newValue){
            localStorage.setItem('list', JSON.stringify(newValue))
          }
        }
      },
      components: {
        TodoHeaderVue,
        TodoMainVue,
        TodoFooterVue
      }
    }
    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

    TodoHeader.vue

    <template>
      <div>
        
        <header class="header">
          <h1>小黑记事本h1>
          <input 
            v-model.trim="todoName" 
            @keyup.enter="handleAdd" placeholder="请输入任务" class="new-todo"/>
          <button @click="handleAdd" class="add">添加任务button>
        header>
      div>
    template>
    
    <script>
    export default {
      data(){
        return {
          todoName: ''
        }
      },
      methods: {
        handleAdd(){
          if(this.todoName.trim() === ''){
            alert('任务名称不能为空')
            return 
          }
          this.$emit('add', this.todoName)
          this.todoName = ''
        }
      }
    }
    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

    TodoMain.vue

    <template>
      <div>
        
        <section class="main">
          <ul class="todo-list">
            <li class="todo" v-for="(item, index) in list" :key="item.id">
              <div class="view">
                <span class="index"> {{index+1}}. span> 
                <label> {{item.name}} label>
                <button @click="handleDel(item.id)" class="destroy">button>
              div>
            li>
          ul>
        section>
      div>
    template>
    
    <script>
    export default {
      props: {
        list: Array
      },
      methods: {
        handleDel(id){
          this.$emit('del', id)
        }
      }
    }
    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

    TodoFooter.vue

    <template>
      <div>
        
        <footer class="footer">
          
          <span class="todo-count">合 计:<strong> {{list.length}} strong>span>
          
          <button @click="clear" class="clear-completed">
            清空任务
          button>
        footer>
      div>
    template>
    
    <script>
    export default {
      props: {
        list: Array
      },
      methods: {
        clear(){
          this.$emit('clear')
        }
      }
    }
    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

    进阶语法

    v-model详解

    v-model原理

    • 原理: v-model本质上是一个语法糖. 例如应用在输入框上, 就是value属性和input事件的合写.
    • 作用: 提供数据的双向绑定
      1. 数据发生变化, 视图自动变化: value
      2. 视图发生变化, 数据自动变化: @input
    • $event: 用在模板中, 获取事件的形参
    <div class="app">
      <input v-model="msg1" type="text" /> <br />
      <input :value="msg2" @input="msg2 = $event.target.value" type="text" >
    div>
    
    • 1
    • 2
    • 3
    • 4

    表单类组件封装

    实现子组件和父组件数据的双向绑定

    • 父传子: 数据 由父组件props传递, v-model拆解绑定数据
    • 子传父: 监听输入, 子传父传值给父组件修改

    父组件

    <template>
      <div class="app">
        <BaseSelect :selectId="selectId" @change="selectId = $event">BaseSelect>
      div>
    template>
    
    <script>
    import BaseSelect from './components/BaseSelect.vue'
    export default {
      data() {
        return {
          selectId: '102',
        }
      },
      components: {
        BaseSelect,
      },
      methods: {
        
      }
    }
    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>
        <select :value="selectId" @change="handleChange">
          <option value="101">北京option>
          <option value="102">上海option>
          <option value="103">武汉option>
          <option value="104">广州option>
          <option value="105">深圳option>
        select>
      div>
    template>
    
    <script>
    export default {
      props: {
        selectId: String
      },
      methods: {
        handleChange(e){
          this.$emit('change', e.target.value)
        }
      }
    }
    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

    v-model简化代码

    父组件v-model简化实现子组件和父组件数据双向绑定

    • 子组件: props通过value接收, 事件触发input
    • 父组件: v-model绑定数据 (:value + @input)

    父组件

    <template>
      <div class="app">
        <BaseSelect v-model="selectId">BaseSelect>
      div>
    template>
    
    <script>
    import BaseSelect from './components/BaseSelect.vue'
    export default {
      data() {
        return {
          selectId: '102',
        }
      },
      components: {
        BaseSelect,
      },
    }
    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>
        <select :value="value" @change="handleChange">
          <option value="101">北京option>
          <option value="102">上海option>
          <option value="103">武汉option>
          <option value="104">广州option>
          <option value="105">深圳option>
        select>
      div>
    template>
    
    <script>
    export default {
      props: {
        value: String
      },
      methods: {
        handleChange(e){
          this.$emit('input', e.target.value)
        }
      }
    }
    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

    sync修饰符

    • 作用: 实现子组件与父组件的数据双向绑定, 简化代码
    • 特点: prop属性名, 可以自定义, 非固定为value
    • 场景: 封装弹框类的基础组件, visible属性 true显示 false隐藏
    • 本质: :属性名 + @update:属性名

    父组件

    <template>
      <div class="app">
        <button @click="isShow = true">退出按钮button>
        <BaseDialog :visible.sync="isShow">BaseDialog>
      div>
    template>
    
    <script>
    import BaseDialog from "./components/BaseDialog.vue"
    export default {
      data() {
        return {
          isShow: false
        }
      },
      methods: {
        
      },
      components: {
        BaseDialog,
      },
    }
    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

    子组件

    <template>
      <div v-show="visible" class="base-dialog-wrap">
        <div class="base-dialog">
          <div class="title">
            <h3>温馨提示:h3>
            <button @click="close" class="close">xbutton>
          div>
          <div class="content">
            <p>你确认要退出本系统么?p>
          div>
          <div class="footer">
            <button>确认button>
            <button>取消button>
          div>
        div>
      div>
    template>
    
    <script>
    export default {
      props: {
        visible: Boolean
      },
      methods: {
        close(){
          this.$emit('update:visible', false)
        }
      }
    }
    script>
    
    <style scoped>
    .base-dialog-wrap {
      width: 300px;
      height: 200px;
      box-shadow: 2px 2px 2px 2px #ccc;
      position: fixed;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      padding: 0 10px;
    }
    .base-dialog .title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 2px solid #000;
    }
    .base-dialog .content {
      margin-top: 38px;
    }
    .base-dialog .title .close {
      width: 20px;
      height: 20px;
      cursor: pointer;
      line-height: 10px;
    }
    .footer {
      display: flex;
      justify-content: flex-end;
      margin-top: 26px;
    }
    .footer button {
      width: 80px;
      height: 40px;
    }
    .footer button:nth-child(1) {
      margin-right: 10px;
      cursor: pointer;
    }
    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

    ref和$refs

    • 作用: 通过ref$refs可以获取dom元素和组件实例
    • 使用:
      1. 目标组件 - 添加ref属性
      <div ref="test">div>
      
      • 1
      1. 通过this.$refs.ref属性值获取目标组件
      this.$refs.test
      
      • 1
    • 获取dom
      <div ref="mychart" class="base-chart-box">子组件</div>
      
      const myChart = echarts.init(this.$refs.mychart)
      
      • 1
      • 2
      • 3
    • 获取组件
      父组件
      <template>
        <div class="app">
          <BaseForm ref="baseFrom">BaseForm>
          <button @click="handleGet">获取数据button>
          <button @click="handleReset">重置数据button>
        div>
        
      template>
      
      <script>
      import BaseForm from './components/BaseForm.vue'
      export default {
        components: {
          BaseForm,
        },
        methods: {
          handleGet(){
            console.log(this.$refs.baseFrom.getValues())
          },
          handleReset(){
            this.$refs.baseFrom.resetValues()
          }
        }
      }
      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="app">
          <div>
            账号: <input v-model="username" type="text">
          div>
          <div>
            密码: <input v-model="password" type="text">
          div>
        div>
      template>
      
      <script>
      export default {
        data() {
          return {
            username: 'admin',
            password: '123456',
          }
        },
        methods: {
          getValues() {
            return {
              username: this.username,
              password: this.password
            }
          },
          resetValues() {
            this.username = ''
            this.password = ''
            console.log('重置表单数据成功');
          },
        }
      }
      script>
      
      <style scoped>
      .app {
        border: 2px solid #ccc;
        padding: 10px;
      }
      .app div{
        margin: 10px 0;
      }
      .app div button{
        margin-right: 8px;
      }
      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

    $nextTick

    • Vue是异步更新DOM的
    • $nextTick: 在DOM更新完成之后做某件事
    <template>
      <div class="app">
        <div v-if="isShowEdit">
          <input type="text" v-model="editValue" ref="inp" />
          <button>确认button>
        div>
        <div v-else>
          <span>{{ title }}span>
          <button @click="handleEdit">编辑button>
        div>
      div>
    template>
    
    <script>
    export default {
      data() {
        return {
          title: '大标题',
          isShowEdit: false,
          editValue: '',
        }
      },
      methods: {
        handleEdit(){
          // 1. 显示输入框 (异步dom更新)
          this.isShowEdit = true
          // 2. 让输入框显示焦点
          // console.log(this.$refs.inp) // undefined
          
          this.$nextTick(()=>{
            this.$refs.inp.focus()
          })
            
        }
      },
    }
    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

    来源

    黑马程序员. Vue2+Vue3基础入门到实战项目

  • 相关阅读:
    自动驾驶研究生就业如何,自动驾驶的研究方向
    【奇想星球】重磅!我们的AIGC共创社区平台上线了!
    聊聊计算机之Intel CPU的MESI协议
    UWB 定位技术方案选择
    MySQL 事务常见面试题总结 | JavaGuide 审核中
    《大模型进化论》第2章2节:从神经网络到预训练——近十年的显著突破与进展
    APP采用原生开发还是混合开发好?
    深度解析BERT:从理论到Pytorch实战
    搜题公众号搭建
    创建镜像发布到镜像仓库【不依赖docker环境】
  • 原文地址:https://blog.csdn.net/Y_cen/article/details/133834889