Skip to content

Data Binding

Data binding is one of the core features of uni-app, allowing you to establish connections between data and views. This guide will introduce various data binding methods and best practices.

Basic Data Binding

Text Interpolation

Use double curly braces for text interpolation:

vue
<template>
  <view>
    <text>Hello {{ name }}!</text>
    <text>Current time: {{ currentTime }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      name: 'World',
      currentTime: new Date().toLocaleString()
    }
  }
}
</script>

HTML Content Binding

Use v-html directive to bind HTML content:

vue
<template>
  <view>
    <view v-html="htmlContent"></view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      htmlContent: '<strong>Bold text</strong>'
    }
  }
}
</script>

Note: Be cautious when using v-html to prevent XSS attacks.

Attribute Binding

Basic Attribute Binding

Use v-bind directive or : shorthand to bind attributes:

vue
<template>
  <view>
    <image :src="imageSrc" :alt="imageAlt" />
    <button :disabled="isDisabled">{{ buttonText }}</button>
    <view :class="containerClass">Container</view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      imageSrc: '/static/logo.png',
      imageAlt: 'Logo',
      isDisabled: false,
      buttonText: 'Click me',
      containerClass: 'main-container'
    }
  }
}
</script>

Dynamic Attribute Names

vue
<template>
  <view>
    <button :[attributeName]="attributeValue">Dynamic attribute</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      attributeName: 'disabled',
      attributeValue: true
    }
  }
}
</script>

Boolean Attributes

For boolean attributes, if the bound value is truthy, the attribute will be included:

vue
<template>
  <view>
    <button :disabled="isButtonDisabled">Button</button>
    <input :readonly="isReadonly" />
  </view>
</template>

<script>
export default {
  data() {
    return {
      isButtonDisabled: true,  // disabled attribute will be added
      isReadonly: false        // readonly attribute will not be added
    }
  }
}
</script>

Class and Style Binding

Class Binding

Object Syntax

vue
<template>
  <view>
    <view :class="{ active: isActive, 'text-danger': hasError }">
      Dynamic classes
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isActive: true,
      hasError: false
    }
  }
}
</script>

Array Syntax

vue
<template>
  <view>
    <view :class="[activeClass, errorClass]">Array classes</view>
    <view :class="[isActive ? activeClass : '', errorClass]">
      Conditional array classes
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      activeClass: 'active',
      errorClass: 'text-danger',
      isActive: true
    }
  }
}
</script>

Mixed Syntax

vue
<template>
  <view>
    <view :class="[{ active: isActive }, errorClass]">
      Mixed syntax
    </view>
  </view>
</template>

Style Binding

Object Syntax

vue
<template>
  <view>
    <view :style="{ color: activeColor, fontSize: fontSize + 'px' }">
      Dynamic styles
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      activeColor: 'red',
      fontSize: 30
    }
  }
}
</script>

Array Syntax

vue
<template>
  <view>
    <view :style="[baseStyles, overridingStyles]">
      Array styles
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      baseStyles: {
        color: 'red',
        fontSize: '13px'
      },
      overridingStyles: {
        color: 'blue'
      }
    }
  }
}
</script>

Form Input Binding

Two-way Data Binding with v-model

Text Input

vue
<template>
  <view>
    <input v-model="message" placeholder="Enter message" />
    <text>Message: {{ message }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  }
}
</script>

Textarea

vue
<template>
  <view>
    <textarea v-model="text" placeholder="Enter text"></textarea>
    <text>{{ text }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      text: ''
    }
  }
}
</script>

Checkbox

vue
<template>
  <view>
    <!-- Single checkbox -->
    <label>
      <checkbox v-model="checked" />
      {{ checked }}
    </label>
    
    <!-- Multiple checkboxes -->
    <view>
      <label v-for="item in items" :key="item.id">
        <checkbox :value="item.value" v-model="checkedItems" />
        {{ item.label }}
      </label>
    </view>
    <text>Checked: {{ checkedItems }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      checked: false,
      checkedItems: [],
      items: [
        { id: 1, label: 'Option A', value: 'a' },
        { id: 2, label: 'Option B', value: 'b' },
        { id: 3, label: 'Option C', value: 'c' }
      ]
    }
  }
}
</script>

