本章将深入学习Vue.js的核心语法,包括模板语法、指令系统、事件处理和数据绑定等基础概念。

3.1 模板语法

文本插值

Vue.js使用双大括号(Mustache语法)进行文本插值。

<template>
  <div>
    <!-- 基本文本插值 -->
    <p>{{ message }}</p>
    
    <!-- 表达式插值 -->
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>
    
    <!-- 方法调用 -->
    <p>{{ formatMessage(message) }}</p>
    
    <!-- 一次性插值 -->
    <p v-once>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue.js!',
      number: 42,
      ok: true
    }
  },
  methods: {
    formatMessage(msg) {
      return msg.toUpperCase()
    }
  }
}
</script>
# 运行示例
npm run dev
# 在浏览器中查看效果

HTML插值

<template>
  <div>
    <!-- 普通插值(会转义HTML) -->
    <p>{{ rawHtml }}</p>
    
    <!-- HTML插值(不会转义) -->
    <p v-html="rawHtml"></p>
    
    <!-- 安全的HTML插值 -->
    <div v-html="sanitizedHtml"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      rawHtml: '<span style="color: red">红色文本</span>'
    }
  },
  computed: {
    sanitizedHtml() {
      // 在实际项目中,应该使用DOMPurify等库来清理HTML
      return this.rawHtml.replace(/<script[^>]*>.*?<\/script>/gi, '')
    }
  }
}
</script>

属性绑定

<template>
  <div>
    <!-- 基本属性绑定 -->
    <div v-bind:id="dynamicId">动态ID</div>
    <div :id="dynamicId">简写语法</div>
    
    <!-- 布尔属性 -->
    <button :disabled="isButtonDisabled">按钮</button>
    
    <!-- 多个属性 -->
    <div v-bind="objectOfAttrs">多属性绑定</div>
    
    <!-- 动态属性名 -->
    <a :[attributeName]="url">动态属性</a>
    
    <!-- 修饰符 -->
    <div :text-content.prop="message">修饰符示例</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dynamicId: 'my-id',
      isButtonDisabled: false,
      objectOfAttrs: {
        id: 'container',
        class: 'wrapper'
      },
      attributeName: 'href',
      url: 'https://vuejs.org',
      message: 'Hello World'
    }
  }
}
</script>

3.2 指令系统

v-if条件渲染

<template>
  <div>
    <!-- 基本条件渲染 -->
    <h1 v-if="awesome">Vue is awesome!</h1>
    <h1 v-else>Oh no 😢</h1>
    
    <!-- v-else-if -->
    <div v-if="type === 'A'">A</div>
    <div v-else-if="type === 'B'">B</div>
    <div v-else-if="type === 'C'">C</div>
    <div v-else>Not A/B/C</div>
    
    <!-- template包装 -->
    <template v-if="loginType === 'username'">
      <label>Username</label>
      <input placeholder="Enter your username" key="username-input">
    </template>
    <template v-else>
      <label>Email</label>
      <input placeholder="Enter your email address" key="email-input">
    </template>
    
    <!-- 切换按钮 -->
    <button @click="toggleLoginType">切换登录类型</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      awesome: true,
      type: 'A',
      loginType: 'username'
    }
  },
  methods: {
    toggleLoginType() {
      this.loginType = this.loginType === 'username' ? 'email' : 'username'
    }
  }
}
</script>

v-show显示隐藏

<template>
  <div>
    <!-- v-show vs v-if -->
    <h1 v-show="showTitle">使用v-show的标题</h1>
    <h1 v-if="showTitle">使用v-if的标题</h1>
    
    <!-- 切换按钮 -->
    <button @click="showTitle = !showTitle">
      {{ showTitle ? '隐藏' : '显示' }}标题
    </button>
    
    <!-- 性能对比示例 -->
    <div class="performance-demo">
      <div v-show="toggle" class="expensive-component">
        v-show: 频繁切换时性能更好
      </div>
      <div v-if="toggle" class="expensive-component">
        v-if: 初始渲染成本更低
      </div>
    </div>
    
    <button @click="toggle = !toggle">快速切换</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showTitle: true,
      toggle: true
    }
  }
}
</script>

