uni-app's Missing Root Component Breaks Global UI; uView Pro Patches It with Multi-Instance Toasts and Modals
Foreword
In uni-app development, have you ever been tormented by the various limitations of uni.showToast and uni.showModal? This article will take you deep into how uView Pro completely solves these pain points through useToast and useModal, and introduces two feasible solutions for implementing global prompts.
1. Pain Points of uni-app Official APIs
1.1 uni-app Has No Real Root Component
This is an issue many developers overlook but is very important:
- App.vue is not a root component: It is only the application entry file and will not be rendered as a Vue root component.
- Pages are independent: Each page under the
pagesdirectory is an independent Vue instance and cannot share state with each other. - Global components are difficult to implement: Displaying the same Toast or Modal across all pages is technically very difficult.
This leads to a fundamental problem: uni-app officially cannot provide a true global Toast and Modal solution.
1.2 The Dilemma of uni.showToast
uni.showToast seems simple:
uni.showToast({
title: 'Loading...',
icon: 'loading'
})
But in actual development, these issues will drive you crazy:
| Pain Point | Specific Manifestation |
|---|---|
| Global Singleton | Only one Toast can be displayed; multiple requests overwrite each other. |
| Fixed Style | Position, text, and background color cannot be customized at all. |
| Single Type | Only four types: loading, success, none, error. |
| Unmanageable | Cannot control display order, cannot manually close (except for loading). |
Typical Scenario: When a user uploads multiple files, the progress prompts for each file overwrite each other, and the user only sees the last prompt.
1.3 Pain Points of uni.showModal
uni.showModal({
title: 'Confirm Delete?',
content: 'Data cannot be recovered after deletion',
success(res) {
if (res.confirm) {
// Confirmation logic
// If another popup is needed here, it becomes callback nesting
}
}
})
Existing Problems:
- Callback Nesting: When combining multiple popups, the code becomes "callback hell."
- Style Limitations: Button colors, text, and size cannot be customized.
- Single Functionality: Does not support complex interactions like input boxes or selection lists.
- Platform Differences: Inconsistent performance across different platforms, potentially requiring extra adaptation.
2. uView Pro's Prompt and Popup Solution
uView Pro is a uni-app ecosystem framework fully supporting Vue 3.0 and TypeScript. It provides 80+ selected UI components, convenient tools, common templates, supports multiple themes, dark mode, internationalization, and fully supports H5/App/HarmonyOS/Mini Program multi-platform development.
2.1 useToast: Multi-Instance, Flexible and Controllable
The useToast provided by uView Pro is a true "game changer":
import { useToast } from 'uview-pro'
const toast1 = useToast({ page: 'upload' })
const toast2 = useToast({ page: 'network' })
const toast3 = useToast({ page: 'toast' })
// ✅ Multiple Toasts displayed simultaneously without interfering with each other
toast1.show({ title: 'Uploading...', type: 'loading', position: 'top' })
toast2.show({ title: 'Network fluctuation', type: 'warning', position: 'center' })
toast3.show({ title: 'Upload successful', type: 'success', position: 'bottom' })
Core Advantages:
| Feature | Description |
|---|---|
| Multi-Instance Support | Multiple independent instances can be created on the same page via pageId. |
| Global Mode | Application-level unique instance, shared across the entire app. |
| Local Mode | Page-level instance, not interfering with each other. |
| Flexible Position | Three positions: top, center, bottom. |
| Rich Types | loading, success, error, warning, info. |
| Fully Controllable | Can manually control display and closing. |
| Customizable Style | Background color, text color, border radius, and opacity can all be adjusted. |
Three Invocation Methods:
// Global mode - the entire app shares one Toast instance
const globalToast = useToast()
// Local mode - independent instance for the current page
const pageToast = useToast({ page: true })
// Custom pageId - multiple independent instances within a page
const topToast = useToast({ page: 'topArea' })
const bottomToast = useToast({ page: 'bottomArea' })
Principle of pageId Implementing Multiple Instances:
When you place multiple <u-toast /> components on a page and distinguish them by pageId, each Toast instance is completely independent:
<template>
<view>
<!-- Top prompt area -->
<u-toast page="topArea" />
<!-- Bottom prompt area -->
<u-toast page="bottomArea" />
</view>
</template>
// Toasts in different areas do not interfere with each other
const topToast = useToast({ page: 'topArea' })
const bottomToast = useToast({ page: 'bottomArea' })
// Show warning at the top
topToast.show({ title: 'Note: Network fluctuation', type: 'warning', position: 'top' })
// Show success at the bottom
bottomToast.show({ title: 'Save successful', type: 'success', position: 'bottom' })
pageId Matching Mechanism:
| useToast Invocation Method | Toast Component | Responds? | Description |
|---|---|---|---|
useToast({ page: 'topArea' }) |
<u-toast page="topArea" /> |
✅ | Exact match |
useToast({ page: 'topArea' }) |
<u-toast page /> |
❌ | No pageId specified, no match |
useToast({ page: true }) |
<u-toast page /> |
✅ | Uses the current page route as pageId |
useToast() |
<u-toast global /> |
✅ | Global mode |
Typical Use Case: Triggering a parent page's Toast from a child component
// In child component
const toast = useToast({ page: 'parentToast' })
toast.success('Prompt triggered by child component')
<!-- In parent page -->
<template>
<view>
<ChildComponent />
<!-- The child component will trigger this Toast -->
<u-toast page="parentToast" />
</view>
</template>
2.2 useModal: Functional Invocation, Flexible and Controllable
uView Pro's useModal provides a concise modal display function:
import { useModal } from 'uview-pro'
const modal = useModal()
// ✅ Single button popup
modal.show({
title: 'Operation Successful',
content: 'Your order has been submitted successfully'
})
// ✅ Dual button confirmation popup
modal.confirm({
title: 'Confirm Delete?',
content: 'Data cannot be recovered after deletion',
onConfirm: () => {
// Confirmation callback
console.log('User clicked confirm')
},
onCancel: () => {
// Cancel callback
console.log('User clicked cancel')
}
})
useModal Features:
| Feature | Description |
|---|---|
| Multi-Instance Support | Multiple independent Modal instances can be created on the same page via pageId. |
| Global Mode | Application-level unique instance, shared across the entire app. |
| Local Mode | Page-level instance, not interfering with each other. |
| Dual Display Methods | show (single button) and confirm (dual button). |
| Callback Functions | Supports onConfirm and onCancel callbacks. |
| Highly Customizable | Button text, color, and style are all configurable. |
| Asynchronous Closing | Supports manual closing after loading. |
Three Invocation Methods:
// Global mode - the entire app shares one Modal instance
const globalModal = useModal()
// Local mode - independent instance for the current page
const pageModal = useModal({ page: true })
// Custom pageId - multiple independent instances within a page
const deleteModal = useModal({ page: 'deleteConfirm' })
const loginModal = useModal({ page: 'loginExpired' })
Principle of pageId Implementing Multiple Instances:
When you place multiple <u-modal /> components on a page and distinguish them by pageId, each Modal instance is completely independent and can fulfill different interaction requirements:
<template>
<view>
<!-- Normal confirmation Modal -->
<u-modal page />
<!-- Delete confirmation Modal - red warning color -->
<u-modal page="deleteConfirm" />
<!-- Login expired Modal -->
<u-modal page="loginExpired" />
</view>
</template>
// Delete operation - using the dedicated deleteConfirm instance
const deleteModal = useModal({ page: 'deleteConfirm' })
deleteModal.confirm({
title: 'Confirm Delete',
content: 'Are you sure you want to delete this data?',
confirmColor: '#ff4d4f',
onConfirm: () => {
// Execute deletion
deleteItem(id)
}
})
// Login expired - using the dedicated loginExpired instance
const loginModal = useModal({ page: 'loginExpired' })
loginModal.confirm({
title: 'Login Expired',
content: 'Please log in again to continue',
showCancelButton: false,
confirmText: 'Log in again',
onConfirm: () => {
// Jump to login page
uni.reLaunch({ url: '/pages/login/index' })
}
})
pageId Matching Mechanism:
| useModal Invocation Method | Modal Component | Responds? | Description |
|---|---|---|---|
useModal({ page: 'deleteConfirm' }) |
<u-modal page="deleteConfirm" /> |
✅ | Exact match |
useModal({ page: 'deleteConfirm' }) |
<u-modal page /> |
❌ | No pageId specified, no match |
useModal({ page: true }) |
<u-modal page /> |
✅ | Uses the current page route as pageId |
useModal() |
<u-modal global /> |
✅ | Global mode |
Typical Use Case: Triggering a parent page's Modal from a child component
// In child component
const modal = useModal({ page: 'parentModal' })
modal.confirm({
title: 'Confirm Operation',
content: 'Popup triggered by child component',
onConfirm: () => {
// Confirmation logic
}
})
<!-- In parent page -->
<template>
<view>
<ChildComponent />
<!-- The child component will trigger this Modal -->
<u-modal page="parentModal" />
</view>
</template>
3. Two Global Injection Solutions
3.1 Solution 1: Using a Global Wrapper Component (Only suitable for new projects)
For new projects, you can use an app-page component on each page to wrap the content, thereby achieving global management of Toast and Modal.
Create AppPage.vue:
<template>
<view class="app-page">
<slot />
<!-- Page-level Toast and Modal -->
<u-toast page />
<u-modal page />
</view>
</template>
Use on each page:
<template>
<AppPage>
<view class="content">
<button @click="handleToast">Show Prompt</button>
<button @click="handleModal">Show Popup</button>
</view>
</AppPage>
</template>
<script setup>
import AppPage from '@/components/AppPage.vue'
import { useToast, useModal } from 'uview-pro'
const toast = useToast()
const modal = useModal()
const handleToast = () => {
toast.show({ title: 'Global Prompt', type: 'success' })
}
const handleModal = () => {
modal.confirm({
title: 'Confirm Operation?',
content: 'This is a global popup',
onConfirm: () => {
toast.show({ title: 'Confirmed' })
}
})
}
</script>
Advantages:
- ✅ Optional for new projects: Establish a good architecture from the start.
- ✅ Fully controllable: Toast/Modal on each page is managed.
- ✅ Easy to maintain: Unified page layout management mode.
3.2 Solution 2: Using a Vite Plugin (Recommended, Non-Intrusive)
Not limited to new projects; for existing projects, you can use the Vite plugin inside uView Pro. It can inject a global root component into the application and use it flexibly without modifying any existing code.
Only two steps are needed:
Step 1: Configure vite.config.ts|js:
// vite.config.ts
import { defineConfig } from 'vite'
import Uni from '@dcloudio/vite-plugin-uni';
import { UniRoot } from 'uview-pro/plugins';
export default defineConfig({
plugins: [
UniRoot(),
Uni(),
]
})
Step 2: Create App.root.vue:
Create App.root.vue at the same level as App.vue and add the following code:
<template>
<view>
<slot />
<!-- Global Toast and Modal -->
<u-toast global />
<u-modal global />
</view>
</template>
Advantages:
- ✅ Completely non-intrusive: No need to modify any page code.
- ✅ Plug and play: Install the plugin to get global capabilities.
- ✅ Suitable for new and old projects: No need to refactor existing code at all.
For usage, please check: Official Documentation
4. Practical Comparison: More Elegant Code
4.1 Login Form Scenario
Traditional Solution (uni.showToast):
const handleLogin = () => {
uni.showToast({ title: 'Logging in...', icon: 'loading', duration: 0 })
loginApi(formData).then((res) => {
uni.hideToast()
uni.showToast({ title: 'Login successful', icon: 'success', duration: 2000 })
setTimeout(() => uni.navigateTo({ url: '/pages/home' }), 2000)
}).catch((error) => {
uni.hideToast()
uni.showToast({ title: error.message || 'Login failed', icon: 'error' })
})
}
uView Pro Solution (useToast):
const toast = useToast()
const handleLogin = () => {
toast.loading('Logging in...');
loginApi(formData).then((res) => {
toast.success({
title: 'Login successful',
callback: () => {
uni.navigateTo({ url: '/pages/home' })
}
})
}).catch((error) => {
toast.error('Login failed')
})
}
Comparison:
| Dimension | uni.showToast | useToast |
|---|---|---|
| Code Clarity | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Maintainability | Difficult | Easy |
| Multi-Prompt Support | ❌ | ✅ |
4.2 Confirmation Popup Scenario
Traditional Solution (uni.showModal):
const handleDelete = () => {
uni.showModal({
title: 'Confirm Delete?',
content: 'Data cannot be recovered after deletion',
success(res) {
if (res.confirm) {
// Deletion logic
deleteApi(id).then(() => {
uni.showToast({ title: 'Deleted successfully' })
})
}
}
})
}
uView Pro Solution (useModal):
const modal = useModal()
const toast = useToast()
const handleDelete = () => {
modal.confirm({
title: 'Confirm Delete?',
content: 'Data cannot be recovered after deletion',
onConfirm: () => {
deleteApi(id).then(() => {
modal.close();
toast.success('Deleted successfully')
})
},
onCancel: () => {
toast.warning('Deletion cancelled');
}
})
}
Comparison:
| Dimension | uni.showModal | useModal |
|---|---|---|
| Code Style | Callback nesting | Functional invocation |
| Flexibility | Fixed style | Fully customizable |
| Global Support | ❌ | ✅ |
5. Project Recommendation and Summary
5.1 Why Choose uView Pro?
| Feature | uni-app Official | uView Pro |
|---|---|---|
| Number of Toasts | 1 | Unlimited |
| Popup Style | Fixed style | Fully customizable |
| Code Style | Callback nesting | Functional invocation |
| Global Support | ❌ No root component | ✅ Supports global/local |
| Platform Adaptation | Requires extra handling | Automatic compatibility |
5.2 Integration Suggestions
| Project Type | Recommended Solution |
|---|---|
| New Project | If you don't want to introduce a plugin, you can use the app-page global wrapper component. |
| New/Old Project (don't want to change code) | uView Pro's Root Vite Plugin |
| Quick Validation | Use directly on the page |
5.3 Quick Start
If you are about to start a new uni-app project, I recommend using uView Pro Starter, a quick-start project based on the uView Pro UI component library.
The uView Pro Starter project provides a complete project skeleton and best practices, allowing you to quickly build cross-platform applications for H5, Android, iOS, HarmonyOS, various mini-programs, etc.
Quick Start Project with uView Pro Starter
Method 1: Direct Clone
git clone https://github.com/anyup/uView-Pro-Starter.git
Method 2: Create using create-uni scaffolding
pnpm create uni <project-name> -t uview-pro-starter
After creation, install dependencies to run the project. pnpm is recommended, but you can also use npm or yarn to manage the project.
cd uView-Pro-Starter
pnpm install
pnpm run dev
For more usage, please refer to the uView Pro Starter documentation: https://starter.uviewpro.cn/
Related Resources
- uView Pro Documentation: https://uviewpro.cn/
- uView Pro Starter Documentation: https://starter.uviewpro.cn/
- GitHub: https://github.com/anyup/uView-Pro
- Communication and Feedback: https://uviewpro.cn/zh/resource/about.html
Appendix: Core API Quick Reference
useToast API
// Create Toast instance
const toast = useToast() // Global mode
const toast = useToast({ page: true }) // Local mode (current page)
const toast = useToast({ page: 'customId' }) // Custom pageId (multi-instance)
// Show Toast
toast.show(content) // Basic usage
toast.show({ title, type, position, duration })
// Convenience methods
toast.success(content)
toast.error(content)
toast.warning(content)
toast.info(content)
toast.loading(content)
// Control
toast.close() // Close current
toast.closeAll() // Close all
pageId Usage Example:
// Top prompt
const topToast = useToast({ page: 'topArea' })
topToast.show({ title: 'Top Prompt', position: 'top' })
// Bottom prompt
const bottomToast = useToast({ page: 'bottomArea' })
bottomToast.show({ title: 'Bottom Prompt', position: 'bottom' })
useModal API
// Create Modal instance
const modal = useModal() // Global mode
const modal = useModal({ page: true }) // Local mode (current page)
const modal = useModal({ page: 'deleteModal' }) // Custom pageId (multi-instance)
// Show single-button popup
modal.show(content)
modal.show({ title, content, onConfirm })
// Show confirmation popup
modal.confirm(content)
modal.confirm({
title,
content,
onConfirm,
onCancel
})
// Control
modal.close()
modal.clearLoading()
pageId Usage Example:
// Delete confirmation popup
const deleteModal = useModal({ page: 'deleteModal' })
deleteModal.confirm({
title: 'Confirm Delete',
content: 'Are you sure you want to delete?',
confirmColor: '#ff4d4f',
onConfirm: () => {
deleteItem(id)
}
})
// Login expired popup
const loginModal = useModal({ page: 'loginExpired' })
loginModal.confirm({
title: 'Login Expired',
content: 'Please log in again',
showCancelButton: false,
confirmText: 'Log in again',
onConfirm: () => {
uni.reLaunch({ url: '/pages/login/index' })
}
})
Global vs Local vs Multi-Instance Comparison
| Mode | Invocation Method | Applicable Scenario |
|---|---|---|
| Global Mode | useToast() / useModal() |
Shared across the entire app, suitable for global prompts. |
| Local Mode | useToast({ page: true }) |
Independent for a single page, does not affect other pages. |
| Multi-Instance Mode | useToast({ page: 'customId' }) |
Multiple areas within a page, each area responds independently. |