Skip to content

Getting Started with uni-app

Overview

uni-app is a cross-platform development framework that allows you to write once and run everywhere. This guide will help you get started with uni-app development, from installation to creating your first application.

What is uni-app?

uni-app is a Vue.js-based framework for developing cross-platform applications. With a single codebase, you can build applications that run on:

  • iOS and Android Apps (native performance)
  • WeChat Mini Programs (and other mini-program platforms)
  • H5 Web Applications (responsive web apps)
  • Desktop Applications (via Electron)

Prerequisites

Before getting started with uni-app, make sure you have:

  • Node.js (version 12 or higher)
  • npm or yarn package manager
  • HBuilderX (recommended IDE) or VS Code
  • Basic knowledge of Vue.js and JavaScript

Installation

  1. Download HBuilderX

  2. Create New Project

    File → New → Project → uni-app
    • Choose a template (default template recommended for beginners)
    • Enter project name and location
    • Click "Create"

Method 2: Using Vue CLI

  1. Install Vue CLI

    bash
    npm install -g @vue/cli
  2. Install uni-app CLI

    bash
    npm install -g @dcloudio/uni-cli
  3. Create Project

    bash
    vue create -p dcloudio/uni-preset-vue my-project
  4. Navigate to Project

    bash
    cd my-project
  5. Start Development Server

    bash
    npm run dev:h5

Project Structure

A typical uni-app project structure looks like this:

my-uni-app/
├── pages/                  # Application pages
│   ├── index/
│   │   └── index.vue      # Home page
│   └── about/
│       └── about.vue      # About page
├── components/            # Reusable components
│   └── MyComponent.vue
├── static/               # Static assets
│   ├── images/
│   └── fonts/
├── store/                # Vuex store (optional)
│   └── index.js
├── utils/                # Utility functions
│   └── request.js
├── App.vue               # Root component
├── main.js               # Entry point
├── manifest.json         # App configuration
├── pages.json            # Page routing configuration
└── uni.scss              # Global styles

Key Files Explained

pages.json

Defines page routes and global configuration:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "Home"
      }
    },
    {
      "path": "pages/about/about",
      "style": {
        "navigationBarTitleText": "About"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "My App",
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8"
  }
}

manifest.json

App metadata and platform-specific configurations:

json
{
  "name": "my-uni-app",
  "appid": "__UNI__XXXXXXX",
  "description": "My first uni-app application",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "usingComponents": true,
    "nvueStyleCompiler": "uni-app",
    "compilerVersion": 3,
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    }
  },
  "quickapp": {},
  "mp-weixin": {
    "appid": "",
    "setting": {
      "urlCheck": false
    },
    "usingComponents": true
  },
  "h5": {
    "title": "My App",
    "template": "index.html"
  }
}

Creating Your First Page

1. Create a New Page

Create a new directory and Vue file:

pages/
└── profile/
    └── profile.vue

2. Define the Page Component

vue
<!-- pages/profile/profile.vue -->
<template>
  <view class="container">
    <view class="header">
      <image :src="userInfo.avatar" class="avatar"></image>
      <text class="username">{{ userInfo.name }}</text>
    </view>
    
    <view class="info-section">
      <view class="info-item">
        <text class="label">Email:</text>
        <text class="value">{{ userInfo.email }}</text>
      </view>
      <view class="info-item">
        <text class="label">Phone:</text>
        <text class="value">{{ userInfo.phone }}</text>
      </view>
    </view>
    
    <button @click="editProfile" class="edit-btn">Edit Profile</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      userInfo: {
        name: 'John Doe',
        email: 'john@example.com',
        phone: '+1234567890',
        avatar: '/static/default-avatar.png'
      }
    }
  },
  
  onLoad() {
    // Page lifecycle - called when page loads
    console.log('Profile page loaded')
    this.loadUserInfo()
  },
  
  methods: {
    loadUserInfo() {
      // Simulate API call
      setTimeout(() => {
        this.userInfo = {
          name: 'Jane Smith',
          email: 'jane@example.com',
          phone: '+0987654321',
          avatar: '/static/user-avatar.png'
        }
      }, 1000)
    },
    
    editProfile() {
      uni.navigateTo({
        url: '/pages/edit-profile/edit-profile'
      })
    }
  }
}
</script>

<style scoped>
.container {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 30px;
  padding: 20px;
  background-color: white;
  border-radius: 10px;
}

.avatar {
  width: 80px;
  height: 80px;
  border-radius: 40px;
  margin-bottom: 10px;
}

.username {
  font-size: 24px;
  font-weight: bold;
  color: #333;
}

.info-section {
  background-color: white;
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 20px;
}

.info-item {
  display: flex;
  justify-content: space-between;
  padding: 15px 0;
  border-bottom: 1px solid #eee;
}