Radio

vue
<template>
  <view>
    <radio-group v-model="picked">
      <label v-for="item in options" :key="item.value">
        <radio :value="item.value" />
        {{ item.label }}
      </label>
    </radio-group>
    <text>Picked: {{ picked }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      picked: '',
      options: [
        { label: 'Option A', value: 'a' },
        { label: 'Option B', value: 'b' },
        { label: 'Option C', value: 'c' }
      ]
    }
  }
}
</script>

Select (Picker)

vue
<template>
  <view>
    <picker 
      :range="options" 
      range-key="label"
      :value="selectedIndex"
      @change="handleChange"
    >
      <view class="picker">
        {{ selectedOption ? selectedOption.label : 'Please select' }}
      </view>
    </picker>
  </view>
</template>

<script>
export default {
  data() {
    return {
      selectedIndex: 0,
      options: [
        { label: 'Option A', value: 'a' },
        { label: 'Option B', value: 'b' },
        { label: 'Option C', value: 'c' }
      ]
    }
  },
  computed: {
    selectedOption() {
      return this.options[this.selectedIndex]
    }
  },
  methods: {
    handleChange(e) {
      this.selectedIndex = e.detail.value
    }
  }
}
</script>

v-model Modifiers

.lazy

By default, v-model syncs the input with the data after each input event. You can add the lazy modifier to sync after change events instead:

vue
<template>
  <input v-model.lazy="msg" />
</template>

.number

If you want user input to be automatically typecast as a Number:

vue
<template>
  <input v-model.number="age" type="number" />
</template>

.trim

If you want whitespace from user input to be trimmed automatically:

vue
<template>
  <input v-model.trim="msg" />
</template>

Computed Properties

Computed properties are cached based on their reactive dependencies and will only re-evaluate when some of their reactive dependencies have changed.

Basic Example

vue
<template>
  <view>
    <text>Original message: "{{ message }}"</text>
    <text>Computed reversed message: "{{ reversedMessage }}"</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  }
}
</script>

Computed vs Methods

vue
<template>
  <view>
    <!-- Computed property (cached) -->
    <text>{{ reversedMessage }}</text>
    <text>{{ reversedMessage }}</text>
    
    <!-- Method (called every time) -->
    <text>{{ reverseMessage() }}</text>
    <text>{{ reverseMessage() }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  computed: {
    reversedMessage() {
      console.log('Computed called') // Only called once
      return this.message.split('').reverse().join('')
    }
  },
  methods: {
    reverseMessage() {
      console.log('Method called') // Called twice
      return this.message.split('').reverse().join('')
    }
  }
}
</script>

Computed Setter

Computed properties are by default getter-only, but you can also provide a setter:

vue
<template>
  <view>
    <text>{{ fullName }}</text>
    <button @click="changeName">Change Name</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  },
  methods: {
    changeName() {
      this.fullName = 'Jane Smith'
    }
  }
}
</script>

Watchers

While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary.

Basic Example

vue
<template>
  <view>
    <input v-model="question" placeholder="Ask a yes/no question" />
    <text>{{ answer }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      answer: 'I cannot give you an answer until you ask a question!'
    }
  },
  watch: {
    question(newQuestion, oldQuestion) {
      if (newQuestion.indexOf('?') > -1) {
        this.getAnswer()
      }
    }
  },
  methods: {
    getAnswer() {
      this.answer = 'Thinking...'
      setTimeout(() => {
        this.answer = Math.random() > 0.5 ? 'Yes' : 'No'
      }, 1000)
    }
  }
}
</script>

Deep Watching

To detect nested value changes inside Objects, you need to pass deep: true:

vue
<script>
export default {
  data() {
    return {
      user: {
        name: 'John',
        profile: {
          age: 25,
          city: 'New York'
        }
      }
    }
  },
  watch: {
    user: {
      handler(newVal, oldVal) {
        console.log('User changed:', newVal)
      },
      deep: true
    }
  }
}
</script>