<style scoped>
.expensive-component {
  padding: 20px;
  margin: 10px;
  border: 1px solid #ccc;
  background: #f9f9f9;
}
</style>

v-for列表渲染

<template>
  <div>
    <!-- 遍历数组 -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.message }}
      </li>
    </ul>
    
    <!-- 带索引的遍历 -->
    <ul>
      <li v-for="(item, index) in items" :key="item.id">
        {{ index }} - {{ item.message }}
      </li>
    </ul>
    
    <!-- 遍历对象 -->
    <ul>
      <li v-for="value in object" :key="value">
        {{ value }}
      </li>
    </ul>
    
    <!-- 遍历对象(带键名) -->
    <ul>
      <li v-for="(value, name) in object" :key="name">
        {{ name }}: {{ value }}
      </li>
    </ul>
    
    <!-- 遍历对象(带索引) -->
    <ul>
      <li v-for="(value, name, index) in object" :key="name">
        {{ index }}. {{ name }}: {{ value }}
      </li>
    </ul>
    
    <!-- 遍历数字 -->
    <span v-for="n in 10" :key="n">{{ n }}</span>
    
    <!-- template包装 -->
    <template v-for="item in items" :key="item.id">
      <li>{{ item.message }}</li>
      <li class="divider" role="presentation"></li>
    </template>
    
    <!-- 嵌套循环 -->
    <ul v-for="set in sets" :key="set.id">
      <li v-for="n in even(set)" :key="n">{{ n }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, message: 'Foo' },
        { id: 2, message: 'Bar' },
        { id: 3, message: 'Baz' }
      ],
      object: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      },
      sets: [
        { id: 1, numbers: [1, 2, 3, 4, 5] },
        { id: 2, numbers: [6, 7, 8, 9, 10] }
      ]
    }
  },
  methods: {
    even(set) {
      return set.numbers.filter(number => number % 2 === 0)
    }
  }
}
</script>

v-model双向绑定

<template>
  <div>
    <!-- 文本输入 -->
    <input v-model="message" placeholder="编辑我">
    <p>消息是: {{ message }}</p>
    
    <!-- 多行文本 -->
    <textarea v-model="multilineMessage" placeholder="添加多行文本"></textarea>
    <p style="white-space: pre-line;">{{ multilineMessage }}</p>
    
    <!-- 复选框 -->
    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">{{ checked }}</label>
    
    <!-- 多个复选框 -->
    <div>
      <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
      <label for="jack">Jack</label>
      <input type="checkbox" id="john" value="John" v-model="checkedNames">
      <label for="john">John</label>
      <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
      <label for="mike">Mike</label>
      <br>
      <span>选中的名字: {{ checkedNames }}</span>
    </div>
    
    <!-- 单选按钮 -->
    <div>
      <input type="radio" id="one" value="One" v-model="picked">
      <label for="one">One</label>
      <br>
      <input type="radio" id="two" value="Two" v-model="picked">
      <label for="two">Two</label>
      <br>
      <span>选中: {{ picked }}</span>
    </div>
    
    <!-- 选择框 -->
    <div>
      <select v-model="selected">
        <option disabled value="">请选择</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>
      <span>选中: {{ selected }}</span>
    </div>
    
    <!-- 多选 -->
    <div>
      <select v-model="multiSelected" multiple style="width: 50px;">
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>
      <br>
      <span>选中: {{ multiSelected }}</span>
    </div>
    
    <!-- 动态选项 -->
    <select v-model="dynamicSelected">
      <option v-for="option in options" :value="option.value" :key="option.value">
        {{ option.text }}
      </option>
    </select>
    <span>选中: {{ dynamicSelected }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '',
      multilineMessage: '',
      checked: false,
      checkedNames: [],
      picked: '',
      selected: '',
      multiSelected: [],
      dynamicSelected: 'A',
      options: [
        { text: 'One', value: 'A' },
        { text: 'Two', value: 'B' },
        { text: 'Three', value: 'C' }
      ]
    }
  }
}
</script>

v-model修饰符

