不再迷惑,无值和 NULL 值

本文探讨了数据库中无值与NULL值的区别及其在不同情况下的转换规则,并详细解释了聚合函数如何处理这两种值。

在关系型数据库的世界中,无值和NULL值的区别是什么?一直被这个问题困扰着,甚至在写TSQL脚本时,心有戚戚焉,害怕因为自己的一知半解,挖了坑,贻害后来人,于是,本着上下求索,不达通幽不罢休的决心(开个玩笑),遂有此文。

学习过关系型数据库的伙伴都知道,NULL是指不确定的值,在数据库中绝对是噩梦的存在;而空值,一般对字符串类型而言,指没有任何值的字符串类型,为字符类型的变量设置为空值:set @vs=”,空值跟无值不同。有人可能会问,无值是什么?无值,是指数据表中没有任何数据。无值和不确定值,单从字面意思上来看,两者之间的定义很清楚,一旦深究,这两者之间的关系,有时令人十分迷惑(confused),这是因为,在特定条件下,无值会转换为NULL值。

一,举个栗子,理解无值和NULL值的区别

比如,创建一个临时表,在不插入任何数据时,该数据表是空的,没有任何值,对其执行select命令,将不会返回任何数据值:

创建一个标量类型的变量,在不初始化时,该变量的值是不确定的,其值是NULL:

创建一个表类型变量,在不初始化时,该表变量没有任何数据,是无值的:

总结一下,声明一个标量型变量,如果没有对变量进行初始化,其值是不确定的,是NULL值;对于表变量,临时表和基础表,如果没有插入任何数据,该表没有任何数据,是无值的。

二,无值和NULL值的转换

在开始本节之前,先为变量赋值,简单的一个select命令就可以完成变量的赋值:

有些朋友思维比较活跃,立马会想到:“用select命令可以从表中取值为变量赋值”,对,但是,赋值方法不是我求索的重点,我关注的是从表中取值为变量赋值的结果。

1,从空表中为变量赋值

如果数据表是空表,没有任何值,那么数据库引擎不会执行赋值语句,变量保持原有值不变:

但是,如果采用以下方式,那么数据库引擎会执行赋值语句,由于空表不返回任何值,数据库引擎会把无值转换为不确定值NULL:

诧异吗?无值和NULL值的转换,居然从不起眼的变量赋值开始。注意,当不返回任何值时,数据库引擎不确定返回值,就把无值转换为NULL值。

2,从空表中计算聚合

空表是没有任何数据的表,计算聚合会产生怎样的结果?

当统计数据行数时,返回的是0;当计算聚合函数(max,min,avg和sum)的聚合值时,由于无值可以聚合,数据库引擎不能确定这些聚合函数的返回值,因此,数据库引擎返回NULL值。

三,聚合函数忽略NULL值

一般情况下,除了count(0),count(*)之外,聚合函数都会忽略NULL值,而统计非NULL值,如果读者有疑问,可以查看我的博客《TSQL 聚合函数忽略NULL值》。如果只知聚合函数忽略NULL值,而不知空表也会产生结果为NULL的聚合值,轻易得出聚合函数不会返回NULL值的定论,那就很尴尬。楼主曾遇到过一次“意外”,在一次调试脚本代码的过程中,我遇到max聚合函数返回NULL值的情况,当时一脸懵逼,直接怀疑自己之前的所学。

当聚合列值都是NULL值时,由于聚合函数忽略NULL值,因此,当计算聚合函数(max,min,avg和sum)的聚合值时,由于无值可以聚合,数据库引擎不能确定这些聚合函数的返回值,因此,数据库引擎返回NULL值。

聚合函数(max,min,sum,avg和count)忽略null值,但不代表聚合函数不返回null值:如果数据表为空表,或聚合列值都是null,那么max,min,sum,avg聚合函数返回null值,而count 聚合函数返回0。聚合函数的共性:Null values are ignored。

不再迷惑:当不返回任何值时,数据库引擎不确定返回值,就把无值转换为NULL值。

