Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Vue数据和方法教程,详解data、methods、computed、watch响应式系统。包含完整数据管理示例,适合开发者掌握Vue响应式编程技术。
核心关键词:Vue数据管理、Vue方法、Vue响应式、data、methods、computed、watch、Vue.js数据绑定
长尾关键词:Vue数据怎么定义、Vue方法怎么写、Vue响应式原理、Vue数据监听、Vue.js数据管理
通过本节数据和方法,你将系统性掌握:
Vue响应式数据是什么?这是理解Vue核心机制的关键问题。Vue响应式数据是能够自动追踪变化并更新视图的数据,通过Proxy代理实现依赖收集和派发更新,也是Vue框架的核心特性。
💡 设计理念:Vue响应式系统的设计目标是让数据变化能够自动反映到视图上,减少手动DOM操作,提升开发效率。
响应式数据定义在Vue 2和Vue 3中有不同的方式:
<template>
<div class="data-methods-demo">
<h2>Vue数据和方法管理演示</h2>
<!-- 基础数据展示 -->
<div class="basic-data">
<h3>基础响应式数据</h3>
<div class="data-section">
<h4>用户信息</h4>
<div class="user-info">
<div class="info-item">
<label>姓名:</label>
<input v-model="userInfo.name" placeholder="输入姓名">
<span>{{ userInfo.name }}</span>
</div>
<div class="info-item">
<label>年龄:</label>
<input v-model.number="userInfo.age" type="number" placeholder="输入年龄">
<span>{{ userInfo.age }}岁</span>
</div>
<div class="info-item">
<label>邮箱:</label>
<input v-model="userInfo.email" type="email" placeholder="输入邮箱">
<span>{{ userInfo.email }}</span>
</div>
<div class="info-item">
<label>状态:</label>
<select v-model="userInfo.status">
<option value="active">活跃</option>
<option value="inactive">非活跃</option>
<option value="pending">待审核</option>
</select>
<span class="status" :class="userInfo.status">{{ userInfo.status }}</span>
</div>
</div>
</div>
<div class="data-section">
<h4>数组数据管理</h4>
<div class="array-management">
<div class="array-controls">
<input v-model="newSkill" placeholder="添加技能" @keyup.enter="addSkill">
<button @click="addSkill">添加技能</button>
<button @click="removeLastSkill">删除最后一个</button>
<button @click="shuffleSkills">随机排序</button>
<button @click="clearSkills">清空技能</button>
</div>
<div class="skills-list">
<div
v-for="(skill, index) in userInfo.skills"
:key="skill.id"
class="skill-item"
>
<span class="skill-name">{{ skill.name }}</span>
<span class="skill-level">{{ skill.level }}</span>
<button @click="removeSkill(index)" class="remove-btn">删除</button>
<button @click="upgradeSkill(index)" class="upgrade-btn">升级</button>
</div>
</div>
<p>技能总数: {{ userInfo.skills.length }}</p>
</div>
</div>
<div class="data-section">
<h4>嵌套对象数据</h4>
<div class="nested-data">
<div class="address-form">
<h5>地址信息</h5>
<input v-model="userInfo.address.street" placeholder="街道">
<input v-model="userInfo.address.city" placeholder="城市">
<input v-model="userInfo.address.zipCode" placeholder="邮编">
</div>
<div class="preferences-form">
<h5>偏好设置</h5>
<label>
<input type="checkbox" v-model="userInfo.preferences.newsletter">
订阅邮件
</label>
<label>
<input type="checkbox" v-model="userInfo.preferences.notifications">
推送通知
</label>
<label>
主题:
<select v-model="userInfo.preferences.theme">
<option value="light">浅色</option>
<option value="dark">深色</option>
<option value="auto">自动</option>
</select>
</label>
</div>
</div>
</div>
</div>
<!-- 方法演示 -->
<div class="methods-demo">
<h3>方法定义和调用</h3>
<div class="method-section">
<h4>用户操作方法</h4>
<div class="user-actions">
<button @click="validateUser">验证用户信息</button>
<button @click="resetUser">重置用户信息</button>
<button @click="exportUserData">导出用户数据</button>
<button @click="generateRandomUser">生成随机用户</button>
</div>
<div class="action-result">
<h5>操作结果:</h5>
<pre>{{ actionResult }}</pre>
</div>
</div>
<div class="method-section">
<h4>数据处理方法</h4>
<div class="data-processing">
<button @click="processUserData">处理用户数据</button>
<button @click="calculateUserStats">计算用户统计</button>
<button @click="formatUserInfo">格式化用户信息</button>
<button @click="backupUserData">备份用户数据</button>
</div>
<div class="processing-result">
<h5>处理结果:</h5>
<div v-if="processingResult">
<p><strong>处理时间:</strong> {{ processingResult.timestamp }}</p>
<p><strong>操作类型:</strong> {{ processingResult.operation }}</p>
<p><strong>结果:</strong> {{ processingResult.result }}</p>
</div>
</div>
</div>
</div>
<!-- 计算属性演示 -->
<div class="computed-demo">
<h3>计算属性应用</h3>
<div class="computed-section">
<h4>用户信息计算属性</h4>
<div class="computed-display">
<div class="computed-item">
<label>完整姓名:</label>
<span>{{ fullName }}</span>
</div>
<div class="computed-item">
<label>用户摘要:</label>
<span>{{ userSummary }}</span>
</div>
<div class="computed-item">
<label>技能等级:</label>
<span>{{ skillLevel }}</span>
</div>
<div class="computed-item">
<label>完整地址:</label>
<span>{{ fullAddress }}</span>
</div>
<div class="computed-item">
<label>用户状态描述:</label>
<span>{{ statusDescription }}</span>
</div>
</div>
</div>
<div class="computed-section">
<h4>数据统计计算属性</h4>
<div class="stats-display">
<div class="stat-card">
<h5>技能统计</h5>
<p>总技能数: {{ skillStats.total }}</p>
<p>高级技能: {{ skillStats.advanced }}</p>
<p>平均等级: {{ skillStats.averageLevel }}</p>
</div>
<div class="stat-card">
<h5>用户评分</h5>
<p>信息完整度: {{ userScore.completeness }}%</p>
<p>活跃度评分: {{ userScore.activity }}</p>
<p>综合评分: {{ userScore.overall }}</p>
</div>
</div>
</div>
</div>
<!-- 监听器演示 -->
<div class="watch-demo">
<h3>数据监听器应用</h3>
<div class="watch-section">
<h4>数据变化监听</h4>
<div class="watch-controls">
<button @click="triggerNameChange">触发姓名变化</button>
<button @click="triggerAgeChange">触发年龄变化</button>
<button @click="triggerSkillChange">触发技能变化</button>
<button @click="triggerAddressChange">触发地址变化</button>
</div>
<div class="watch-log">
<h5>监听日志:</h5>
<div class="log-container">
<div
v-for="(log, index) in watchLogs"
:key="index"
class="log-entry"
>
<span class="log-time">{{ log.time }}</span>
<span class="log-property">{{ log.property }}</span>
<span class="log-change">{{ log.oldValue }} → {{ log.newValue }}</span>
</div>
</div>
</div>
</div>
<div class="watch-section">
<h4>深度监听和立即执行</h4>
<div class="deep-watch-demo">
<p>深度监听对象变化次数: {{ deepWatchCount }}</p>
<p>立即执行监听器触发次数: {{ immediateWatchCount }}</p>
<button @click="modifyNestedData">修改嵌套数据</button>
<button @click="replaceEntireObject">替换整个对象</button>
</div>
</div>
</div>
<!-- 响应式原理演示 -->
<div class="reactivity-demo">
<h3>响应式原理演示</h3>
<div class="reactivity-section">
<h4>响应式转换</h4>
<div class="reactivity-controls">
<button @click="createReactiveData">创建响应式数据</button>
<button @click="createShallowReactive">创建浅层响应式</button>
<button @click="createReadonlyData">创建只读数据</button>
<button @click="testReactivity">测试响应性</button>
</div>
<div class="reactivity-result">
<h5>响应式测试结果:</h5>
<pre>{{ reactivityTestResult }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import {
ref,
reactive,
computed,
watch,
watchEffect,
shallowReactive,
readonly,
isReactive,
isReadonly,
toRaw
} from 'vue'
export default {
name: 'DataMethodsDemo',
setup() {
// 响应式数据定义
const userInfo = reactive({
name: '张三',
age: 25,
email: 'zhangsan@example.com',
status: 'active',
skills: [
{ id: 1, name: 'JavaScript', level: 'advanced' },
{ id: 2, name: 'Vue.js', level: 'intermediate' },
{ id: 3, name: 'CSS', level: 'advanced' }
],
address: {
street: '中山路123号',
city: '北京',
zipCode: '100000'
},
preferences: {
newsletter: true,
notifications: false,
theme: 'light'
}
})
const newSkill = ref('')
const actionResult = ref('')
const processingResult = ref(null)
const watchLogs = ref([])
const deepWatchCount = ref(0)
const immediateWatchCount = ref(0)
const reactivityTestResult = ref('')
// 计算属性
const fullName = computed(() => {
return `${userInfo.name} (${userInfo.age}岁)`
})
const userSummary = computed(() => {
const skillCount = userInfo.skills.length
const statusText = userInfo.status === 'active' ? '活跃用户' : '非活跃用户'
return `${userInfo.name}是一位${statusText},掌握${skillCount}项技能`
})
const skillLevel = computed(() => {
const levels = userInfo.skills.map(skill => skill.level)
const advancedCount = levels.filter(level => level === 'advanced').length
const intermediateCount = levels.filter(level => level === 'intermediate').length
const beginnerCount = levels.filter(level => level === 'beginner').length
if (advancedCount >= intermediateCount + beginnerCount) {
return '高级'
} else if (intermediateCount >= beginnerCount) {
return '中级'
} else {
return '初级'
}
})
const fullAddress = computed(() => {
const { street, city, zipCode } = userInfo.address
return `${city} ${street} ${zipCode}`
})
const statusDescription = computed(() => {
const descriptions = {
active: '用户当前处于活跃状态,可以正常使用所有功能',
inactive: '用户当前处于非活跃状态,部分功能可能受限',
pending: '用户账户正在审核中,请耐心等待'
}
return descriptions[userInfo.status] || '未知状态'
})
const skillStats = computed(() => {
const skills = userInfo.skills
const total = skills.length
const advanced = skills.filter(skill => skill.level === 'advanced').length
const levelValues = { beginner: 1, intermediate: 2, advanced: 3 }
const averageLevel = skills.length > 0
? (skills.reduce((sum, skill) => sum + levelValues[skill.level], 0) / skills.length).toFixed(1)
: 0
return { total, advanced, averageLevel }
})
const userScore = computed(() => {
// 信息完整度计算
let completeness = 0
if (userInfo.name) completeness += 20
if (userInfo.email) completeness += 20
if (userInfo.age > 0) completeness += 20
if (userInfo.address.street && userInfo.address.city) completeness += 20
if (userInfo.skills.length > 0) completeness += 20
// 活跃度评分
const activity = userInfo.status === 'active' ? 100 :
userInfo.status === 'inactive' ? 50 : 30
// 综合评分
const overall = Math.round((completeness + activity) / 2)
return { completeness, activity, overall }
})
// 方法定义
const addLog = (property, oldValue, newValue) => {
watchLogs.value.unshift({
time: new Date().toLocaleTimeString(),
property,
oldValue: JSON.stringify(oldValue),
newValue: JSON.stringify(newValue)
})
// 限制日志数量
if (watchLogs.value.length > 20) {
watchLogs.value = watchLogs.value.slice(0, 20)
}
}
const addSkill = () => {
if (newSkill.value.trim()) {
const skill = {
id: Date.now(),
name: newSkill.value.trim(),
level: 'beginner'
}
userInfo.skills.push(skill)
newSkill.value = ''
}
}
const removeSkill = (index) => {
userInfo.skills.splice(index, 1)
}
const removeLastSkill = () => {
if (userInfo.skills.length > 0) {
userInfo.skills.pop()
}
}
const upgradeSkill = (index) => {
const skill = userInfo.skills[index]
const levels = ['beginner', 'intermediate', 'advanced']
const currentIndex = levels.indexOf(skill.level)
if (currentIndex < levels.length - 1) {
skill.level = levels[currentIndex + 1]
}
}
const shuffleSkills = () => {
for (let i = userInfo.skills.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[userInfo.skills[i], userInfo.skills[j]] = [userInfo.skills[j], userInfo.skills[i]]
}
}
const clearSkills = () => {
userInfo.skills.splice(0)
}
const validateUser = () => {
const errors = []
if (!userInfo.name.trim()) {
errors.push('姓名不能为空')
}
if (!userInfo.email.includes('@')) {
errors.push('邮箱格式不正确')
}
if (userInfo.age < 18 || userInfo.age > 100) {
errors.push('年龄必须在18-100之间')
}
if (userInfo.skills.length === 0) {
errors.push('至少需要一项技能')
}
actionResult.value = errors.length === 0
? '用户信息验证通过'
: `验证失败:${errors.join(', ')}`
}
const resetUser = () => {
Object.assign(userInfo, {
name: '张三',
age: 25,
email: 'zhangsan@example.com',
status: 'active',
skills: [
{ id: 1, name: 'JavaScript', level: 'advanced' }
],
address: {
street: '中山路123号',
city: '北京',
zipCode: '100000'
},
preferences: {
newsletter: true,
notifications: false,
theme: 'light'
}
})
actionResult.value = '用户信息已重置'
}
const exportUserData = () => {
const data = JSON.stringify(userInfo, null, 2)
actionResult.value = `用户数据已导出,大小:${data.length}字符`
// 模拟下载
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `user-data-${Date.now()}.json`
a.click()
URL.revokeObjectURL(url)
}
const generateRandomUser = () => {
const names = ['李四', '王五', '赵六', '孙七', '周八']
const cities = ['北京', '上海', '广州', '深圳', '杭州']
const skills = ['Python', 'Java', 'React', 'Angular', 'Node.js', 'TypeScript']
userInfo.name = names[Math.floor(Math.random() * names.length)]
userInfo.age = Math.floor(Math.random() * 50) + 20
userInfo.email = `${userInfo.name.toLowerCase()}@example.com`
userInfo.address.city = cities[Math.floor(Math.random() * cities.length)]
// 随机生成技能
const randomSkills = []
const skillCount = Math.floor(Math.random() * 3) + 1
for (let i = 0; i < skillCount; i++) {
const skill = skills[Math.floor(Math.random() * skills.length)]
if (!randomSkills.find(s => s.name === skill)) {
randomSkills.push({
id: Date.now() + i,
name: skill,
level: ['beginner', 'intermediate', 'advanced'][Math.floor(Math.random() * 3)]
})
}
}
userInfo.skills = randomSkills
actionResult.value = '随机用户数据已生成'
}
const processUserData = () => {
const startTime = performance.now()
// 模拟数据处理
const processedData = {
...userInfo,
processedAt: new Date().toISOString(),
hash: btoa(JSON.stringify(userInfo)).slice(0, 10)
}
const endTime = performance.now()
processingResult.value = {
timestamp: new Date().toLocaleTimeString(),
operation: '数据处理',
result: `处理完成,耗时${(endTime - startTime).toFixed(2)}ms`,
data: processedData
}
}
const calculateUserStats = () => {
const stats = {
nameLength: userInfo.name.length,
emailDomain: userInfo.email.split('@')[1],
skillCount: userInfo.skills.length,
addressComplete: !!(userInfo.address.street && userInfo.address.city && userInfo.address.zipCode),
preferencesSet: Object.values(userInfo.preferences).filter(Boolean).length
}
processingResult.value = {
timestamp: new Date().toLocaleTimeString(),
operation: '统计计算',
result: '统计计算完成',
data: stats
}
}
const formatUserInfo = () => {
const formatted = {
displayName: `${userInfo.name} (${userInfo.age}岁)`,
contactInfo: `${userInfo.email} | ${userInfo.address.city}`,
skillSummary: userInfo.skills.map(s => `${s.name}(${s.level})`).join(', '),
statusBadge: userInfo.status.toUpperCase()
}
processingResult.value = {
timestamp: new Date().toLocaleTimeString(),
operation: '格式化',
result: '格式化完成',
data: formatted
}
}
const backupUserData = () => {
const backup = JSON.parse(JSON.stringify(userInfo))
backup.backupTime = new Date().toISOString()
// 模拟保存到localStorage
localStorage.setItem('userBackup', JSON.stringify(backup))
processingResult.value = {
timestamp: new Date().toLocaleTimeString(),
operation: '备份',
result: '数据备份完成',
data: { backupSize: JSON.stringify(backup).length }
}
}
const triggerNameChange = () => {
userInfo.name = `${userInfo.name}_${Math.floor(Math.random() * 100)}`
}
const triggerAgeChange = () => {
userInfo.age = Math.floor(Math.random() * 50) + 20
}
const triggerSkillChange = () => {
if (userInfo.skills.length > 0) {
const randomIndex = Math.floor(Math.random() * userInfo.skills.length)
userInfo.skills[randomIndex].level = ['beginner', 'intermediate', 'advanced'][Math.floor(Math.random() * 3)]
}
}
const triggerAddressChange = () => {
userInfo.address.street = `新街道${Math.floor(Math.random() * 1000)}号`
}
const modifyNestedData = () => {
userInfo.preferences.theme = userInfo.preferences.theme === 'light' ? 'dark' : 'light'
userInfo.address.zipCode = String(Math.floor(Math.random() * 900000) + 100000)
}
const replaceEntireObject = () => {
Object.assign(userInfo.address, {
street: '全新街道999号',
city: '新城市',
zipCode: '999999'
})
}
const createReactiveData = () => {
const data = reactive({ test: 'reactive data' })
reactivityTestResult.value = `创建响应式数据: isReactive = ${isReactive(data)}`
}
const createShallowReactive = () => {
const data = shallowReactive({ test: { nested: 'shallow reactive' } })
reactivityTestResult.value = `创建浅层响应式: isReactive = ${isReactive(data)}, nested isReactive = ${isReactive(data.test)}`
}
const createReadonlyData = () => {
const data = readonly({ test: 'readonly data' })
reactivityTestResult.value = `创建只读数据: isReadonly = ${isReadonly(data)}`
}
const testReactivity = () => {
const raw = toRaw(userInfo)
reactivityTestResult.value = `原始对象测试: isReactive(userInfo) = ${isReactive(userInfo)}, isReactive(raw) = ${isReactive(raw)}`
}
// 监听器设置
watch(() => userInfo.name, (newVal, oldVal) => {
addLog('name', oldVal, newVal)
})
watch(() => userInfo.age, (newVal, oldVal) => {
addLog('age', oldVal, newVal)
})
watch(() => userInfo.skills, (newVal, oldVal) => {
addLog('skills', `${oldVal?.length} items`, `${newVal.length} items`)
}, { deep: true })
watch(() => userInfo.address, (newVal, oldVal) => {
addLog('address', 'address object', 'address object')
}, { deep: true })
// 深度监听
watch(userInfo, () => {
deepWatchCount.value++
}, { deep: true })
// 立即执行监听
watch(() => userInfo.status, (newVal) => {
immediateWatchCount.value++
console.log('状态变化:', newVal)
}, { immediate: true })
return {
// 数据
userInfo,
newSkill,
actionResult,
processingResult,
watchLogs,
deepWatchCount,
immediateWatchCount,
reactivityTestResult,
// 计算属性
fullName,
userSummary,
skillLevel,
fullAddress,
statusDescription,
skillStats,
userScore,
// 方法
addSkill,
removeSkill,
removeLastSkill,
upgradeSkill,
shuffleSkills,
clearSkills,
validateUser,
resetUser,
exportUserData,
generateRandomUser,
processUserData,
calculateUserStats,
formatUserInfo,
backupUserData,
triggerNameChange,
triggerAgeChange,
triggerSkillChange,
triggerAddressChange,
modifyNestedData,
replaceEntireObject,
createReactiveData,
createShallowReactive,
createReadonlyData,
testReactivity
}
}
}
</script>
<style scoped>
.data-methods-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.basic-data,
.methods-demo,
.computed-demo,
.watch-demo,
.reactivity-demo {
margin: 30px 0;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #fafafa;
}
.basic-data h3,
.methods-demo h3,
.computed-demo h3,
.watch-demo h3,
.reactivity-demo h3 {
margin-top: 0;
color: #42b983;
border-bottom: 2px solid #42b983;
padding-bottom: 10px;
}
.data-section,
.method-section,
.computed-section,
.watch-section,
.reactivity-section {
margin: 20px 0;
padding: 15px;
background-color: white;
border-radius: 8px;
border: 1px solid #ddd;
}
.data-section h4,
.method-section h4,
.computed-section h4,
.watch-section h4,
.reactivity-section h4 {
margin-top: 0;
color: #666;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
.user-info {
display: flex;
flex-direction: column;
gap: 15px;
}
.info-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
}
.info-item label {
min-width: 60px;
font-weight: bold;
color: #333;
}
.info-item input,
.info-item select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 150px;
}
.info-item span {
color: #42b983;
font-weight: bold;
}
.status.active { color: #28a745; }
.status.inactive { color: #dc3545; }
.status.pending { color: #ffc107; }
.array-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.skills-list {
display: flex;
flex-direction: column;
gap: 8px;
margin: 15px 0;
}
.skill-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.skill-name {
flex: 1;
font-weight: bold;
color: #333;
}
.skill-level {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
color: white;
}
.skill-level {
background-color: #6c757d;
}
.remove-btn,
.upgrade-btn {
padding: 4px 8px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.remove-btn {
background-color: #dc3545;
color: white;
}
.upgrade-btn {
background-color: #28a745;
color: white;
}
.nested-data {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.address-form,
.preferences-form {
flex: 1;
min-width: 250px;
}
.address-form input,
.preferences-form input,
.preferences-form select {
margin: 5px;
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.preferences-form label {
display: block;
margin: 8px 0;
}
.user-actions,
.data-processing,
.reactivity-controls,
.watch-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.action-result,
.processing-result,
.reactivity-result {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
border: 1px solid #dee2e6;
margin-top: 15px;
}
.action-result pre,
.processing-result pre,
.reactivity-result pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
}
.computed-display {
display: flex;
flex-direction: column;
gap: 10px;
}
.computed-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background-color: #e8f5e8;
border-radius: 4px;
border-left: 4px solid #28a745;
}
.computed-item label {
min-width: 120px;
font-weight: bold;
color: #333;
}
.computed-item span {
color: #28a745;
font-weight: bold;
}
.stats-display {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
padding: 15px;
background-color: #fff3e0;
border-radius: 8px;
border: 1px solid #ffcc02;
}
.stat-card h5 {
margin-top: 0;
color: #e65100;
}
.stat-card p {
margin: 8px 0;
color: #333;
}
.log-container {
max-height: 300px;
overflow-y: auto;
background-color: #1e1e1e;
border-radius: 4px;
padding: 10px;
font-family: 'Courier New', monospace;
}
.log-entry {
display: flex;
gap: 10px;
margin: 5px 0;
padding: 5px;
border-radius: 4px;
font-size: 12px;
color: #fff;
}
.log-time {
color: #999;
min-width: 80px;
}
.log-property {
color: #42b983;
min-width: 80px;
font-weight: bold;
}
.log-change {
color: #ffc107;
flex: 1;
}
.deep-watch-demo {
background-color: #f0f9ff;
padding: 15px;
border-radius: 4px;
border-left: 4px solid #0ea5e9;
}
button {
padding: 8px 16px;
border: 1px solid #42b983;
background-color: #42b983;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #369870;
}
button:disabled {
background-color: #ccc;
border-color: #ccc;
cursor: not-allowed;
}
</style>方法定义在Vue中有多种方式,需要根据场景选择:
// 选项式API方法定义
export default {
data() {
return {
count: 0,
user: { name: 'Vue' }
}
},
methods: {
// 普通方法
increment() {
this.count++
},
// 异步方法
async fetchData() {
try {
const response = await fetch('/api/data')
return await response.json()
} catch (error) {
console.error('获取数据失败:', error)
}
},
// 带参数的方法
updateUser(name, age) {
this.user.name = name
this.user.age = age
},
// 返回值的方法
calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0)
}
}
}
// 组合式API方法定义
export default {
setup() {
const count = ref(0)
const user = reactive({ name: 'Vue' })
// 普通方法
const increment = () => {
count.value++
}
// 异步方法
const fetchData = async () => {
try {
const response = await fetch('/api/data')
return await response.json()
} catch (error) {
console.error('获取数据失败:', error)
}
}
// 带参数的方法
const updateUser = (name, age) => {
user.name = name
user.age = age
}
// 返回值的方法
const calculateTotal = (items) => {
return items.reduce((sum, item) => sum + item.price, 0)
}
return {
count,
user,
increment,
fetchData,
updateUser,
calculateTotal
}
}
}方法定义的最佳实践:
💼 性能考虑:避免在方法中进行重型计算,考虑使用计算属性或缓存机制。
export default {
setup() {
const items = ref([])
const filter = ref('')
const sortBy = ref('name')
// 基础计算属性
const filteredItems = computed(() => {
console.log('计算属性重新计算') // 只在依赖变化时执行
return items.value.filter(item =>
item.name.toLowerCase().includes(filter.value.toLowerCase())
)
})
// 复杂计算属性
const sortedAndFilteredItems = computed(() => {
const filtered = filteredItems.value
return [...filtered].sort((a, b) => {
const aVal = a[sortBy.value]
const bVal = b[sortBy.value]
return typeof aVal === 'string'
? aVal.localeCompare(bVal)
: aVal - bVal
})
})
// 可写计算属性
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value) {
const names = value.split(' ')
firstName.value = names[0] || ''
lastName.value = names[1] || ''
}
})
return {
items,
filter,
sortBy,
filteredItems,
sortedAndFilteredItems,
fullName
}
}
}export default {
setup() {
const user = reactive({
profile: {
name: 'Vue',
settings: {
theme: 'light'
}
}
})
// 基础监听
watch(() => user.profile.name, (newVal, oldVal) => {
console.log(`姓名变化: ${oldVal} -> ${newVal}`)
})
// 深度监听
watch(user, (newVal, oldVal) => {
console.log('用户对象发生变化')
}, { deep: true })
// 立即执行
watch(() => user.profile.name, (name) => {
document.title = `用户: ${name}`
}, { immediate: true })
// 监听多个源
watch([() => user.profile.name, () => user.profile.settings.theme],
([newName, newTheme], [oldName, oldTheme]) => {
console.log('姓名或主题发生变化')
}
)
// 停止监听
const stopWatcher = watch(() => user.profile.name, () => {
console.log('这个监听器可以被停止')
})
// 在某个条件下停止监听
if (someCondition) {
stopWatcher()
}
// watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(`当前用户: ${user.profile.name}, 主题: ${user.profile.settings.theme}`)
})
return { user }
}
}A: data是选项式API的数据定义方式,reactive是组合式API的响应式转换函数。reactive更灵活,支持任意对象类型。
A: computed用于基于现有数据计算派生数据,有缓存机制。methods用于处理用户交互和业务逻辑,每次调用都会执行。
A: watch需要明确指定监听的数据源,watchEffect会自动追踪函数内使用的响应式数据。watchEffect更简洁,watch更精确。
A: 1)避免深层嵌套;2)使用shallowReactive处理大对象;3)合理使用readonly;4)及时清理不需要的监听器。
A: reactive对象可以修改属性但不能整体替换,ref可以通过.value重新赋值。使用Object.assign()可以批量更新reactive对象。
// 响应式数据调试工具
import { effect, stop } from 'vue'
const trackReactiveAccess = (target, property) => {
let accessCount = 0
const runner = effect(() => {
const value = target[property]
accessCount++
console.log(`${property} 被访问第 ${accessCount} 次,值为:`, value)
})
return () => stop(runner)
}// 计算属性性能监控
const monitorComputed = (computedFn, name) => {
return computed(() => {
const start = performance.now()
const result = computedFn()
const end = performance.now()
console.log(`计算属性 ${name} 执行时间: ${end - start}ms`)
return result
})
}"Vue的数据和方法是构建响应式应用的基础。通过掌握响应式数据定义、方法编写、计算属性和监听器的使用,你已经具备了开发复杂Vue应用的核心技能。记住,良好的数据设计和方法组织是高质量Vue应用的关键。下一步,我们将深入学习Vue的事件处理机制!"