Fork me on GitHub

Vue3-选项式API

状态选项

功能 作用 类比打比方
data 定义本地数据(组合式用 ref 自己家里的存折
props 接收父组件传值 父母给的生活费
computed 计算属性,自动依赖更新 动态工资单,工资随业绩变
methods 定义函数逻辑(组合式直接写) 工具箱里的工具
watch 监听数据变化 监控摄像头,数据变动就提醒
emits 定义并触发向父组件的事件 打电话通知家长
expose 子组件主动暴露接口给父组件 对外开放的“公开功能按钮”

父组件

<template>
  <MyChild ref="childRef" msg="父传子数据" @customEvent="handleCustomEvent" />
  <button @click="callChild">父组件调用子组件方法</button>
</template>

<script setup>
import { ref } from 'vue'
import MyChild from './MyChild.vue'

const childRef = ref(null)

function handleCustomEvent(val) {
  console.log('收到子组件消息:', val)
}

function callChild() {
  childRef.value?.increment()
}
</script>

子组件

<template>
  <div>
    <h3>姓名:{{ fullName }}</h3>
    <p>年龄:{{ age }}</p>
    <button @click="increment">增长年龄</button>

    <p>父组件传来:{{ msg }}</p>
    <button @click="sendToParent">通知父组件</button>
  </div>
</template>

<script setup>
// 1. props:接收父组件传值
const props = defineProps({
  msg: {
    type: String,
    default: ''
  }
})

// 2. emits:定义向父组件触发的事件
const emit = defineEmits(['customEvent'])

// 3. data:组合式API下用ref模拟data
import { ref, computed, watch, expose } from 'vue'
const age = ref(18)
const firstName = ref('秋')
const lastName = ref('先生')

// 4. computed:计算属性
const fullName = computed(() => `${firstName.value}${lastName.value}`)

// 5. methods:组合式下直接写函数
function increment() {
  age.value++
}

function sendToParent() {
  emit('customEvent', '子组件的消息')
}

// 6. watch:监听响应式数据
watch(age, (newVal, oldVal) => {
  console.log(`年龄变化:${oldVal} → ${newVal}`)
})

// 7. expose:向父组件暴露子组件方法
expose({
  increment
})
</script>

渲染选项

名称 作用 类比打比方
template 模板区域,编译为渲染函数 HTML 布局图纸
render 手写渲染函数,替代 template 直接用 JavaScript 造 DOM 结构
compilerOptions 控制模板编译规则 施工队的特殊工作说明书
slots 插槽,父组件传递内容的机制 租房,房东给你预留的空白区域

父组件

<template>
  <CustomCard>
    <template #header>
      <h3>我是头部插槽</h3>
    </template>

    <p>我是默认插槽内容</p>

    <template #footer>
      <small>底部信息</small>
    </template>
  </CustomCard>
</template>

<script setup>
import CustomCard from './CustomCard.vue'
</script>

子组件 CustomCard.vue(template 写法)

<template>
  <div class="card">
    <header>
      <slot name="header">默认头部</slot>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      <slot name="footer">默认底部</slot>
    </footer>
  </div>
</template>

手写 render 写法(替代 template)

<script setup>
import { h, useSlots } from 'vue'

const slots = useSlots()

defineExpose({
  test() {
    console.log('暴露给父组件的函数')
  }
})

const render = () => {
  return h('div', { class: 'card' }, [
    h('header', slots.header ? slots.header() : '默认头部'),
    h('main', slots.default ? slots.default() : '默认内容'),
    h('footer', slots.footer ? slots.footer() : '默认底部')
  ])
}
</script>

compilerOptions
在 vue.config.js 或 vite.config.js 里:

export default {
  compilerOptions: {
    // 自定义模板标签识别
    isCustomElement: tag => tag.startsWith('x-')
  }
}

配置告诉 Vue:
遇到 x-button、x-panel这种标签,别当普通组件检查,直接原样保留,适合 Web 组件、跨框架场景。

生命周期选项

<template>
  <div>
    <h3>Vue3 生命周期演示</h3>
    <p>计数:{{ count }}</p>
    <button @click="count++">点击+1</button>
  </div>
</template>

<script>
export default {
  name: 'LifeCycleDemo',

  data() {
    return {
      count: 0
    }
  },

  // ------------------- 创建阶段 -------------------
  beforeCreate() {
    console.log('beforeCreate:数据、props、methods 都还没初始化')
  },

  created() {
    console.log('created:数据、props、methods 已经初始化完毕')
  },

  // ------------------- 挂载阶段 -------------------
  beforeMount() {
    console.log('beforeMount:模板已经编译,但还没渲染到页面')
  },

  mounted() {
    console.log('mounted:组件已挂载到 DOM,真实可见')
  },

  // ------------------- 更新阶段 -------------------
  beforeUpdate() {
    console.log('beforeUpdate:响应式数据变了,DOM 更新前')
  },

  updated() {
    console.log('updated:DOM 已经更新完毕')
  },

  // ------------------- 卸载阶段 -------------------
  beforeUnmount() {
    console.log('beforeUnmount:组件即将被卸载')
  },

  unmounted() {
    console.log('unmounted:组件已完全销毁')
  },

  // ------------------- 其他钩子 -------------------
  errorCaptured(err, vm, info) {
    console.log('errorCaptured:捕获到子组件错误', err, info)
    return false // 阻止错误继续向上传播
  },

  renderTracked(e) {
    console.log('renderTracked:追踪到依赖', e)
  },

  renderTriggered(e) {
    console.log('renderTriggered:响应式依赖变了导致重新渲染', e)
  },

  activated() {
    console.log('activated:<KeepAlive> 缓存组件激活')
  },

  deactivated() {
    console.log('deactivated:<KeepAlive> 缓存组件失活')
  },

  serverPrefetch() {
    console.log('serverPrefetch:SSR 场景数据预拉取')
  }
}
</script>

组合选项

//公共 mixin:mixin.js
export default {
  data() {
    return {
      mixinMsg: '我是 mixin 里的数据'
    }
  },
  methods: {
    mixinMethod() {
      console.log('mixinMethod 被调用')
    }
  }
}


//公共基类:BaseComponent.js
export default {
  data() {
    return {
      baseMsg: '我是 extends 继承的基类数据'
    }
  },
  methods: {
    baseMethod() {
      console.log('baseMethod 被调用')
    }
  }
}


//父组件:Parent.vue
<template>
  <div>
    <h3>父组件</h3>
    <p>父组件数据:{{ msg }}</p>
    <ChildComponent />
  </div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
  components: { ChildComponent },
  data() {
    return {
      msg: '这是父组件传下来的数据'
    }
  },
  //向下传递数据(跨多层)
  provide() {
    return {
      sharedMsg: this.msg
    }
  }
}
</script>


