模板语法与数据绑定
模板语法基础
uni-app的模板语法基于Vue.js,提供了一套简洁的声明式渲染机制。在.vue
文件的<template>
部分,开发者可以使用各种指令和插值表达式来构建UI界面。
<template>
<view>
<!-- 文本插值 -->
<text>{{ message }}</text>
<!-- 属性绑定 -->
<image :src="imgUrl" mode="aspectFit"></image>
<!-- 条件渲染 -->
<view v-if="showContent">显示内容</view>
<!-- 列表渲染 -->
<view v-for="(item, index) in list" :key="index">
{{ item.name }}
</view>
</view>
</template>
数据绑定类型
单向数据绑定
单向数据绑定使用{{ }}
插值表达式或v-bind
指令(简写为:
),数据从JavaScript流向DOM:
<template>
<view>
<!-- 文本插值 -->
<text>当前计数:{{ count }}</text>
<!-- 属性绑定 -->
<input :value="inputValue" />
<!-- 类名绑定 -->
<view :class="{ active: isActive }">动态类名</view>
<!-- 样式绑定 -->
<view :style="{ color: textColor, fontSize: fontSize + 'px' }">
动态样式
</view>
</view>
</template>
<script>
export default {
data() {
return {
count: 0,
inputValue: '',
isActive: true,
textColor: '#ff0000',
fontSize: 16
}
}
}
</script>
双向数据绑定
双向数据绑定使用v-model
指令,主要应用于表单元素,实现数据在DOM和JavaScript之间的双向同步:
<template>
<view>
<!-- 输入框双向绑定 -->
<input v-model="username" placeholder="请输入用户名" />
<!-- 单选按钮双向绑定 -->
<radio-group v-model="gender">
<radio value="male" />男
<radio value="female" />女
</radio-group>
<!-- 复选框双向绑定 -->
<checkbox-group v-model="hobbies">
<checkbox value="reading" />阅读
<checkbox value="sports" />运动
<checkbox value="music" />音乐
</checkbox-group>
<!-- 选择器双向绑定 -->
<picker mode="selector" :range="cities" v-model="selectedCity">
<view>当前选择:{{ cities[selectedCity] }}</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
gender: 'male',
hobbies: ['reading'],
cities: ['北京', '上海', '广州', '深圳'],
selectedCity: 0
}
}
}
</script>
指令系统详解
条件渲染指令
v-if
、v-else-if
和v-else
用于条件性地渲染内容:
<template>
<view>
<view v-if="score >= 90">优秀</view>
<view v-else-if="score >= 80">良好</view>
<view v-else-if="score >= 60">及格</view>
<view v-else>不及格</view>
<!-- 使用template包裹多个元素 -->
<template v-if="showDetails">
<view>详细信息1</view>
<view>详细信息2</view>
</template>
</view>
</template>
v-show
通过CSS的display属性控制显示/隐藏,适合频繁切换的场景:
<view v-show="isVisible">可见内容</view>
列表渲染指令
v-for
指令用于渲染数组或对象:
<template>
<view>
<!-- 数组渲染 -->
<view v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</view>
<!-- 对象渲染 -->
<view v-for="(value, key) in userInfo" :key="key">
{{ key }}: {{ value }}
</view>
<!-- 使用范围值 -->
<view v-for="n in 5" :key="n">第{{ n }}次循环</view>
</view>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '商品A' },
{ id: 2, name: '商品B' },
{ id: 3, name: '商品C' }
],
userInfo: {
name: '张三',
age: 25,
gender: '男'
}
}
}
}
</script>
事件处理指令
v-on
指令(简写为@
)用于监听DOM事件:
<template>
<view>
<!-- 内联事件处理器 -->
<button @click="count += 1">增加</button>
<!-- 方法事件处理器 -->
<button @click="handleClick">点击事件</button>
<!-- 事件修饰符 -->
<view @click.stop="doThis">阻止冒泡</view>
<form @submit.prevent="onSubmit">阻止默认行为</form>
<!-- 按键修饰符 -->
<input @keyup.enter="submitForm" placeholder="按回车提交" />
<!-- 传递参数 -->
<button @click="sayHello('小明')">打招呼</button>
</view>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
handleClick(event) {
console.log('点击事件', event)
},
doThis() {
console.log('点击事件被触发')
},
onSubmit() {
console.log('表单提交')
},
submitForm() {
console.log('表单提交')
},
sayHello(name) {
console.log(`你好,${name}`)
}
}
}
</script>
计算属性与侦听器
计算属性
计算属性基于响应式依赖进行缓存,适合复杂逻辑计算:
<template>
<view>
<view>原始价格:{{ price }}元</view>
<view>折扣后价格:{{ discountedPrice }}元</view>
<view>总价:{{ totalPrice }}元</view>
</view>
</template>
<script>
export default {
data() {
return {
price: 100,
quantity: 2,
discount: 0.8
}
},
computed: {
discountedPrice() {
return this.price * this.discount
},
totalPrice() {
return this.discountedPrice * this.quantity
}
}
}
</script>
侦听器
侦听器用于观察和响应数据变化,适合执行异步或开销较大的操作:
<template>
<view>
<input v-model="searchQuery" placeholder="搜索..." />
<view v-if="searchResults.length > 0">
搜索结果:{{ searchResults.join(', ') }}
</view>
</view>
</template>
<script>
export default {
data() {
return {
searchQuery: '',
searchResults: [],
timer: null
}
},
watch: {
searchQuery(newVal, oldVal) {
// 防抖处理
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.fetchSearchResults(newVal)
}, 500)
}
},
methods: {
fetchSearchResults(query) {
// 模拟API请求
this.searchResults = query
? ['结果1', '结果2', '结果3'].filter(item =>
item.includes(query))
: []
}
}
}
</script>
组件间数据传递
Props向下传递
父组件向子组件传递数据:
<!-- 父组件 -->
<template>
<view>
<child-component
:title="pageTitle"
:items="listItems"
@item-click="handleItemClick"
/>
</view>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
pageTitle: '商品列表',
listItems: ['商品A', '商品B', '商品C']
}
},
methods: {
handleItemClick(item) {
console.log('点击了:', item)
}
}
}
</script>
<!-- 子组件 ChildComponent.vue -->
<template>
<view>
<view class="title">{{ title }}</view>
<view
v-for="(item, index) in items"
:key="index"
@click="$emit('item-click', item)"
>
{{ item }}
</view>
</view>
</template>
<script>
export default {
props: {
title: {
type: String,
default: '默认标题'
},
items: {
type: Array,
default: () => []
}
}
}
</script>
自定义事件向上传递
子组件通过$emit
触发父组件事件:
<!-- 子组件 -->
<template>
<view>
<button @click="incrementCounter">增加计数</button>
<button @click="resetCounter">重置计数</button>
</view>
</template>
<script>
export default {
methods: {
incrementCounter() {
this.$emit('counter-change', 1)
},
resetCounter() {
this.$emit('counter-change', 0)
}
}
}
</script>
<!-- 父组件 -->
<template>
<view>
<child-component @counter-change="handleCounterChange" />
<view>当前计数:{{ counter }}</view>
</view>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
counter: 0
}
},
methods: {
handleCounterChange(value) {
this.counter = value === 0 ? 0 : this.counter + value
}
}
}
</script>
跨组件通信
对于非父子组件通信,可以使用事件总线或Vuex:
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A
import { EventBus } from './event-bus.js'
export default {
methods: {
sendMessage() {
EventBus.$emit('message', '来自组件A的消息')
}
}
}
// 组件B
import { EventBus } from './event-bus.js'
export default {
created() {
EventBus.$on('message', (msg) => {
console.log('收到消息:', msg)
})
},
beforeDestroy() {
EventBus.$off('message')
}
}
高级数据绑定技巧
动态组件与异步组件
使用<component>
实现动态组件:
<template>
<view>
<button @click="currentComponent = 'ComponentA'">显示A</button>
<button @click="currentComponent = 'ComponentB'">显示B</button>
<component :is="currentComponent" :key="currentComponent" />
</view>
</template>
<script>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
export default {
components: {
ComponentA,
ComponentB
},
data() {
return {
currentComponent: 'ComponentA'
}
}
}
</script>
异步组件按需加载:
// 路由配置或组件注册
{
path: '/async',
component: () => import('./AsyncComponent.vue')
}
插槽内容分发
基本插槽使用:
<!-- 子组件 Layout.vue -->
<template>
<view class="container">
<header class="header">
<slot name="header"></slot>
</header>
<main class="main">
<slot></slot>
</main>
<footer class="footer">
<slot name="footer"></slot>
</footer>
</view>
</template>
<!-- 父组件 -->
<template>
<layout>
<template v-slot:header>
<view>自定义头部内容</view>
</template>
<view>默认插槽内容</view>
<template #footer>
<view>自定义底部内容</view>
</template>
</layout>
</template>
作用域插槽允许子组件向插槽传递数据:
<!-- 子组件 List.vue -->
<template>
<view>
<view v-for="(item, index) in items" :key="index">
<slot :item="item" :index="index"></slot>
</view>
</view>
</template>
<script>
export default {
props: {
items: Array
}
}
</script>
<!-- 父组件 -->
<template>
<list :items="userList">
<template v-slot:default="slotProps">
<view>
{{ slotProps.index + 1 }}. {{ slotProps.item.name }}
<text v-if="slotProps.item.isNew" class="new-tag">新</text>
</view>
</template>
</list>
</template>
<script>
export default {
data() {
return {
userList: [
{ name: '张三', isNew: true },
{ name: '李四', isNew: false },
{ name: '王五', isNew: true }
]
}
}
}
</script>
自定义指令
注册和使用自定义指令:
// 全局指令
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
// 局部指令
export default {
directives: {
highlight: {
bind(el, binding) {
el.style.backgroundColor = binding.value || 'yellow'
},
update(el, binding) {
el.style.backgroundColor = binding.value || 'yellow'
}
}
}
}
<template>
<view>
<input v-focus placeholder="自动聚焦" />
<view v-highlight="'#ffcccc'">高亮显示</view>
</view>
</template>
性能优化相关
列表渲染优化
使用key
属性提高列表渲染效率:
<template>
<view>
<!-- 不推荐:使用索引作为key -->
<view v-for="(item, index) in items" :key="index">
{{ item.name }}
</view>
<!-- 推荐:使用唯一ID作为key -->
<view v-for="item in items" :key="item.id">
{{ item.name }}
</view>
</view>
</template>
对于大型列表,使用虚拟滚动:
<template>
<view>
<!-- uni-app的scroll-view组件 -->
<scroll-view
scroll-y
style="height: 300px;"
@scrolltolower="loadMore"
>
<view
v-for="item in largeList"
:key="item.id"
style="height: 50px;"
>
{{ item.name }}
</view>
</scroll-view>
</view>
</template>
计算属性缓存
合理使用计算属性替代方法调用:
<template>
<view>
<!-- 不推荐:每次渲染都会执行方法 -->
<view>{{ getFullName() }}</view>
<!-- 推荐:基于依赖缓存 -->
<view>{{ fullName }}</view>
</view>
</template>
<script>
export default {
data() {
return {
firstName: '张',
lastName: '三'
}
},
methods: {
getFullName() {
console.log('方法调用')
return this.firstName + this.lastName
}
},
computed: {
fullName() {
console.log('计算属性调用')
return this.firstName + this.lastName
}
}
}
</script>
减少不必要的响应式数据
对于不需要响应式的数据,可以使用Object.freeze
或直接定义在created中:
export default {
data() {
return {
// 响应式数据
userInfo: {
name: '张三',
age: 25
},
// 大型静态数据,冻结避免不必要的响应式转换
largeStaticData: Object.freeze([
/* 大量静态数据 */
])
}
},
created() {
// 非响应式数据
this.staticConfig = {
apiUrl: 'https://example.com/api',
maxItems: 100
}
}
}
常见问题与解决方案
数据更新视图不渲染
使用this.$set
或Vue.set
解决数组或对象新增属性不响应问题:
export default {
data() {
return {
user: {
name: '张三'
},
items: ['A', 'B']
}
},
methods: {
addProperty() {
// 错误方式
// this.user.age = 25 // 不会触发视图更新
// 正确方式
this.$set(this.user, 'age', 25)
},
addItem() {
// 错误方式
// this.items[2] = 'C' // 不会触发视图更新
// 正确方式1
this.$set(this.items, 2, 'C')
// 正确方式2
this.items = [...this.items, 'C']
}
}
}
事件处理中的this指向
使用箭头函数或在methods中定义方法保持this指向:
export default {
data() {
return {
message: 'Hello'
}
},
methods: {
// 推荐方式
handleClick() {
console.log(this.message)
},
// 也可以使用箭头函数
handleHover: () => {
// 注意箭头函数中的this不是组件实例
}
},
created() {
// 错误示例
// setTimeout(function() {
// console.log(this.message
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:条件渲染与列表渲染