Immediate Execution

If you want the callback to be fired immediately with the current value:

vue
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  watch: {
    message: {
      handler(newVal, oldVal) {
        console.log('Message changed:', newVal)
      },
      immediate: true
    }
  }
}
</script>

Advanced Data Binding Patterns

Dynamic Component Props

vue
<template>
  <view>
    <component 
      :is="currentComponent" 
      v-bind="componentProps"
      @custom-event="handleEvent"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'my-component',
      componentProps: {
        title: 'Dynamic Title',
        data: [1, 2, 3],
        config: { theme: 'dark' }
      }
    }
  },
  methods: {
    handleEvent(payload) {
      console.log('Event received:', payload)
    }
  }
}
</script>

Slot Props

vue
<!-- Parent Component -->
<template>
  <my-list :items="items">
    <template v-slot:item="{ item, index }">
      <view class="custom-item">
        {{ index + 1 }}. {{ item.name }} - {{ item.price }}
      </view>
    </template>
  </my-list>
</template>

<!-- Child Component (my-list) -->
<template>
  <view>
    <view v-for="(item, index) in items" :key="item.id">
      <slot name="item" :item="item" :index="index">
        <!-- Default content -->
        <text>{{ item.name }}</text>
      </slot>
    </view>
  </view>
</template>

Provide/Inject

vue
<!-- Ancestor Component -->
<script>
export default {
  provide() {
    return {
      theme: this.theme,
      updateTheme: this.updateTheme
    }
  },
  data() {
    return {
      theme: 'light'
    }
  },
  methods: {
    updateTheme(newTheme) {
      this.theme = newTheme
    }
  }
}
</script>

<!-- Descendant Component -->
<script>
export default {
  inject: ['theme', 'updateTheme'],
  template: `
    <view :class="theme">
      <button @click="updateTheme('dark')">Switch to Dark</button>
    </view>
  `
}
</script>

Performance Optimization

Use Object.freeze for Static Data

For large arrays or objects that won't change, use Object.freeze() to prevent Vue from making them reactive:

javascript
export default {
  data() {
    return {
      // This large list won't be reactive
      staticList: Object.freeze([
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        // ... many more items
      ])
    }
  }
}

Avoid Inline Object/Array Creation in Templates

vue
<template>
  <view>
    <!-- Avoid: Creates new object on every render -->
    <my-component :style="{ color: 'red', fontSize: '14px' }" />
    
    <!-- Better: Use computed property or data -->
    <my-component :style="componentStyle" />
  </view>
</template>

<script>
export default {
  computed: {
    componentStyle() {
      return {
        color: 'red',
        fontSize: '14px'
      }
    }
  }
}
</script>

Use v-once for Static Content

For content that will never change, use v-once directive:

vue
<template>
  <view>
    <!-- This will only render once -->
    <expensive-component v-once :data="staticData" />
  </view>
</template>

Common Patterns and Best Practices

Form Validation

vue
<template>
  <view>
    <form @submit="handleSubmit">
      <view class="form-group">
        <input 
          v-model="form.email"
          :class="{ error: errors.email }"
          placeholder="Email"
          @blur="validateEmail"
        />
        <text v-if="errors.email" class="error-message">
          {{ errors.email }}
        </text>
      </view>
      
      <view class="form-group">
        <input 
          v-model="form.password"
          :class="{ error: errors.password }"
          type="password"
          placeholder="Password"
          @blur="validatePassword"
        />
        <text v-if="errors.password" class="error-message">
          {{ errors.password }}
        </text>
      </view>
      
      <button :disabled="!isFormValid" type="submit">
        Submit
      </button>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      form: {
        email: '',
        password: ''
      },
      errors: {}
    }
  },
  computed: {
    isFormValid() {
      return Object.keys(this.errors).length === 0 && 
             this.form.email && 
             this.form.password
    }
  },
  methods: {
    validateEmail() {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      if (!this.form.email) {
        this.$set(this.errors, 'email', 'Email is required')
      } else if (!emailRegex.test(this.form.email)) {
        this.$set(this.errors, 'email', 'Invalid email format')
      } else {
        this.$delete(this.errors, 'email')
      }
    },
    validatePassword() {
      if (!this.form.password) {
        this.$set(this.errors, 'password', 'Password is required')
      } else if (this.form.password.length < 6) {
        this.$set(this.errors, 'password', 'Password must be at least 6 characters')
      } else {
        this.$delete(this.errors, 'password')
      }
    },
    handleSubmit() {
      this.validateEmail()
      this.validatePassword()
      
      if (this.isFormValid) {
        console.log('Form submitted:', this.form)
      }
    }
  }
}
</script>