<template>
  <div>
    <!-- .lazy修饰符 -->
    <input v-model.lazy="lazyMsg" placeholder="失去焦点时更新">
    <p>{{ lazyMsg }}</p>
    
    <!-- .number修饰符 -->
    <input v-model.number="age" type="number" placeholder="自动转换为数字">
    <p>年龄: {{ age }} (类型: {{ typeof age }})</p>
    
    <!-- .trim修饰符 -->
    <input v-model.trim="trimmedMsg" placeholder="自动去除首尾空格">
    <p>"{{ trimmedMsg }}"</p>
    
    <!-- 组合修饰符 -->
    <input v-model.lazy.trim="combinedMsg" placeholder="组合修饰符">
    <p>"{{ combinedMsg }}"</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      lazyMsg: '',
      age: 0,
      trimmedMsg: '',
      combinedMsg: ''
    }
  }
}
</script>

3.3 事件处理

基本事件监听

<template>
  <div>
    <!-- 基本事件处理 -->
    <button @click="counter += 1">增加 1</button>
    <p>按钮被点击了 {{ counter }} 次。</p>
    
    <!-- 方法事件处理器 -->
    <button @click="greet">问候</button>
    
    <!-- 内联处理器中的方法 -->
    <button @click="say('hi')">说 hi</button>
    <button @click="say('what')">说 what</button>
    
    <!-- 访问原始DOM事件 -->
    <button @click="warn('表单还不能提交。', $event)">
      提交
    </button>
    
    <!-- 多个事件处理器 -->
    <button @click="one($event), two($event)">
      多个处理器
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0,
      name: 'Vue.js'
    }
  },
  methods: {
    greet(event) {
      alert('Hello ' + this.name + '!')
      if (event) {
        alert(event.target.tagName)
      }
    },
    say(message) {
      alert(message)
    },
    warn(message, event) {
      if (event) {
        event.preventDefault()
      }
      alert(message)
    },
    one(event) {
      console.log('第一个处理器', event)
    },
    two(event) {
      console.log('第二个处理器', event)
    }
  }
}
</script>

事件修饰符

<template>
  <div>
    <!-- 阻止默认行为 -->
    <form @submit.prevent="onSubmit">
      <input type="submit" value="提交">
    </form>
    
    <!-- 阻止事件冒泡 -->
    <div @click="divClick">
      <button @click.stop="buttonClick">阻止冒泡</button>
    </div>
    
    <!-- 修饰符可以串联 -->
    <a @click.stop.prevent="doThat" href="#">串联修饰符</a>
    
    <!-- 只有修饰符 -->
    <form @submit.prevent></form>
    
    <!-- 添加事件监听器时使用事件捕获模式 -->
    <div @click.capture="doThis">捕获模式</div>
    
    <!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
    <div @click.self="doThat">只在自身触发</div>
    
    <!-- 点击事件将只会触发一次 -->
    <a @click.once="doThis">只触发一次</a>
    
    <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
    <div @scroll.passive="onScroll">被动监听</div>
  </div>
</template>

<script>
export default {
  methods: {
    onSubmit() {
      console.log('表单提交')
    },
    divClick() {
      console.log('div被点击')
    },
    buttonClick() {
      console.log('按钮被点击')
    },
    doThat() {
      console.log('执行操作')
    },
    doThis() {
      console.log('执行这个')
    },
    onScroll() {
      console.log('滚动事件')
    }
  }
}
</script>

按键修饰符

<template>
  <div>
    <!-- 按键修饰符 -->
    <input @keyup.enter="submit" placeholder="按回车提交">
    <input @keyup.tab="handleTab" placeholder="按Tab键">
    <input @keyup.delete="handleDelete" placeholder="按删除键">
    <input @keyup.esc="handleEscape" placeholder="按ESC键">
    <input @keyup.space="handleSpace" placeholder="按空格键">
    <input @keyup.up="handleUp" placeholder="按上箭头">
    <input @keyup.down="handleDown" placeholder="按下箭头">
    <input @keyup.left="handleLeft" placeholder="按左箭头">
    <input @keyup.right="handleRight" placeholder="按右箭头">
    
    <!-- 系统修饰键 -->
    <input @keyup.ctrl="handleCtrl" placeholder="按Ctrl键">
    <input @keyup.alt="handleAlt" placeholder="按Alt键">
    <input @keyup.shift="handleShift" placeholder="按Shift键">
    <input @keyup.meta="handleMeta" placeholder="按Meta键">
    
    <!-- 组合按键 -->
    <input @keyup.ctrl.enter="handleCtrlEnter" placeholder="Ctrl+Enter">
    <input @keyup.alt.67="handleAltC" placeholder="Alt+C">
    
    <!-- 精确修饰符 -->
    <button @click.ctrl.exact="onCtrlClick">Ctrl+点击</button>
    <button @click.exact="onClick">仅点击</button>
    
    <!-- 鼠标按钮修饰符 -->
    <button @click.left="onLeftClick">左键点击</button>
    <button @click.right="onRightClick">右键点击</button>
    <button @click.middle="onMiddleClick">中键点击</button>
  </div>