//子组件:ChildComponent.vue
<template>
  <div>
    <h4>子组件</h4>
    <p>inject 的数据:{{ sharedMsg }}</p>
    <p>mixin 数据:{{ mixinMsg }}</p>
    <p>extends 数据:{{ baseMsg }}</p>
    <button @click="testAll">调用所有方法</button>
  </div>
</template>

<script>
import mixin from './mixin'
import BaseComponent from './BaseComponent'

export default {
  //临时混入多个通用功能、数据、方法
  mixins: [mixin],
  //继承基础组件的功能、数据、方法
  extends: BaseComponent,
  //获取祖先提供的数据
  inject: ['sharedMsg'],
  methods: {
    testAll() {
      //使用
      this.mixinMethod()
      this.baseMethod()
    }
  }
}
</script>

image

其他选项

//1. 子组件 MyInput.vue
<template>
  <div>
    <p>自定义输入框:</p>
    <input v-bind="attrs" v-my-focus />
  </div>
</template>
<script>
export default {
  name: 'MyInput', // 组件名,便于调试和递归调用
  inheritAttrs: false, // 阻止未声明的属性自动挂载到根节点,但是元素使用了v-bind="attrs"手动把这些属性绑定到 <input> 上
  directives: {
    // 自定义指令:自动获取焦点
    myFocus: {
      mounted(el) {
        el.focus()
      }
    }
  },
  computed: {
    // 通过 $attrs 手动控制透传属性
    attrs() {
      return this.$attrs
    }
  }
}
</script>



