当前位置:首页 > 技术 > 正文内容

Vue 组件通信机制详解

访客 技术 2026年7月14日 1

父子组件数据传递

Props 单向数据流

父组件通过自定义属性向子组件传递数据,子组件通过 props 选项接收。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Props 示例</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="root">
    <user-card :username="userInfo.name" :age="userInfo.age"></user-card>
  </div>

  <script>
    const UserCard = {
      props: {
        username: {
          type: String,
          required: true
        },
        age: {
          type: Number,
          default: 18
        }
      },
      template: `
        <div class="card">
          <h3>{{ username }}</h3>
          <p>年龄: {{ age }}</p>
        </div>
      `
    };

    new Vue({
      el: '#root',
      data: {
        userInfo: {
          name: '张三',
          age: 25
        }
      },
      components: {
        'user-card': UserCard
      }
    });
  </script>
</body>
</html>

自定义事件向上通信

子组件通过 $emit 触发事件,父组件监听并接收数据。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>自定义事件</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <p>当前计数: {{ counter }}</p>
    <counter-btn @increment="addCount"></counter-btn>
  </div>

  <script>
    const CounterBtn = {
      data() {
        return {
          step: 5
        };
      },
      template: `
        <button @click="notifyParent">
          增加 {{ step }}
        </button>
      `,
      methods: {
        notifyParent() {
          this.$emit('increment', this.step);
        }
      }
    };

    new Vue({
      el: '#app',
      data: {
        counter: 0
      },
      methods: {
        addCount(value) {
          this.counter += value;
        }
      },
      components: {
        'counter-btn': CounterBtn
      }
    });
  </script>
</body>
</html>

跨层级组件通信

依赖注入 provide / inject

适用于祖先组件向后代组件传递数据,无需逐层传递。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>依赖注入</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="container">
    <level-one></level-one>
  </div>

  <script>
    const LevelThree = {
      inject: ['themeColor', 'getUser'],
      template: `
        <div :style="{ color: themeColor }">
          深层组件: {{ getUser() }}
        </div>
      `
    };

    const LevelTwo = {
      template: `
        <div>
          <level-three></level-three>
        </div>
      `,
      components: {
        'level-three': LevelThree
      }
    };

    const LevelOne = {
      template: `
        <div>
          <level-two></level-two>
        </div>
      `,
      components: {
        'level-two': LevelTwo
      }
    };

    new Vue({
      el: '#container',
      provide() {
        return {
          themeColor: '#1890ff',
          getUser: () => this.user.name
        };
      },
      data: {
        user: {
          name: '管理员',
          role: 'super'
        }
      },
      components: {
        'level-one': LevelOne
      }
    });
  </script>
</body>
</html>

组件实例访问

根实例访问 $root

任何组件可通过 $root 访问根实例的数据和方法。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>根实例访问</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="main">
    <deep-child></deep-child>
  </div>

  <script>
    const DeepChild = {
      template: `
        <div>
          <p>全局消息: {{ globalMsg }}</p>
          <button @click="triggerGlobal">调用根方法</button>
        </div>
      `,
      data() {
        return {
          globalMsg: this.$root.appTitle
        };
      },
      methods: {
        triggerGlobal() {
          this.$root.showAlert('来自深层组件的调用');
        }
      }
    };

    new Vue({
      el: '#main',
      data: {
        appTitle: 'Vue 应用'
      },
      methods: {
        showAlert(msg) {
          window.alert(msg);
        }
      },
      components: {
        'deep-child': DeepChild
      }
    });
  </script>
</body>
</html>

父组件访问 $parent

子组件通过 $parent 直接访问父组件实例。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>父组件访问</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="wrapper">
    <parent-box></parent-box>
  </div>

  <script>
    const InnerChild = {
      template: `
        <div>
          <span>父级数据: {{ parentData }}</span>
          <button @click="callParentMethod">执行父级操作</button>
        </div>
      `,
      data() {
        return {
          parentData: this.$parent.sharedState
        };
      },
      methods: {
        callParentMethod() {
          this.$parent.updateState('子组件触发更新');
        }
      }
    };

    const ParentBox = {
      data() {
        return {
          sharedState: '初始状态'
        };
      },
      template: `
        <div class="parent">
          <p>当前状态: {{ sharedState }}</p>
          <inner-child></inner-child>
        </div>
      `,
      methods: {
        updateState(newVal) {
          this.sharedState = newVal;
        }
      },
      components: {
        'inner-child': InnerChild
      }
    };

    new Vue({
      el: '#wrapper',
      components: {
        'parent-box': ParentBox
      }
    });
  </script>
</body>
</html>

子组件引用 $refs