</template>

<script>
export default {
  methods: {
    submit() {
      console.log('提交表单')
    },
    handleTab() {
      console.log('Tab键被按下')
    },
    handleDelete() {
      console.log('删除键被按下')
    },
    handleEscape() {
      console.log('ESC键被按下')
    },
    handleSpace() {
      console.log('空格键被按下')
    },
    handleUp() {
      console.log('上箭头被按下')
    },
    handleDown() {
      console.log('下箭头被按下')
    },
    handleLeft() {
      console.log('左箭头被按下')
    },
    handleRight() {
      console.log('右箭头被按下')
    },
    handleCtrl() {
      console.log('Ctrl键被按下')
    },
    handleAlt() {
      console.log('Alt键被按下')
    },
    handleShift() {
      console.log('Shift键被按下')
    },
    handleMeta() {
      console.log('Meta键被按下')
    },
    handleCtrlEnter() {
      console.log('Ctrl+Enter被按下')
    },
    handleAltC() {
      console.log('Alt+C被按下')
    },
    onCtrlClick() {
      console.log('Ctrl+点击')
    },
    onClick() {
      console.log('仅点击')
    },
    onLeftClick() {
      console.log('左键点击')
    },
    onRightClick() {
      console.log('右键点击')
    },
    onMiddleClick() {
      console.log('中键点击')
    }
  }
}
</script>

3.4 计算属性和侦听器

计算属性

<template>
  <div>
    <!-- 基本计算属性 -->
    <p>原始消息: "{{ message }}"</p>
    <p>计算后的反转消息: "{{ reversedMessage }}"</p>
    
    <!-- 计算属性 vs 方法 -->
    <p>计算属性: {{ reversedMessage }}</p>
    <p>方法: {{ reverseMessage() }}</p>
    
    <!-- 复杂计算属性 -->
    <div>
      <h3>购物车</h3>
      <div v-for="item in cartItems" :key="item.id">
        {{ item.name }} - 数量: {{ item.quantity }} - 价格: ${{ item.price }}
      </div>
      <p><strong>总价: ${{ totalPrice }}</strong></p>
      <p><strong>商品总数: {{ totalQuantity }}</strong></p>
    </div>
    
    <!-- 计算属性的getter和setter -->
    <div>
      <p>全名: {{ fullName }}</p>
      <input v-model="firstName" placeholder="名">
      <input v-model="lastName" placeholder="姓">
      <input v-model="fullName" placeholder="全名">
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue.js',
      firstName: 'John',
      lastName: 'Doe',
      cartItems: [
        { id: 1, name: '苹果', quantity: 3, price: 2.5 },
        { id: 2, name: '香蕉', quantity: 2, price: 1.8 },
        { id: 3, name: '橙子', quantity: 1, price: 3.2 }
      ]
    }
  },
  computed: {
    // 基本计算属性
    reversedMessage() {
      return this.message.split('').reverse().join('')
    },
    
    // 复杂计算属性
    totalPrice() {
      return this.cartItems.reduce((total, item) => {
        return total + (item.quantity * item.price)
      }, 0).toFixed(2)
    },
    
    totalQuantity() {
      return this.cartItems.reduce((total, item) => {
        return total + item.quantity
      }, 0)
    },
    
    // 带getter和setter的计算属性
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  },
  methods: {
    reverseMessage() {
      return this.message.split('').reverse().join('')
    }
  }
}
</script>

侦听器

