前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

文章学习导航
面试总览

30 秒速记

  • 父传属性,子发事件,单向数据来源清楚
  • 插槽复用结构,暴露必要变化入口
  • 深层共享可用注入,跨区域状态再评估存储
  • 引用用于有限命令,不成为任意修改后门
  • 订阅与事件总线都要管理生命周期

组件化实践的重点,是让组件之间怎样交换信息一眼能看明白。 父级提供输入,子级发事件表达操作,插槽把可变内容交给使用方;深层共享和全局状态再选择相应机制。引用适合聚焦等少量命令,不应让父级随意改子级内部数据。通信方式越隐含,排查越难,所以优先选择最局部、最明确的连接,并给外部订阅安排清理。

版本校准: 本文出现 Object.defineProperty、Dep、Watcher 和双端 Diff 时,主要描述 Vue 2 实现;Vue 3 使用 Proxy、effect 与新的渲染器路径。Vue 2 已于 2023-12-31 结束维护,新项目应以 Vue 3 为基线,旧项目参考 Vue 2 EOL 官方说明 制定迁移与安全策略。

# vue组件化实践

# 组件化

vue组件系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界 面都可以抽象为一个组件树。组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护 性,便于多人协同开发。

# 组件通信常用方式

# 1. props

父给子传值

 
// child
props: { msg: String }
// parent
<HelloWorld msg="Welcome to Your Vue.js App"/>
@前端进阶之旅: 代码已经复制到剪贴板

# 2. 自定义事件

子给父传值

  
// child this.$emit('add', good)
// parent
<Cart @add="cartAdd($event)"></Cart>
@前端进阶之旅: 代码已经复制到剪贴板

# 3. 事件总线

任意两个组件之间传值常用事件总线 或 vuex的方式。

  // Bus:事件派发、监听和回调管理 class Bus {
constructor(){ this.callbacks = {}
  }
  $on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn) }
$emit(name, args){ if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(args)) }
} }
// main.js
Vue.prototype.$bus = new Bus()
// child1
this.$bus.$on('foo', handle) // child2 this.$bus.$emit('foo')
@前端进阶之旅: 代码已经复制到剪贴板

实践中通常用Vue代替Bus,因为Vue已经实现了相应接口

# 4. vuex

创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更。

# 5. $parent/$root

兄弟组件之间通信可通过共同祖辈搭桥,$parent或$root。

 
// brother1
this.$parent.$on('foo', handle)
// brother2 
this.$parent.$emit('foo')
@前端进阶之旅: 代码已经复制到剪贴板

# 6. $children

父组件可以通过$children访问子组件实现父子通信。

 // parent 
 this.$children[0].xx = 'xxx'
@前端进阶之旅: 代码已经复制到剪贴板

# 7. $attrs/$listeners

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有 声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 v- bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

 
// child:并未在props中声明foo 
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>
@前端进阶之旅: 代码已经复制到剪贴板

# 8. refs

获取子节点引用

 
// parent
<HelloWorld ref="hw"/>
mounted() { 
  this.$refs.hw.xx = 'xxx'
}
@前端进阶之旅: 代码已经复制到剪贴板

# 9. provide/inject

能够实现祖先和后代之间传值

   
// ancestor
provide() {
  return {foo: 'foo'}
}
// descendant
inject: ['foo']
@前端进阶之旅: 代码已经复制到剪贴板

实例

// index.vue
<template>
  <div>
    <h2>组件通信</h2>
    <!-- props, 自定义事件 -->
    <Child1 msg="some msg from parent" @some-event="onSomeEvent"></Child1>
    <!-- 事件总线 -->
    <Child2 msg="other msg"></Child2>
  </div>
</template>

