浅析setup如何通过ref获取⼦组件实例中的DOM结构数据⽅法及获取⼦组件实例数据都是空。。。
⼀、通过 ref 获取⼦组件实例中的DOM结构数据及⽅法
  setup 怎么获取⼦组件的 ref ?在 Vue3 中,如果要在⽗组件拿到⼦组件(⼦组件的DOM结构、数据、⽅法),可以通过 ref。即在⽗组件中定义响应式数据 ref(null) ,并绑定给⼦组件,在需要的时候,通过定义的响应式变量即可获取。获取后,即获取了⼦组件的⼀切,可以看到⼦组件的DOM结构,也可以看到⼦组件中对外暴露的的数据和⽅法,并且可以直接调⽤。
<ExpertFormVue ref="ExpertRef"></ExpertFormVue>
const ExpertRef = ref() // 通过 ref 绑定⼦组件
function getSonComponent () { // 通过 ref 获取⼦组件
// 获取⼦组件的数据
console.log(ExpertRef.value)
console.log(ExpertRef.value.msg)
}
  这⾥⾯涉及 2 个问题:
1、如何通过 ref 获取⼦组件实例
  ref ⽤法1:响应式数据。ref ⽤法2:模板ref,获取dom元素节点(重点)
1、const a = ref(null);
2、在template中定义ref
<div ref='a'>*********</div>
3、setup中获取对应节点【在onMounted⾥】;
4、将a return出去;
  但是这时候你可能会发现,你⽆法获取这个节点,这是什么原因呢?其实还是⽣命周期的问题,结合官⽅⽂档可知:原来的created没有了,setup充当了原来的created。
  所以在setup的时候,dom元素还没有被创建,⼀切都处于混沌状态,只有setup完毕了HTML才能完整构建,才能真正访问到value值,所以⾃然⽆法获取到dom节点,要想解决这个问题,就要配合钩⼦函数 onMounted ,在dom挂载完毕后再进⾏获取。
2、为什么获取⼦组件实例中的数据都是空?  ——  defineExpose API 的使⽤
  问题:开始的时候,我像上⾯写的,怎么都拿不到⼦组件实例的数据和⽅法等。
  原因:其实写法是对的,只不过拿到的引⽤是空的。⼦组件需要明确使⽤ expose ⽅法暴露出接⼝之后,才能在⽗组件获取到接⼝引⽤。未暴露接⼝的情况下,引⽤的始终是⼀个空对象。
// ⼦组件导出⽅法,才能在⽗组件拿到⼦组件的实例⾥使⽤
defineExpose({
initExp
})
  在 vue3.x 的 setup 语法糖中定义的变量默认不会暴露出去,这时使⽤ definExpose({ })来暴露组件内部属性给⽗组件使⽤,在⽗组件中直接修改⼦组件的属性,⼦组件也会相应更新。
// ⼦组件
<script setup>
let aaa = ref("aaa")
defineExpose({ aaa });
</script>
// ⽗组件
<Chlid ref="child"></Chlid>
<script setup>
let child = ref(null)
child.value.aaa //获取⼦组件的aaa
</script>
⼆、官⽹:模板引⽤
  尽管存在 prop 和事件,但有时你可能仍然需要直接访问 JavaScript 中的⼦组件。为此,可以使⽤ ref attribute 为⼦组件或 HTML 元素指定引⽤ ID。例如:
<input ref="input" />
  例如,你希望在组件挂载时,以编程的⽅式 focus 到这个 input 上,这可能有⽤(可以看到直接使⽤this.$refs.input 就获取到了 input Dom)
const app = ateApp({})
appponent('base-input', {
template: `
<input ref="input" />
`,
vue中reactive
methods: {
focusInput() {
this.$refs.input.focus()
}
},
mounted() {
this.focusInput()
}
})
  此外,还可以向组件本⾝添加另⼀个 ref,并使⽤它从⽗组件触发 focusInput 事件:
<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput.focusInput()
  需要注意的是:$refs 只会在组件渲染完成之后⽣效(也就是⽣命周期 onMounted 之后)。这仅作为⼀个⽤于直接操作⼦元素的“逃⽣舱”——你应该避免在模板或计算属性中访问 $refs。
三、官⽹:
  在使⽤组合式 API 时,和的概念是统⼀的。为了获得对模板内元素或组件实例的引⽤,我们可以像往常⼀样声明 ref 并从返回:
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM 元素将在初始渲染后分配给 ref
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
}
</script>
  这⾥我们在渲染上下⽂中暴露 root,并通过 ref="root",将其绑定到 div 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的 ref 键对应于渲染上下⽂中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执⾏的,因此模板引⽤只会在初始渲染之后获得赋值。
  作为模板使⽤的 ref 的⾏为与任何其他 ref ⼀样:它们是响应式的,可以传递到 (或从中返回) 复合函数中
1、v-for 中的⽤法(for循环中如何获取及重置refs)
  组合式 API 模板引⽤在 v-for 内部使⽤时没有特殊处理。相反,请使⽤函数引⽤执⾏⾃定义处理:
<template>
<div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
{{ item }}
</div>
</template>
<script>
import { ref, reactive, onBeforeUpdate } from'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs
}
}
}
</script>
2、侦听模板引⽤:侦听模板引⽤的变更可以替代前⾯例⼦中演⽰使⽤的⽣命周期钩⼦。
  但与⽣命周期钩⼦的⼀个关键区别是,watch() 和 watchEffect() 在 DOM 挂载或更新之前运⾏副作⽤,所以当侦听器运⾏时,模板引⽤还未被更新。
const root = ref(null)
watchEffect(() => {
  // 这个副作⽤在 DOM 更新之前运⾏,因此,模板引⽤还没有持有对元素的引⽤。
  console.log(root.value) // => null
})
  因此,使⽤模板引⽤的侦听器应该⽤ flush: 'post' 选项来定义,这将在 DOM 更新后运⾏副作⽤,确保模板引⽤与 DOM 保持同步,并引⽤正确的元素。
watchEffect(() => {
  console.log(root.value) // => <div>This is a root element</div>
  },
  {
    flush: 'post'
  })