//2. 父组件 App.vue
<template>
  <div>
    <h3>父组件</h3>
    //父组件给 <MyInput> 传入 placeholder 和 class
    <MyInput placeholder="请输入内容" class="custom-class" />
  </div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
  name: 'App',
  components: {
    MyInput // 局部注册子组件
  }
}
</script>

image

组件实例

属性/方法 作用 类比打比方
this.$data 组件内部所有响应式数据 自己家里的账本
this.$props 父组件传递的所有 props 父母给的生活费
this.$el 组件根 DOM 元素 自己的房子门口
this.$options 组件所有选项配置对象 出生档案,包含所有定义
this.$parent 父组件实例 父母对象
this.$root 根组件实例 整个 Vue 应用的大管家
this.$slots 父组件传入的插槽内容 留给你的装修空间
this.$refs 通过 ref 获取的 DOM 或组件实例 门牌号查找的结果
this.$attrs 未被 props 接收的属性集合 杂七杂八外部传来的信息
this.$watch() 手动监听数据变化 监控摄像头
this.$emit() 触发自定义事件,通知父组件 打电话告诉家长
this.$forceUpdate() 强制刷新视图(不建议滥用) 手动刷新屏幕
this.$nextTick() DOM 更新完成后执行回调 等装修完再检查
//父组件 App.vue
<template>
  <div>
    <h3>父组件</h3>
    <ChildComponent ref="childRef" msg="父传子数据">
      <template #default>
        <p>这是插槽内容</p>
      </template>
    </ChildComponent>
    <button @click="testChild">父组件调用子组件</button>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
  components: { ChildComponent },
  methods: {
    testChild() {
      console.log('父组件 this.$refs.childRef:', this.$refs.childRef)
      this.$refs.childRef.testExposeMethod()
    }
  }
}
</script>



//子组件 ChildComponent.vue
<template>
  <div ref="box">
    <h4>子组件</h4>
    <p>msg:{{ msg }}</p>
    <slot />
    <button @click="updateData">更新数据</button>
  </div>
</template>

<script>
export default {
  name: 'ChildComponent',

  props: {
    msg: String
  },

  data() {
    return {
      count: 0
    }
  },

  mounted() {
    console.log('--- 组件实例属性演示 ---')

    console.log('$data:', this.$data)
    console.log('$props:', this.$props)
    console.log('$el:', this.$el)
    console.log('$options:', this.$options)
    console.log('$parent:', this.$parent)
    console.log('$root:', this.$root)
    console.log('$slots:', this.$slots)
    console.log('$refs:', this.$refs)
    console.log('$attrs:', this.$attrs)

    // $watch 示例
    this.$watch('count', (newVal, oldVal) => {
      console.log(`count 变化:${oldVal} -> ${newVal}`)
    })

    // $emit 示例
    this.$emit('customEvent', '子组件触发的事件')

    // $nextTick 示例
    this.count = 100
    this.$nextTick(() => {
      console.log('DOM 已经更新完毕')
    })
  },

  methods: {
    updateData() {
      this.count++
      this.$forceUpdate()
    },
    testExposeMethod() {
      console.log('子组件暴露的方法被父组件调用了')
    }
  }
}
</script>

posted @ 2025-07-06 12:00  秋夜雨巷  阅读(53)  评论(0)    收藏  举报