<script>
  import Child1 from '@/components/communication/Child1.vue'
  import Child2 from '@/components/communication/Child2.vue'
  
  export default {
    components: {
      Child1, Child2,
      // Child3: () => import('./Child3.vue')
    },
    methods: {
      onSomeEvent(msg) {
        console.log('Communition:', msg);
      }
    },
    mounted () {
      // $children持有所有自定义组件
      // 它不保证顺序
      console.log(this.$children);
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板
// child1.vue
<template>
  <div @click="$emit('some-event', 'msg from child1')">
    <h3>child1</h3>
    <p>{{msg}}</p>
  </div>
</template>

<script>
  export default {
    props: {
      msg: {
        type: String,
        default: ''
      },
    },
    mounted () {
      // this.$bus.$on('event-from-child2', msg => {
      //   console.log('Child1:', msg);
      // });
      this.$parent.$on('event-from-child2', msg => {
        console.log('Child1:', msg);
      });
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板
// child2.vue
<template>
  <div>
    <!-- 展开$attrs对象 -->
    <h3 v-bind="$attrs">child2</h3>
    <button @click="sendToChild1">给child1发送消息</button>
    <!-- $attrs -->
    <p>{{$attrs.msg}}</p>
    <!-- inject -->
    <p>{{foo}}</p>
  </div>
</template>

<script>
  export default {
    inheritAttrs: false,
    inject: ['foo'],
    methods: {
      sendToChild1() {
        // 利用事件总线发送事件
        // this.$bus.$emit('event-from-child2', 'some msg from child2')
        this.$parent.$emit('event-from-child2', 'some msg from child2')
      }
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板

# 插槽

插槽语法是Vue 实现的内容分发 API,用于复合组件开发。该技术在通用组件库开发中有大量应用

# 1. 匿名插槽

 
// comp1
<div>
  <slot></slot>
</div>
// parent
<comp>hello</comp>
@前端进阶之旅: 代码已经复制到剪贴板

# 2. 具名插槽

将内容分发到子组件指定位置

 
// comp2
<div>
<slot></slot>
<slot name="content"></slot>
</div>
// parent
<Comp2>
<!-- 默认插槽用default做参数 -->
<template v-slot:default>具名插槽</template> <!-- 具名插槽用插槽名做参数 -->
<template v-slot:content>内容...</
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
      • 组件化
      • 组件通信常用方式
        • 1. props
        • 2. 自定义事件
        • 3. 事件总线
        • 4. vuex
        • 5. $parent/$root
        • 6. $children
        • 7. $attrs/$listeners
        • 8. refs
        • 9. provide/inject
      • 插槽
      • 组件化实战
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

Vue组件化实践|原理篇

文章学习导航
面试总览

30 秒速记

  • 父传属性,子发事件,单向数据来源清楚
  • 插槽复用结构,暴露必要变化入口
  • 深层共享可用注入,跨区域状态再评估存储
  • 引用用于有限命令,不成为任意修改后门
  • 订阅与事件总线都要管理生命周期

组件化实践的重点,是让组件之间怎样交换信息一眼能看明白。 父级提供输入,子级发事件表达操作,插槽把可变内容交给使用方;深层共享和全局状态再选择相应机制。引用适合聚焦等少量命令,不应让父级随意改子级内部数据。通信方式越隐含,排查越难,所以优先选择最局部、最明确的连接,并给外部订阅安排清理。

版本校准: 本文出现 Object.defineProperty、Dep、Watcher 和双端 Diff 时,主要描述 Vue 2 实现;Vue 3 使用 Proxy、effect 与新的渲染器路径。Vue 2 已于 2023-12-31 结束维护,新项目应以 Vue 3 为基线,旧项目参考 Vue 2 EOL 官方说明 制定迁移与安全策略。

# vue组件化实践

# 组件化

vue组件系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界 面都可以抽象为一个组件树。组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护 性,便于多人协同开发。

# 组件通信常用方式

# 1. props

父给子传值

 
// child
props: { msg: String }
// parent
<HelloWorld msg="Welcome to Your Vue.js App"/>
@前端进阶之旅: 代码已经复制到剪贴板

# 2. 自定义事件

子给父传值

  
// child this.$emit('add', good)
// parent
<Cart @add="cartAdd($event)"></Cart>
@前端进阶之旅: 代码已经复制到剪贴板

# 3. 事件总线

任意两个组件之间传值常用事件总线 或 vuex的方式。

  // Bus:事件派发、监听和回调管理 class Bus {
constructor(){ this.callbacks = {}
  }
  $on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn) }
$emit(name, args){ if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(args)) }
} }
// main.js
Vue.prototype.$bus = new Bus()
// child1
this.$bus.$on('foo', handle) // child2 this.$bus.$emit('foo')
@前端进阶之旅: 代码已经复制到剪贴板

实践中通常用Vue代替Bus,因为Vue已经实现了相应接口

# 4. vuex

创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更。

# 5. $parent/$root

兄弟组件之间通信可通过共同祖辈搭桥,$parent或$root。

 
// brother1
this.$parent.$on('foo', handle)
// brother2 
this.$parent.$emit('foo')
@前端进阶之旅: 代码已经复制到剪贴板

# 6. $children

父组件可以通过$children访问子组件实现父子通信。

 // parent 
 this.$children[0].xx = 'xxx'
@前端进阶之旅: 代码已经复制到剪贴板

# 7. $attrs/$listeners

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有 声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 v- bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

 
// child:并未在props中声明foo 
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>
@前端进阶之旅: 代码已经复制到剪贴板

# 8. refs

获取子节点引用

 
// parent
<HelloWorld ref="hw"/>
mounted() { 
  this.$refs.hw.xx = 'xxx'
}
@前端进阶之旅: 代码已经复制到剪贴板

# 9. provide/inject

能够实现祖先和后代之间传值

   
// ancestor
provide() {
  return {foo: 'foo'}
}
// descendant
inject: ['foo']
@前端进阶之旅: 代码已经复制到剪贴板

实例

// index.vue
<template>
  <div>
    <h2>组件通信</h2>
    <!-- props, 自定义事件 -->
    <Child1 msg="some msg from parent" @some-event="onSomeEvent"></Child1>
    <!-- 事件总线 -->
    <Child2 msg="other msg"></Child2>
  </div>
</template>

<script>
  import Child1 from '@/components/communication/Child1.vue'
  import Child2 from '@/components/communication/Child2.vue'
  
  export default {
    components: {
      Child1, Child2,
      // Child3: () => import('./Child3.vue')
    },
    methods: {
      onSomeEvent(msg) {
        console.log('Communition:', msg);
      }
    },
    mounted () {
      // $children持有所有自定义组件
      // 它不保证顺序
      console.log(this.$children);
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板
// child1.vue
<template>
  <div @click="$emit('some-event', 'msg from child1')">
    <h3>child1</h3>
    <p>{{msg}}</p>
  </div>
</template>

<script>
  export default {
    props: {
      msg: {
        type: String,
        default: ''
      },
    },
    mounted () {
      // this.$bus.$on('event-from-child2', msg => {
      //   console.log('Child1:', msg);
      // });
      this.$parent.$on('event-from-child2', msg => {
        console.log('Child1:', msg);
      });
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板
// child2.vue
<template>
  <div>
    <!-- 展开$attrs对象 -->
    <h3 v-bind="$attrs">child2</h3>
    <button @click="sendToChild1">给child1发送消息</button>
    <!-- $attrs -->
    <p>{{$attrs.msg}}</p>
    <!-- inject -->
    <p>{{foo}}</p>
  </div>
</template>

<script>
  export default {
    inheritAttrs: false,
    inject: ['foo'],
    methods: {
      sendToChild1() {
        // 利用事件总线发送事件
        // this.$bus.$emit('event-from-child2', 'some msg from child2')
        this.$parent.$emit('event-from-child2', 'some msg from child2')
      }
    },
  }
</script>

<style scoped>

</style>
@前端进阶之旅: 代码已经复制到剪贴板

# 插槽

插槽语法是Vue 实现的内容分发 API,用于复合组件开发。该技术在通用组件库开发中有大量应用

# 1. 匿名插槽

 
// comp1
<div>
  <slot></slot>
</div>
// parent
<comp>hello</comp>
@前端进阶之旅: 代码已经复制到剪贴板

# 2. 具名插槽

将内容分发到子组件指定位置

 
// comp2
<div>
<slot></slot>
<slot name="content"></slot>
</div>
// parent
<Comp2>
<!-- 默认插槽用default做参数 -->
<template v-slot:default>具名插槽</template> <!-- 具名插槽用插槽名做参数 -->
<template v-slot:content>内容...</template>
</Comp2>
@前端进阶之旅: 代码已经复制到剪贴板

# 3. 作用域插槽

分发内容要用到子组件中的数据

 
// comp3
<div>
<slot :foo="foo"></slot>
</div>
// parent
<Comp3>
<!-- 把v-slot的值指定为作用域上下文对象 --> <template v-slot:default="slotProps"> 来自子组件数据:{{slotProps.foo}} </template>
</Comp3>
@前端进阶之旅: 代码已经复制到剪贴板

范例

← vue2源码分析React router原理 →

fe