在 Vue 中,以 _
或 $
开头的变量名确实会有特殊行为,这源于 Vue 的设计决策。
🚫 问题:自动代理排除
Vue 会自动跳过以 _
或 $
开头的属性,不会将它们设置为响应式数据,也不会代理到 Vue 实例上。
示例演示
<template>
<div>
<p>普通属性: {{ normalProp }}</p> <!-- 正常显示 -->
<p>下划线属性: {{ _privateProp }}</p> <!-- 不会显示 -->
<p>美元属性: {{ $internalProp }}</p> <!-- 不会显示 -->
</div>
</template>
<script>
export default {
data() {
return {
normalProp: '我可以正常显示',
_privateProp: '我不会被响应式处理',
$internalProp: '我也不会被代理'
}
},
mounted() {
console.log(this.normalProp); // "我可以正常显示"
console.log(this._privateProp); // undefined
console.log(this.$internalProp); // undefined
console.log(this.$data._privateProp); // "我不会被响应式处理"
}
}
</script>
🔍 访问这些值的多种方式
方式一:通过 $data
直接访问
<template>
<div>
<p>通过 $data 访问: {{ $data._privateData }}</p>
<button @click="updatePrivateData">更新私有数据</button>
</div>
</template>
<script>
export default {
data() {
return {
_privateData: '我是私有数据',
$internalValue: '内部值'
}
},
methods: {
updatePrivateData() {
// 通过 $data 访问和修改
this.$data._privateData = '更新后的私有数据';
this.$data.$internalValue = '更新后的内部值';
console.log('_privateData:', this.$data._privateData);
console.log('$internalValue:', this.$data.$internalValue);
},
accessInMethod() {
// 在方法中访问
const privateValue = this.$data._privateData;
const internalValue = this.$data.$internalValue;
return { privateValue, internalValue };
}
},
computed: {
computedFromPrivate() {
// 在计算属性中访问
return this.$data._privateData.toUpperCase();
}
}
}
</script>
方式二:使用计算属性包装
<template>
<div>
<p>私有数据: {{ privateData }}</p>
<p>内部配置: {{ internalConfig }}</p>
</div>
</template>
<script>
export default {
data() {
return {
_privateData: '敏感信息',
$appConfig: { theme: 'dark', version: '1.0.0' }
}
},
computed: {
// 使用计算属性暴露私有数据
privateData() {
return this.$data._privateData;
},
internalConfig() {
return this.$data.$appConfig;
}
},
methods: {
updateConfig() {
// 通过计算属性间接更新
this.$data.$appConfig.theme = 'light';
}
}
}
</script>
方式三:在生命周期钩子中访问
<script>
export default {
data() {
return {
_initialized: false,
$cache: new Map()
}
},
created() {
// 在生命周期中直接操作 $data
this.$data._initialized = true;
this.$data.$cache.set('key', 'value');
},
mounted() {
console.log('初始化状态:', this.$data._initialized);
console.log('缓存内容:', this.$data.$cache.get('key'));
}
}
</script>
💡 实际应用场景
场景一:私有状态管理
<template>
<div>
<p>页面加载次数: {{ accessCount }}</p>
<p>最后访问时间: {{ lastAccess }}</p>
</div>
</template>
<script>
export default {
data() {
return {
// 公共可访问的数据
accessCount: 0,
lastAccess: null,
// 私有内部状态
_internalState: {
startTime: null,
sessionId: null
},
// 内部配置
$performanceConfig: {
trackTiming: true,
logLevel: 'debug'
}
}
},
created() {
this.$data._internalState.startTime = Date.now();
this.$data._internalState.sessionId = this.generateSessionId();
this.incrementAccessCount();
},
methods: {
incrementAccessCount() {
this.accessCount++;
this.lastAccess = new Date().toLocaleString();
// 记录私有日志
this.$data._internalState.lastOperation = 'increment';
},
generateSessionId() {
return 'session_' + Math.random().toString(36).substr(2, 9);
},
getInternalState() {
// 提供受控的访问方式
return {
sessionId: this.$data._internalState.sessionId,
runningTime: Date.now() - this.$data._internalState.startTime
};
}
},
computed: {
// 暴露部分私有信息
sessionInfo() {
return `会话: ${this.$data._internalState.sessionId}`;
}
}
}
</script>
场景二:插件开发内部状态
<script>
export default {
data() {
return {
// 公共API
isLoading: false,
data: null,
// 插件内部状态(不暴露给用户)
_$requestQueue: [],
_$retryCount: 0,
_$cache: new Map(),
// 内部配置
$_config: {
timeout: 5000,
maxRetries: 3,
cacheTTL: 300000
}
}
},
methods: {
async fetchData(url) {
this.isLoading = true;
// 使用内部队列管理
this.$data._$requestQueue.push(url);
try {
const response = await this.makeRequest(url);
this.data = response;
// 更新内部缓存
this.$data._$cache.set(url, {
data: response,
timestamp: Date.now()
});
} catch (error) {
this.handleError(error);
} finally {
this.isLoading = false;
this.$data._$requestQueue = this.$data._$requestQueue.filter(
item => item !== url
);
}
},
makeRequest(url) {
// 内部实现细节
return fetch(url, {
timeout: this.$data.$_config.timeout
}).then(res => res.json());
},
// 内部方法,不暴露给模板
handleError(error) {
this.$data._$retryCount++;
console.error('请求失败:', error);
}
}
}
</script>
场景三:混入(Mixin)中的私有属性
<script>
const PrivateDataMixin = {
data() {
return {
_mixinPrivateState: '混入私有状态',
$mixinInternalConfig: { enabled: true }
}
},
methods: {
// 混入的私有方法
$_mixinInternalMethod() {
console.log('内部方法被调用');
return this.$data._mixinPrivateState;
}
}
}
export default {
mixins: [PrivateDataMixin],
data() {
return {
publicData: '公共数据',
_componentPrivate: '组件私有'
}
},
mounted() {
// 访问混入的私有数据
console.log('混入私有状态:', this.$data._mixinPrivateState);
console.log('混入配置:', this.$data.$mixinInternalConfig);
// 调用混入的私有方法
const result = this.$_mixinInternalMethod();
console.log('内部方法结果:', result);
}
}
</script>
🚀 最佳实践和建议
1. 合理使用前缀
<script>
export default {
data() {
return {
// ✅ 公共接口 - 驼峰命名
userName: '',
isLoading: false,
// ✅ 内部状态 - _ 前缀
_pendingRequests: new Set(),
_updateTimer: null,
// ✅ 框架相关 - $ 前缀
$validationRules: {},
$pluginConfig: {}
}
}
}
</script>
2. 提供受控的访问接口
<script>
export default {
data() {
return {
_sensitiveData: '机密信息',
$internalState: { debug: false }
}
},
methods: {
// 提供安全的访问方法
getSensitiveData() {
// 添加访问控制逻辑
if (this.hasPermission()) {
return this.$data._sensitiveData;
}
return null;
},
setInternalDebug(enable) {
this.$data.$internalState.debug = enable;
},
hasPermission() {
// 权限检查逻辑
return true;
}
}
}
</script>
3. 在 Vue 3 Composition API 中
<template>
<div>
<p>私有状态: {{ internalState }}</p>
<p>配置信息: {{ config }}</p>
</div>
</template>
<script>
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// Vue 3 中没有这种限制,但可以遵循相同约定
const publicState = ref('公共状态');
// 使用 _ 前缀表示私有(约定)
const _privateState = ref('私有状态');
// 使用 $ 前缀表示内部(约定)
const $internalConfig = reactive({
debug: true,
version: '1.0.0'
});
// 通过计算属性暴露
const internalState = computed(() => _privateState.value);
const config = computed(() => $internalConfig);
return {
publicState,
internalState,
config
}
}
}
</script>
📝 总结
- 问题:Vue 自动排除
_
和$
开头的属性,不进行响应式代理 - 访问方式:通过
this.$data._propertyName
访问 - 使用场景:
_
前缀:表示私有数据、内部状态$
前缀:表示框架相关、插件内部属性
- 最佳实践:
- 使用前缀区分公共接口和内部实现
- 通过计算属性或方法提供受控访问
- 在文档中说明这些约定的含义
这种设计有助于代码的组织和维护,明确区分了公共API和内部实现细节。
THE END