<think>在Vue 3的Composition API中,使用`ref`获取子组件实例时遇到`formRef.value`为`null`的问题,通常是由于以下原因之一: ### 原因分析: 1. **组件未挂载完成**:在组件的`setup`阶段或`onMounted`生命周期钩子之前访问`ref`,此时DOM子组件尚未渲染完成。 2. **异步组件**:如果子组件是异步加载的(例如使用`defineAsyncComponent`),则初始渲染时子组件实例可能不可用。 3. **条件渲染**:子组件被包裹在`v-if`或`v-show`等条件渲染指令中,初始条件为`false`时,子组件不会渲染,因此`ref`也无法获取实例。 4. **作用域问题**:在`setup`函数中返回的`ref`没有被正确绑定到模板上,或者模板中的`ref`名称与脚本中的变量名不一致。 5. **组件内部未暴露**:在`<script setup>`语法糖中,子组件的属性方法默认是私有的,必须使用`defineExpose`显式暴露才能被父组件访问。 ### 解决方案: #### 1. 确保在组件挂载后访问 使用`onMounted`生命周期钩子确保在组件挂载完成后再访问`ref`: ```vue <template> <BasicForm ref="formRef" /> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from 'vue'; import BasicForm from './BasicForm.vue'; export default defineComponent({ components: { BasicForm }, setup() { const formRef = ref<InstanceType<typeof BasicForm> | null>(null); onMounted(() => { if (formRef.value) { console.log('子组件实例:', formRef.value); // 调用子组件方法或属性 } }); return { formRef }; }, }); </script> ``` #### 2. 处理条件渲染 如果子组件被条件渲染,可以使用`nextTick`确保在DOM更新后访问: ```vue <template> <BasicForm v-if="showForm" ref="formRef" /> </template> <script lang="ts"> import { defineComponent, ref, nextTick } from 'vue'; import BasicForm from './BasicForm.vue'; export default defineComponent({ components: { BasicForm }, setup() { const showForm = ref(false); const formRef = ref<InstanceType<typeof BasicForm> | null>(null); // 当条件变为true时,等待DOM更新 const initForm = async () => { showForm.value = true; await nextTick(); if (formRef.value) { console.log('子组件实例:', formRef.value); } }; return { showForm, formRef, initForm }; }, }); </script> ``` #### 3. 子组件显式暴露 如果子组件使用`<script setup>`,需用`defineExpose`暴露属性方法: ```vue <!-- 子组件 BasicForm.vue --> <script setup lang="ts"> import { ref } from 'vue'; const formData = ref({}); const submit = () => { console.log('提交表单'); }; // 暴露给父组件 defineExpose({ submit, formData, }); </script> ``` #### 4. 确保模板脚本中的ref名称一致 检查模板中的`ref`属性是否与脚本中声明的`ref`变量同名: ```vue <template> <!-- 确保ref="formRef"与变量名formRef一致 --> <BasicForm ref="formRef" /> </template> ``` #### 5. 使用watchEffect监听变化 如果子组件渲染时机不确定,可用`watchEffect`监听: ```ts import { watchEffect } from 'vue'; watchEffect(() => { if (formRef.value) { console.log('子组件实例变化:', formRef.value); } }); ``` ### 总结: - **挂载时机**:在`onMounted`或`nextTick`中访问`ref`。 - **条件渲染**:确保子组件已渲染后再访问。 - **暴露方法**:子组件使用`defineExpose`暴露方法。 - **类型安全**:使用`InstanceType<typeof Component>`指定`ref`类型。 通过以上步骤,可解决`formRef.value`为`null`的问题,并安全访问子组件实例[^1][^3][^5]。 ### 相关问题 1. Vue 3中如何通过ref获取DOM元素而非组件实例? 2. 如何在Vue 3的Composition API中正确使用`defineExpose`? 3. Vue 3中如何处理异步组件的ref获取问题? [^1]: 当使用前文中的ref时会感到迷惑,模板中也有一个ref用于获取组件实例或dom对象,这样会不会冲突。而实际上在vue3中,ref会用来保存templates的ref。 [^3]: 从这个例子我们可以很清晰看到,vue 的 setup 函数可以就收两个参数,分别为 props、context。context 中包含属性、插槽、触发事件等内容。 在组件内部可以通过 compose 方法,将内部方法暴露到模板引用上以供父组件使用。 [^5]: vue3 composition api父组件调用子组件的方法。为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏。</think>在 Vue 3 Composition API 中,使用 `ref` 无法获取子组件实例(`formRef.value` 为 `null`)的常见原因及解决方案如下: ### 原因分析 1. **组件未挂载完成**:在组件渲染前访问 `ref`,会得到 `null`(Vue 的生命周期中,`ref` 在挂载阶段后才可用)[^1] 2. **异步组件问题**:子组件是异步加载的(如使用 `defineAsyncComponent`),初始渲染时 `ref` 未被赋 3. **作用域问题**:在 `setup()` 中直接访问 `ref.value`(应在生命周期钩子中访问) 4. **组件未暴露方法**:子组件使用 `<script setup>` 时未用 `defineExpose` 暴露方法[^5] 5. **条件渲染导致**:子组件被 `v-if` 包裹且初始条件为 `false` 6. **TypeScript 类型错误**:未正确定义 `ref` 的类型 --- ### 解决方案 #### 1. 确保在组件挂载后访问(核心方案) ```vue <script lang="ts" setup> import { ref, onMounted } from 'vue' import BasicForm from './BasicForm.vue' const formRef = ref<InstanceType<typeof BasicForm> | null>(null) onMounted(() => { if (formRef.value) { console.log('成功获取实例:', formRef.value) formRef.value.submit() // 调用子组件方法 } }) </script> <template> <BasicForm ref="formRef" /> </template> ``` #### 2. 子组件显式暴露方法(使用 `<script setup>` 时必需) ```vue <!-- 子组件 BasicForm.vue --> <script setup lang="ts"> import { ref } from 'vue' const submit = () => { console.log('表单提交') } // 必须暴露方法父组件才能访问 defineExpose({ submit }) // [^5] </script> ``` #### 3. 处理条件渲染场景 ```vue <template> <BasicForm v-if="showForm" ref="formRef" /> </template> <script setup> import { nextTick } from 'vue' const showForm = ref(false) const formRef = ref(null) const show = async () => { showForm.value = true await nextTick() // 等待DOM更新 console.log(formRef.value) // 此时可获取实例 } </script> ``` #### 4. 正确声明 TypeScript 类型 ```ts import BasicForm from './BasicForm.vue' // 使用 InstanceType 确保类型安全 const formRef = ref<InstanceType<typeof BasicForm> | null>(null) ``` #### 5. 处理异步组件 ```vue <script setup> import { defineAsyncComponent } from 'vue' const AsyncForm = defineAsyncComponent(() => import('./BasicForm.vue') ) const formRef = ref(null) onMounted(() => { // 添加延时确保异步组件加载完成 setTimeout(() => { console.log(formRef.value) }, 100) }) </script> <template> <AsyncForm ref="formRef" /> </template> ``` --- ### 完整解决方案流程图 ```mermaid graph TD A[访问formRef.value为null] --> B{检查原因} B --> C[组件是否已挂载?] B --> D[子组件是否暴露方法?] B --> E[是否条件渲染?] B --> F[是否异步组件?] C --> |未挂载| G[使用onMounted生命周期] D --> |未暴露| H[使用defineExpose暴露方法] E --> |条件渲染| I[使用nextTick等待渲染] F --> |异步组件| J[添加加载等待机制] G --> K[问题解决] H --> K I --> K J --> K ``` --- ### 最佳实践建议 1. **生命周期管理**:始终在 `onMounted` 或 `nextTick` 中访问 `ref` 2. **组件设计**:子组件使用 `defineExpose` 明确暴露公共接口 3. **类型安全**:使用 `InstanceType<typeof Component>` 定义 `ref` 类型 4. **条件渲染处理**:结合 `v-if` `nextTick` 确保组件渲染完成 5. **错误处理**:添加空检查避免运行时错误 ```ts if (formRef.value) { formRef.value.submit() } else { console.error('组件实例未就绪') } ``` > **关键提示**:在 Vue 3 中,使用 `<script setup>` 的子组件**默认不暴露任何内部状态**,必须显式使用 `defineExpose` 编译器宏暴露方法[^5]。这是与 Vue 2 的主要区别之一。 [^1]: 当使用前文中的ref时会感到迷惑,模板中也有一个ref用于获取组件实例或dom对象,这样会不会冲突。而实际上在vue3中,ref会用来保存templates的ref。 [^5]: vue3 composition api父组件调用子组件的方法。为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏。 ### 相关问题 1. Vue 3 中如何通过 ref 获取 DOM 元素而非组件实例? 2. TypeScript 在 Vue 组件中如何正确声明复杂类型? 3. Vue 3 生命周期钩子的执行顺序应用场景是什么? 4. 如何在异步组件中正确处理加载状态错误处理? 5. Vue 3 的 defineExpose Vue 2 的 this.$refs 有何本质区别?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值