<template>
  <div>
    <!-- 基本侦听器 -->
    <div>
      <p>问一个 yes/no 问题:</p>
      <input v-model="question">
      <p>{{ answer }}</p>
    </div>
    
    <!-- 深度侦听 -->
    <div>
      <h3>用户信息</h3>
      <input v-model="user.name" placeholder="姓名">
      <input v-model="user.age" type="number" placeholder="年龄">
      <input v-model="user.email" placeholder="邮箱">
      <p>用户信息变化次数: {{ userChangeCount }}</p>
    </div>
    
    <!-- 数组侦听 -->
    <div>
      <h3>待办事项</h3>
      <input v-model="newTodo" @keyup.enter="addTodo" placeholder="添加待办事项">
      <button @click="addTodo">添加</button>
      <ul>
        <li v-for="(todo, index) in todos" :key="index">
          {{ todo }}
          <button @click="removeTodo(index)">删除</button>
        </li>
      </ul>
      <p>待办事项变化次数: {{ todoChangeCount }}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      answer: '问题通常以问号结尾。;-)',
      user: {
        name: '',
        age: 0,
        email: ''
      },
      userChangeCount: 0,
      todos: ['学习Vue.js', '构建项目'],
      todoChangeCount: 0,
      newTodo: ''
    }
  },
  watch: {
    // 基本侦听器
    question(newQuestion, oldQuestion) {
      if (newQuestion.indexOf('?') > -1) {
        this.getAnswer()
      }
    },
    
    // 深度侦听对象
    user: {
      handler(newUser, oldUser) {
        console.log('用户信息发生变化:', newUser)
        this.userChangeCount++
      },
      deep: true
    },
    
    // 侦听对象的特定属性
    'user.name'(newName, oldName) {
      console.log(`用户名从 ${oldName} 变为 ${newName}`)
    },
    
    // 侦听数组
    todos: {
      handler(newTodos, oldTodos) {
        console.log('待办事项发生变化:', newTodos)
        this.todoChangeCount++
      },
      deep: true
    },
    
    // 立即执行的侦听器
    question: {
      handler(newQuestion) {
        console.log('问题:', newQuestion)
      },
      immediate: true
    }
  },
  methods: {
    getAnswer() {
      this.answer = '思考中...'
      setTimeout(() => {
        this.answer = Math.random() > 0.5 ? '是的' : '不是'
      }, 1000)
    },
    
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push(this.newTodo.trim())
        this.newTodo = ''
      }
    },
    
    removeTodo(index) {
      this.todos.splice(index, 1)
    }
  }
}
</script>

3.5 Class与Style绑定

Class绑定

<template>
  <div>
    <!-- 对象语法 -->
    <div :class="{ active: isActive, 'text-danger': hasError }">
      对象语法绑定Class
    </div>
    
    <!-- 绑定数据对象 -->
    <div :class="classObject">绑定数据对象</div>
    
    <!-- 绑定计算属性 -->
    <div :class="computedClassObject">绑定计算属性</div>
    
    <!-- 数组语法 -->
    <div :class="[activeClass, errorClass]">数组语法</div>
    
    <!-- 数组中使用对象语法 -->
    <div :class="[{ active: isActive }, errorClass]">数组中的对象语法</div>
    
    <!-- 数组中使用三元表达式 -->
    <div :class="[isActive ? activeClass : '', errorClass]">三元表达式</div>
    
    <!-- 组件上的class -->
    <my-component :class="{ active: isActive }"></my-component>
    
    <!-- 控制按钮 -->
    <div class="controls">
      <button @click="isActive = !isActive">切换Active</button>
      <button @click="hasError = !hasError">切换Error</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isActive: true,
      hasError: false,
      activeClass: 'active',
      errorClass: 'text-danger'
    }
  },
  computed: {
    classObject() {
      return {
        active: this.isActive && !this.hasError,
        'text-danger': this.hasError && this.hasError.type === 'fatal'
      }
    },
    
    computedClassObject() {
      return {
        active: this.isActive,
        'text-danger': this.hasError,
        'font-bold': this.isActive && this.hasError
      }
    }
  }
}
</script>

<style scoped>
.active {
  background-color: #42b983;
  color: white;
  padding: 10px;
  margin: 5px;
}

.text-danger {
  color: #e74c3c;
  border: 2px solid #e74c3c;
}

.font-bold {
  font-weight: bold;
}

.controls {
  margin-top: 20px;
}

