跪拜 Guibai
← Back to the summary

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:

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:

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' })

1.gif

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')
  }
})

2.gif

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:

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:


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')
  })
}

3.gif

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');
    }
  })
}

4.gif

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

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.