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.

Develop once, deploy everywhere - Making cross-platform development easier