父组件通过 ref 属性获取子组件实例,直接调用其方法或访问数据。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>组件引用</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <button @click="getChildInfo">获取子组件信息</button>
    <button @click="runChildTask">执行子组件任务</button>
    <info-panel ref="panel"></info-panel>
  </div>

  <script>
    const InfoPanel = {
      data() {
        return {
          content: '面板内容',
          version: '1.0.0'
        };
      },
      template: `
        <div class="panel">
          <h4>{{ content }}</h4>
          <p>版本: {{ version }}</p>
        </div>
      `,
      methods: {
        refreshData() {
          this.content = '数据已刷新 ' + Date.now();
          return '刷新完成';
        },
        getVersion() {
          return this.version;
        }
      }
    };

    new Vue({
      el: '#app',
      methods: {
        getChildInfo() {
          console.log('子组件数据:', this.$refs.panel.content);
          console.log('子组件版本:', this.$refs.panel.getVersion());
        },
        runChildTask() {
          const result = this.$refs.panel.refreshData();
          console.log('任务结果:', result);
        }
      },
      components: {
        'info-panel': InfoPanel
      }
    });
  </script>
</body>
</html>

特性继承与动态组件

$attrs 与 inheritAttrs

$attrs 包含父作用域中未被识别为 props 的特性绑定,inheritAttrs 控制是否将特性应用到根元素。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>特性继承</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="mount">
    <base-input 
      placeholder="请输入内容"
      data-id="1001"
      class="custom-input"
      @focus="onFocus"
    ></base-input>
  </div>

  <script>
    const BaseInput = {
      inheritAttrs: false,
      props: ['placeholder'],
      template: `
        <div class="input-wrapper">
          <input 
            v-bind="$attrs"
            :placeholder="placeholder"
          />
          <span>额外信息</span>
        </div>
      `,
      mounted() {
        console.log('未被props接收的特性:', this.$attrs);
      }
    };

    new Vue({
      el: '#mount',
      methods: {
        onFocus() {
          console.log('输入框获得焦点');
        }
      },
      components: {
        'base-input': BaseInput
      }
    });
  </script>
</body>
</html>

动态组件与 keep-alive

<component> 配合 is 属性实现动态切换,keep-alive 缓存组件状态避免重复渲染。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>动态组件</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <style>
    .tab-nav { display: flex; gap: 10px; margin-bottom: 20px; }
    .tab-nav button { padding: 8px 16px; cursor: pointer; }
    .tab-nav button.active { background: #1890ff; color: white; }
    .tab-content { padding: 20px; border: 1px solid #ddd; }
  </style>
</head>
<body>
  <div id="dynamic">
    <div class="tab-nav">
      <button 
        v-for="item in menuList" 
        :key="item.code"
        :class="{ active: current === item.code }"
        @click="switchTab(item.code)"
      >
        {{ item.label }}
      </button>
    </div>
    <keep-alive :include="cachedViews">
      <component :is="activeComponent" class="tab-content"></component>
    </keep-alive>
  </div>

  <script>
    const ViewHome = {
      name: 'ViewHome',
      data() {
        return { inputVal: '' };
      },
      template: `
        <div>
          <h3>首页</h3>
          <input v-model="inputVal" placeholder="输入会被缓存" />
        </div>
      `,
      activated() {
        console.log('首页被激活');
      },
      deactivated() {
        console.log('首页被缓存');
      }
    };

    const ViewList = {
      name: 'ViewList',
      template: '<div><h3>列表页</h3><ul><li>项目1</li><li>项目2</li></ul></div>'
    };

    const ViewSettings = {
      name: 'ViewSettings',
      template: '<div><h3>设置页</h3><p>配置选项</p></div>'
    };

    new Vue({
      el: '#dynamic',
      data: {
        current: 'home',
        menuList: [
          { code: 'home', label: '首页' },
          { code: 'list', label: '列表' },
          { code: 'settings', label: '设置' }
        ],
        cachedViews: ['ViewHome', 'ViewSettings']
      },
      computed: {
        activeComponent() {
          return 'view-' + this.current;
        }
      },
      methods: {
        switchTab(code) {
          this.current = code;
        }
      },
      components: {
        'view-home': ViewHome,
        'view-list': ViewList,
        'view-settings': ViewSettings
      }
    });
  </script>
</body>
</html>

异步更新队列

Vue 的 DOM 更新是异步执行的,如需在数据变化后操作 DOM,应使用 $nextTick

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>异步更新</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="async">
    <p ref="msgBox">{{ message }}</p>
    <button @click="updateAndCheck">修改并检测</button>
  </div>

  <script>
    new Vue({
      el: '#async',
      data: {
        message: '初始文本'
      },
      methods: {
        updateAndCheck() {
          this.message = '更新后的内容';
          
          // 同步获取,DOM 尚未更新
          console.log('同步获取:', this.$refs.msgBox.textContent);
          
          // 异步获取,DOM 已更新
          this.$nextTick(() => {
            console.log('nextTick 获取:', this.$refs.msgBox.textContent);
          });
        }
      }
    });
  </script>
</body>
</html>

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。