阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 模板语法与数据绑定

模板语法与数据绑定

作者:陈川 阅读数:41746人阅读 分类: uni-app

模板语法基础

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-ifv-else-ifv-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.$setVue.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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