.controls button {
  margin-right: 10px;
  padding: 5px 10px;
}
</style>

Style绑定

<template>
  <div>
    <!-- 对象语法 -->
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
      对象语法绑定Style
    </div>
    
    <!-- 绑定样式对象 -->
    <div :style="styleObject">绑定样式对象</div>
    
    <!-- 数组语法 -->
    <div :style="[baseStyles, overridingStyles]">数组语法</div>
    
    <!-- 自动添加前缀 -->
    <div :style="{ transform: 'rotate(' + rotation + 'deg)' }">
      自动添加浏览器前缀
    </div>
    
    <!-- 多重值 -->
    <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">
      多重值(浏览器会选择支持的最后一个值)
    </div>
    
    <!-- 动态样式示例 -->
    <div class="dynamic-box" :style="dynamicStyles">
      动态样式盒子
    </div>
    
    <!-- 控制面板 -->
    <div class="controls">
      <label>
        颜色: 
        <input type="color" v-model="activeColor">
      </label>
      <label>
        字体大小: 
        <input type="range" min="12" max="48" v-model="fontSize">
        {{ fontSize }}px
      </label>
      <label>
        旋转角度: 
        <input type="range" min="0" max="360" v-model="rotation">
        {{ rotation }}°
      </label>
      <label>
        背景色: 
        <input type="color" v-model="backgroundColor">
      </label>
      <label>
        边框宽度: 
        <input type="range" min="0" max="10" v-model="borderWidth">
        {{ borderWidth }}px
      </label>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeColor: '#42b983',
      fontSize: 16,
      rotation: 0,
      backgroundColor: '#f0f0f0',
      borderWidth: 2
    }
  },
  computed: {
    styleObject() {
      return {
        color: this.activeColor,
        fontSize: this.fontSize + 'px',
        fontWeight: 'bold',
        padding: '10px',
        margin: '5px 0'
      }
    },
    
    baseStyles() {
      return {
        padding: '10px',
        margin: '5px 0',
        border: '1px solid #ccc'
      }
    },
    
    overridingStyles() {
      return {
        color: this.activeColor,
        fontSize: this.fontSize + 'px'
      }
    },
    
    dynamicStyles() {
      return {
        backgroundColor: this.backgroundColor,
        color: this.activeColor,
        fontSize: this.fontSize + 'px',
        border: `${this.borderWidth}px solid ${this.activeColor}`,
        transform: `rotate(${this.rotation}deg)`,
        transition: 'all 0.3s ease',
        padding: '20px',
        margin: '10px 0',
        textAlign: 'center',
        borderRadius: '8px'
      }
    }
  }
}
</script>

<style scoped>
.controls {
  margin-top: 20px;
  padding: 20px;
  background: #f9f9f9;
  border-radius: 8px;
}

.controls label {
  display: block;
  margin-bottom: 10px;
  font-weight: bold;
}

.controls input {
  margin-left: 10px;
}

.dynamic-box {
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

本章小结

本章我们学习了Vue.js的核心语法:

  1. 模板语法:文本插值、HTML插值、属性绑定
  2. 指令系统:v-if、v-show、v-for、v-model等核心指令
  3. 事件处理:事件监听、事件修饰符、按键修饰符
  4. 计算属性:基本用法、getter/setter、与方法的区别
  5. 侦听器:基本侦听、深度侦听、立即执行
  6. 样式绑定:Class绑定、Style绑定的多种语法

下一章预告

下一章我们将学习Vue.js的组件系统,包括: - 组件的定义和使用 - 组件间通信 - 插槽系统 - 动态组件

练习题

基础练习

  1. 模板语法练习

    • 创建一个用户信息展示组件
    • 使用插值语法显示用户数据
    • 实现条件渲染显示不同状态
  2. 列表渲染练习

    • 创建一个待办事项列表
    • 实现添加、删除、标记完成功能
    • 使用v-for渲染列表项

进阶练习

  1. 表单处理练习

    • 创建一个用户注册表单
    • 使用v-model绑定各种表单元素
    • 实现表单验证和提交
  2. 计算属性练习

    • 创建一个购物车组件
    • 使用计算属性计算总价和商品数量
    • 实现商品筛选和排序功能

提示:多练习这些基础语法,它们是Vue.js开发的基石。