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
Method 1: Using HBuilderX (Recommended)
Download HBuilderX
- Visit HBuilderX official website
- Download the latest version for your operating system
- Install and launch HBuilderX
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
Install Vue CLI
bashnpm install -g @vue/cli
Install uni-app CLI
bashnpm install -g @dcloudio/uni-cli
Create Project
bashvue create -p dcloudio/uni-preset-vue my-project
Navigate to Project
bashcd my-project
Start Development Server
bashnpm 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:
{
"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:
{
"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
<!-- 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
:
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "Home"
}
},
{
"path": "pages/profile/profile",
"style": {
"navigationBarTitleText": "Profile"
}
}
]
}
Navigation Between Pages
Basic Navigation
// 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
:
{
"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:
uni.switchTab({
url: '/pages/profile/profile'
})
Working with APIs
Making HTTP Requests
// 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
// 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
// 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
# 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
# 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:
<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
<!-- 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
<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:
- State Management - Learn about Vuex for complex state management
- Custom Components - Create more sophisticated reusable components
- Plugins and Extensions - Integrate third-party plugins
- Performance Optimization - Advanced techniques for better performance
- Platform-Specific Features - Leverage unique platform capabilities
- Testing - Unit and integration testing strategies
- 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! 🚀