.info-item:last-child {
  border-bottom: none;
}

.label {
  font-weight: bold;
  color: #666;
}

.value {
  color: #333;
}

.edit-btn {
  width: 100%;
  background-color: #007aff;
  color: white;
  border: none;
  border-radius: 5px;
  padding: 15px;
  font-size: 16px;
}
</style>

3. Register the Page

Add the new page to pages.json:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "Home"
      }
    },
    {
      "path": "pages/profile/profile",
      "style": {
        "navigationBarTitleText": "Profile"
      }
    }
  ]
}

Basic Navigation

javascript
// Navigate to a new page
uni.navigateTo({
  url: '/pages/profile/profile'
})

// Navigate with parameters
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=John'
})

// Replace current page
uni.redirectTo({
  url: '/pages/result/result'
})

// Go back
uni.navigateBack({
  delta: 1 // Number of pages to go back
})

Tab Navigation

Configure tab bar in pages.json:

json
{
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/tab-home.png",
        "selectedIconPath": "static/tab-home-active.png",
        "text": "Home"
      },
      {
        "pagePath": "pages/profile/profile",
        "iconPath": "static/tab-profile.png",
        "selectedIconPath": "static/tab-profile-active.png",
        "text": "Profile"
      }
    ]
  }
}

Navigate to tab pages:

javascript
uni.switchTab({
  url: '/pages/profile/profile'
})

Working with APIs

Making HTTP Requests

javascript
// GET request
uni.request({
  url: 'https://api.example.com/users',
  method: 'GET',
  success: (res) => {
    console.log('Success:', res.data)
  },
  fail: (err) => {
    console.error('Error:', err)
  }
})

// POST request with data
uni.request({
  url: 'https://api.example.com/users',
  method: 'POST',
  data: {
    name: 'John Doe',
    email: 'john@example.com'
  },
  header: {
    'Content-Type': 'application/json'
  },
  success: (res) => {
    console.log('User created:', res.data)
  }
})

// Using async/await
async function fetchUsers() {
  try {
    const response = await uni.request({
      url: 'https://api.example.com/users',
      method: 'GET'
    })
    return response.data
  } catch (error) {
    console.error('Failed to fetch users:', error)
    throw error
  }
}

Creating an API Service

javascript
// utils/api.js
const BASE_URL = 'https://api.example.com'

export const apiService = {
  // Generic request method
  async request(options) {
    const defaultOptions = {
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      header: {
        'Content-Type': 'application/json',
        ...options.header
      }
    }
    
    // Add authentication token if available
    const token = uni.getStorageSync('token')
    if (token) {
      defaultOptions.header.Authorization = `Bearer ${token}`
    }
    
    try {
      const response = await uni.request({
        ...defaultOptions,
        ...options
      })
      
      if (response.statusCode >= 200 && response.statusCode < 300) {
        return response.data
      } else {
        throw new Error(`HTTP ${response.statusCode}: ${response.data.message}`)
      }
    } catch (error) {
      console.error('API Request failed:', error)
      throw error
    }
  },
  
  // Specific API methods
  async getUsers() {
    return this.request({
      url: '/users',
      method: 'GET'
    })
  },
  
  async createUser(userData) {
    return this.request({
      url: '/users',
      method: 'POST',
      data: userData
    })
  },
  
  async updateUser(id, userData) {
    return this.request({
      url: `/users/${id}`,
      method: 'PUT',
      data: userData
    })
  },
  
  async deleteUser(id) {
    return this.request({
      url: `/users/${id}`,
      method: 'DELETE'
    })
  }
}

Local Storage

Storing and Retrieving Data

javascript
// Store data
uni.setStorageSync('userInfo', {
  name: 'John Doe',
  email: 'john@example.com'
})

// Retrieve data
const userInfo = uni.getStorageSync('userInfo')
console.log(userInfo)

// Async methods
uni.setStorage({
  key: 'settings',
  data: {
    theme: 'dark',
    language: 'en'
  },
  success: () => {
    console.log('Settings saved')
  }
})

uni.getStorage({
  key: 'settings',
  success: (res) => {
    console.log('Settings:', res.data)
  }
})

// Remove data
uni.removeStorageSync('userInfo')

// Clear all storage
uni.clearStorageSync()

Building and Running

Development

bash
# Run on H5 (web browser)
npm run dev:h5

# Run on WeChat Mini Program
npm run dev:mp-weixin

# Run on App (requires HBuilderX)
npm run dev:app-plus

Production Build

bash
# Build for H5
npm run build:h5

# Build for WeChat Mini Program
npm run build:mp-weixin

# Build for App
npm run build:app-plus

Platform-Specific Code

Use conditional compilation to write platform-specific code:

vue
<template>
  <view>
    <!-- Common content -->
    <text>This appears on all platforms</text>
    
    <!-- Platform-specific content -->
    <!-- #ifdef MP-WEIXIN -->
    <text>This only appears in WeChat Mini Program</text>
    <!-- #endif -->
    
    <!-- #ifdef APP-PLUS -->
    <text>This only appears in mobile apps</text>
    <!-- #endif -->
    
    <!-- #ifdef H5 -->
    <text>This only appears in web browsers</text>
    <!-- #endif -->
  </view>
</template>

<script>
export default {
  methods: {
    platformSpecificMethod() {
      // #ifdef MP-WEIXIN
      wx.showToast({
        title: 'WeChat Mini Program'
      })
      // #endif
      
      // #ifdef APP-PLUS
      plus.nativeUI.toast('Mobile App')
      // #endif
      
      // #ifdef H5
      alert('Web Browser')
      // #endif
    }
  }
}
</script>

Common Components

Creating Reusable Components

vue
<!-- components/UserCard/UserCard.vue -->
<template>
  <view class="user-card" @click="handleClick">
    <image :src="user.avatar" class="avatar"></image>
    <view class="info">
      <text class="name">{{ user.name }}</text>
      <text class="email">{{ user.email }}</text>
    </view>
    <view class="actions">
      <button @click.stop="editUser" size="mini">Edit</button>
      <button @click.stop="deleteUser" size="mini" type="warn">Delete</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'UserCard',
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  
  methods: {
    handleClick() {
      this.$emit('click', this.user)
    },
    
    editUser() {
      this.$emit('edit', this.user)
    },
    
    deleteUser() {
      this.$emit('delete', this.user)
    }
  }
}
</script>

<style scoped>
.user-card {
  display: flex;
  align-items: center;
  padding: 15px;
  background-color: white;
  border-radius: 8px;
  margin-bottom: 10px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 25px;
  margin-right: 15px;
}

.info {
  flex: 1;
}

.name {
  display: block;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 5px;
}

.email {
  font-size: 14px;
  color: #666;
}

.actions {
  display: flex;
  gap: 10px;
}
</style>

Using the Component

vue
<template>
  <view class="container">
    <UserCard 
      v-for="user in users" 
      :key="user.id"
      :user="user"
      @click="viewUser"
      @edit="editUser"
      @delete="deleteUser"
    />
  </view>
</template>

<script>
import UserCard from '@/components/UserCard/UserCard.vue'

export default {
  components: {
    UserCard
  },
  
  data() {
    return {
      users: [
        {
          id: 1,
          name: 'John Doe',
          email: 'john@example.com',
          avatar: '/static/avatar1.png'
        },
        {
          id: 2,
          name: 'Jane Smith',
          email: 'jane@example.com',
          avatar: '/static/avatar2.png'
        }
      ]
    }
  },
  
  methods: {
    viewUser(user) {
      uni.navigateTo({
        url: `/pages/user-detail/user-detail?id=${user.id}`
      })
    },
    
    editUser(user) {
      uni.navigateTo({
        url: `/pages/edit-user/edit-user?id=${user.id}`
      })
    },
    
    deleteUser(user) {
      uni.showModal({
        title: 'Confirm Delete',
        content: `Are you sure you want to delete ${user.name}?`,
        success: (res) => {
          if (res.confirm) {
            this.users = this.users.filter(u => u.id !== user.id)
          }
        }
      })
    }
  }
}
</script>

Best Practices

1. Code Organization

  • Keep components small and focused
  • Use meaningful file and folder names
  • Separate business logic from UI components
  • Create reusable utility functions

2. Performance

  • Use v-show for frequently toggled elements
  • Use v-if for conditionally rendered elements
  • Implement lazy loading for large lists
  • Optimize images and assets

3. Cross-Platform Compatibility

  • Test on all target platforms regularly
  • Use uni-app APIs instead of platform-specific APIs when possible
  • Handle platform differences gracefully
  • Follow uni-app component guidelines

4. Error Handling

  • Always handle API errors
  • Provide meaningful error messages to users
  • Implement retry mechanisms for network requests
  • Use try-catch blocks for async operations

Next Steps

Now that you have the basics down, you can explore more advanced topics:

  1. State Management - Learn about Vuex for complex state management
  2. Custom Components - Create more sophisticated reusable components
  3. Plugins and Extensions - Integrate third-party plugins
  4. Performance Optimization - Advanced techniques for better performance
  5. Platform-Specific Features - Leverage unique platform capabilities
  6. Testing - Unit and integration testing strategies
  7. Deployment - Publishing to app stores and web hosting

Conclusion

uni-app provides a powerful foundation for cross-platform development. With Vue.js knowledge and the concepts covered in this guide, you're ready to start building amazing applications that work everywhere. Remember to refer to the official documentation for detailed API references and keep practicing with small projects to build your expertise.

Happy coding with uni-app! 🚀

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