Search and Filter

vue
<template>
  <view>
    <input 
      v-model="searchQuery" 
      placeholder="Search items..."
      class="search-input"
    />
    
    <view class="filters">
      <button 
        v-for="category in categories"
        :key="category"
        :class="{ active: selectedCategory === category }"
        @click="selectedCategory = category"
      >
        {{ category }}
      </button>
    </view>
    
    <view class="items">
      <view 
        v-for="item in filteredItems" 
        :key="item.id"
        class="item"
      >
        <text>{{ item.name }}</text>
        <text>{{ item.category }}</text>
      </view>
    </view>
    
    <text v-if="filteredItems.length === 0" class="no-results">
      No items found
    </text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      searchQuery: '',
      selectedCategory: 'All',
      items: [
        { id: 1, name: 'Apple', category: 'Fruit' },
        { id: 2, name: 'Banana', category: 'Fruit' },
        { id: 3, name: 'Carrot', category: 'Vegetable' },
        { id: 4, name: 'Broccoli', category: 'Vegetable' }
      ]
    }
  },
  computed: {
    categories() {
      const cats = [...new Set(this.items.map(item => item.category))]
      return ['All', ...cats]
    },
    filteredItems() {
      let filtered = this.items
      
      // Filter by category
      if (this.selectedCategory !== 'All') {
        filtered = filtered.filter(item => item.category === this.selectedCategory)
      }
      
      // Filter by search query
      if (this.searchQuery) {
        filtered = filtered.filter(item => 
          item.name.toLowerCase().includes(this.searchQuery.toLowerCase())
        )
      }
      
      return filtered
    }
  }
}
</script>

Loading States

vue
<template>
  <view>
    <button @click="loadData" :disabled="loading">
      {{ loading ? 'Loading...' : 'Load Data' }}
    </button>
    
    <view v-if="loading" class="loading">
      <text>Loading...</text>
    </view>
    
    <view v-else-if="error" class="error">
      <text>Error: {{ error }}</text>
      <button @click="loadData">Retry</button>
    </view>
    
    <view v-else-if="data" class="data">
      <view v-for="item in data" :key="item.id">
        {{ item.name }}
      </view>
    </view>
    
    <view v-else class="empty">
      <text>No data loaded</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      error: null,
      data: null
    }
  },
  methods: {
    async loadData() {
      this.loading = true
      this.error = null
      
      try {
        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 2000))
        
        // Simulate random success/failure
        if (Math.random() > 0.3) {
          this.data = [
            { id: 1, name: 'Item 1' },
            { id: 2, name: 'Item 2' },
            { id: 3, name: 'Item 3' }
          ]
        } else {
          throw new Error('Failed to load data')
        }
      } catch (err) {
        this.error = err.message
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

Summary

Data binding is a fundamental concept in uni-app that enables reactive user interfaces. Key points to remember:

  • Use interpolation for text content
  • Use v-bind or : for attribute binding
  • Use v-model for two-way data binding with form inputs
  • Leverage computed properties for derived data
  • Use watchers for side effects and async operations
  • Follow performance best practices to ensure smooth user experience

By mastering these data binding techniques, you can build dynamic and interactive applications that respond to user input and data changes efficiently.

一次开发,多端部署 - 让跨平台开发更简单