请添加图片描述

项目开源地址:https://atomgit.com/nutpi/rn_for_openharmony_element

Alert 是那种"看起来简单,但设计不好就会被忽略"的组件。

用户在使用应用时,总会遇到各种需要被告知的情况:操作成功了、出错了、有风险需要注意、有新消息需要了解。这些信息如果用普通文字展示,很容易被忽略;如果用弹窗展示,又太打扰用户。Alert 就是介于两者之间的选择——它足够醒目,但不会打断用户的操作流程。

我在项目里把提示类组件分成两类:

  • [阻断式提示] 必须用户响应才能继续,比如 Modal、Dialog
  • [非阻断式提示] 展示信息但不打断操作,比如 Alert、Toast

Alert 属于第二类。它的核心任务是"让用户注意到这条信息",但不强制用户做出响应。

这篇文章会基于项目中真实存在的代码,把 Alert 的实现拆开讲清楚。全文代码片段都来自项目中的真实文件,路径是 src/components/ui/Alert.tsx

Alert 在项目里的位置

这套 UI 组件库的文件组织比较统一。Alert 相关的文件主要在这几处:

  • src/components/ui/Alert.tsx
  • src/screens/demos/AlertDemo.tsx

建议先看 Alert.tsx,理解组件的结构和样式计算逻辑;再看 AlertDemo.tsx,了解组件在仓库里被期望怎么用。

依赖引入

import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native';
import { UITheme, ColorType } from './theme';

这段引入包含几个关键模块:

  • ViewText 是基础布局和文字组件,Alert 的主体结构就靠它们
  • TouchableOpacity 用于可点击的元素,这里是关闭按钮和操作按钮
  • StyleSheet 用于定义静态样式,性能比内联样式更好
  • UIThemeColorType 来自项目的主题配置,保证组件风格统一

为什么用 TouchableOpacity 而不是 Pressable?TouchableOpacity 自带点击时的透明度变化反馈,对于这种小按钮来说足够了,不需要 Pressable 那么多的自定义能力。

AlertProps:接口设计

interface AlertProps {
  type?: ColorType;
  title?: string;
  message: string;
  icon?: string;
  closable?: boolean;
  onClose?: () => void;
  action?: { label: string; onPress: () => void };
  variant?: 'filled' | 'outlined' | 'soft';
  style?: ViewStyle;
}

接口设计覆盖了 Alert 的所有使用场景,我来逐个解释:

  • type:语义类型,决定 Alert 的颜色。支持 primary、secondary、success、warning、danger、info 六种。不同类型传达不同的信息级别,比如 success 表示操作成功,danger 表示出错了

  • title:可选的标题。简单提示可以只有 message,重要提示可以加上 title 让信息层次更清晰

  • message:必填的提示内容。这是 Alert 最核心的信息,必须传入

  • icon:自定义图标。如果不传,会根据 type 自动选择默认图标

  • closable:是否显示关闭按钮。临时性的提示通常需要让用户能关闭它

  • onClose:关闭按钮的回调函数。配合 closable 使用,点击关闭按钮时触发

  • action:操作按钮配置。有些 Alert 需要用户做出响应,比如"立即更新"、“查看详情”

  • variant:样式变体,三种可选。filled 是填充背景,outlined 是描边,soft 是柔和的浅色背景

  • style:允许外部传入额外样式,用于微调位置或覆盖默认样式

组件函数签名与默认值

export const Alert: React.FC<AlertProps> = ({
  type = 'info',
  title,
  message,
  icon,
  closable = false,
  onClose,
  action,
  variant = 'soft',
  style,
}) => {

这里用解构赋值的方式设置了默认值:

  • type = 'info':默认是信息类型。info 是最中性的类型,适合一般性的提示
  • closable = false:默认不可关闭。大多数 Alert 是持久展示的,需要关闭功能时再显式开启
  • variant = 'soft':默认使用柔和样式。soft 样式最不抢眼,适合大多数场景

为什么默认用 soft 而不是 filled?因为 Alert 通常是页面内容的一部分,不应该太抢眼。soft 样式用浅色背景,既能让用户注意到,又不会喧宾夺主。filled 样式更适合需要强调的场景。

颜色值获取

  const colorValue = UITheme.colors[type];

这行代码把语义化的类型名转换成实际的颜色值。比如 type = 'success' 会得到 '#10B981'(绿色)。

这种设计的好处是:

  1. 使用时只需要关心语义(success、danger),不需要记颜色值
  2. 换主题时只需要改 UITheme,所有组件都会跟着变
  3. 保证了整个应用的颜色一致性

默认图标映射

  const defaultIcons: Record<ColorType, string> = {
    primary: 'ℹ️',
    secondary: '📌',
    success: '✅',
    warning: '⚠️',
    danger: '❌',
    info: 'ℹ️',
  };

这个对象定义了每种类型对应的默认图标:

  • primary 和 info:用 ℹ️ 信息图标,表示一般性提示
  • secondary:用 📌 图钉图标,表示次要信息或备注
  • success:用 ✅ 对勾图标,表示操作成功
  • warning:用 ⚠️ 警告图标,表示需要注意
  • danger:用 ❌ 叉号图标,表示错误或危险

用 emoji 作为图标有几个好处:不需要引入图标库、跨平台显示一致、语义直观。如果你的项目用了图标库,也可以把这里改成图标组件。

样式计算函数

  const getStyles = (): { bg: string; border: string; text: string } => {
    switch (variant) {
      case 'filled':
        return { bg: colorValue, border: colorValue, text: UITheme.colors.white };
      case 'outlined':
        return { bg: 'transparent', border: colorValue, text: colorValue };
      case 'soft':
        return { bg: `${colorValue}15`, border: `${colorValue}30`, text: colorValue };
      default:
        return { bg: `${colorValue}15`, border: `${colorValue}30`, text: colorValue };
    }
  };

这个函数根据 variant 计算背景色、边框色和文字色。三种变体的视觉效果差异很大:

filled(填充)

  • 背景色是主题色本身(colorValue)
  • 边框色也是主题色
  • 文字用白色,和深色背景形成对比
  • 视觉冲击力最强,适合需要强调的场景

outlined(描边)

  • 背景透明
  • 边框是主题色
  • 文字也是主题色
  • 视觉上比较轻,适合不想太抢眼但又要有存在感的场景

soft(柔和)

  • 背景是主题色加 15% 透明度(${colorValue}15
  • 边框是主题色加 30% 透明度
  • 文字是主题色
  • 最柔和的样式,适合大多数场景

这里用了一个小技巧:${colorValue}15 是在颜色值后面加两位十六进制数,表示透明度。15 大约是 8% 的透明度,30 大约是 19% 的透明度。这样可以基于主题色生成浅色版本,不需要额外定义。

  const { bg, border, text } = getStyles();

调用函数并解构出三个颜色值,后面渲染时会用到。

容器渲染

  return (
    <View
      style={[
        styles.container,
        { backgroundColor: bg, borderColor: border },
        style,
      ]}
    >

Alert 的最外层容器,样式合并了三部分:

  • styles.container:静态的基础样式,包括布局方向、内边距、圆角、边框宽度
  • { backgroundColor: bg, borderColor: border }:动态计算的颜色
  • style:外部传入的自定义样式

样式数组的顺序很重要:后面的会覆盖前面的。所以外部传入的 style 可以覆盖默认样式。

图标渲染

      <Text style={styles.icon}>{icon || defaultIcons[type]}</Text>

图标的渲染逻辑很简单:

  • 如果外部传了 icon,就用外部的
  • 如果没传,就从 defaultIcons 里根据 type 取默认图标

|| 运算符实现这个逻辑。如果 icon 是 undefined 或空字符串,就会使用后面的默认值。

图标放在最左边,和内容区域有一定间距(marginRight),让整个 Alert 的视觉结构更清晰。

内容区域渲染

      <View style={styles.content}>
        {title && <Text style={[styles.title, { color: text }]}>{title}</Text>}
        <Text style={[styles.message, { color: variant === 'filled' ? text : UITheme.colors.gray[700] }]}>
          {message}
        </Text>

内容区域包含标题和消息两部分:

标题渲染

  • {title && ...} 条件渲染,只有传了 title 才显示
  • 文字颜色用前面计算的 text 值
  • 样式里设置了 fontWeight: ‘600’ 让标题更醒目

消息渲染

  • message 是必填的,所以不需要条件判断
  • 文字颜色有个特殊处理:filled 变体用 text 颜色(白色),其他变体用灰色(gray[700])

为什么 message 的颜色要特殊处理?因为 filled 变体的背景是深色的,如果 message 也用灰色会看不清。而 soft 和 outlined 变体的背景是浅色或透明的,用灰色的 message 可以和彩色的 title 形成层次感。

操作按钮渲染

        {action && (
          <TouchableOpacity onPress={action.onPress} style={styles.action}>
            <Text style={[styles.actionText, { color: text }]}>{action.label}</Text>
          </TouchableOpacity>
        )}
      </View>

操作按钮的渲染逻辑:

  • {action && ...} 条件渲染,只有传了 action 才显示
  • action.onPress 是点击回调,action.label 是按钮文字
  • 按钮文字用主题色,和普通文字区分开,让用户知道这是可点击的
  • marginTop: UITheme.spacing.sm 让按钮和上面的文字有间距

操作按钮的典型使用场景:

  • “立即更新”——提示有新版本
  • “查看详情”——提示有新消息
  • “重试”——提示操作失败

关闭按钮渲染

      {closable && (
        <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
          <Text style={[styles.closeIcon, { color: text }]}>×</Text>
        </TouchableOpacity>
      )}
    </View>
  );
};

关闭按钮的渲染逻辑:

  • {closable && ...} 条件渲染,只有 closable 为 true 才显示
  • 点击时调用 onClose 回调,由外部决定如何处理(通常是隐藏这个 Alert)
  • 用 × 符号作为关闭图标,简单直观
  • 放在最右边,和内容区域有间距

关闭按钮的颜色也用 text 值,保证和整体风格一致。fontSize: 20 让关闭按钮足够大,方便点击。

样式定义

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    padding: UITheme.spacing.md,
    borderRadius: UITheme.borderRadius.md,
    borderWidth: 1,
  },

容器的基础样式:

  • flexDirection: 'row':让图标、内容、关闭按钮横向排列
  • padding: UITheme.spacing.md:内边距用主题的中等间距(12px)
  • borderRadius: UITheme.borderRadius.md:圆角用主题的中等圆角(8px)
  • borderWidth: 1:边框宽度 1px,颜色由动态样式控制
  icon: { fontSize: 18, marginRight: UITheme.spacing.sm },

图标样式:

  • fontSize: 18:图标大小 18px,比正文稍大一点
  • marginRight: UITheme.spacing.sm:右边距 8px,和内容区域保持间距
  content: { flex: 1 },

内容区域样式:

  • flex: 1:占据剩余空间。这样图标在左边固定宽度,关闭按钮在右边固定宽度,中间的内容区域自适应
  title: { fontSize: UITheme.fontSize.md, fontWeight: '600', marginBottom: 2 },
  message: { fontSize: UITheme.fontSize.sm },

标题和消息的样式:

  • 标题用 md 字号(14px),加粗(fontWeight: ‘600’),下边距 2px
  • 消息用 sm 字号(12px),比标题小一号,形成层次感
  action: { marginTop: UITheme.spacing.sm },
  actionText: { fontSize: UITheme.fontSize.sm, fontWeight: '600' },

操作按钮样式:

  • marginTop: UITheme.spacing.sm:和上面的文字保持 8px 间距
  • 文字加粗,让用户知道这是可点击的
  closeBtn: { marginLeft: UITheme.spacing.sm },
  closeIcon: { fontSize: 20, fontWeight: '600' },
});

关闭按钮样式:

  • marginLeft: UITheme.spacing.sm:和内容区域保持 8px 间距
  • fontSize: 20:关闭图标稍大一点,方便点击
  • fontWeight: '600':加粗让 × 符号更清晰

Demo 页面解析

Demo 页面展示了 Alert 的各种用法,路径是 src/screens/demos/AlertDemo.tsx

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { ComponentShowcase, ShowcaseSection } from '../ComponentShowcase';
import { Alert } from '../../components/ui/Alert';
import { UITheme } from '../../components/ui/theme';

引入了展示框架组件和 Alert 组件。

export const AlertDemo: React.FC<{ onBack: () => void }> = ({ onBack }) => {
  return (
    <ComponentShowcase title="Alert" icon="⚠️" description="警告提示用于展示重要的提示信息" onBack={onBack}>

Demo 页面的外层容器,提供标题、图标、描述和返回按钮。

      <ShowcaseSection title="类型" description="四种语义类型">
        <Alert type="info" message="这是一条信息提示" />
        <View style={{ height: 12 }} />
        <Alert type="success" message="操作成功完成" />
        <View style={{ height: 12 }} />
        <Alert type="warning" message="请注意,这是一条警告" />
        <View style={{ height: 12 }} />
        <Alert type="danger" message="发生错误,请重试" />
      </ShowcaseSection>

第一个展示区块展示四种语义类型。每种类型有不同的颜色和默认图标:

  • info:蓝色,ℹ️ 图标,用于一般性提示
  • success:绿色,✅ 图标,用于成功提示
  • warning:橙色,⚠️ 图标,用于警告提示
  • danger:红色,❌ 图标,用于错误提示

<View style={{ height: 12 }} /> 是间距占位,让多个 Alert 之间有空隙。

      <ShowcaseSection title="带标题" description="显示标题和描述">
        <Alert type="info" title="提示" message="这是一条带标题的信息提示,可以包含更详细的说明文字。" />
        <View style={{ height: 12 }} />
        <Alert type="success" title="成功" message="您的订单已成功提交,我们会尽快处理。" />
      </ShowcaseSection>

第二个展示区块展示带标题的 Alert。标题让信息层次更清晰:

  • 标题是简短的关键词(“提示”、“成功”)
  • message 是详细的说明文字

这种结构适合需要传达较多信息的场景。

      <ShowcaseSection title="样式变体" description="填充、描边、柔和三种样式">
        <Alert type="primary" message="填充样式" variant="filled" />
        <View style={{ height: 12 }} />
        <Alert type="primary" message="描边样式" variant="outlined" />
        <View style={{ height: 12 }} />
        <Alert type="primary" message="柔和样式" variant="soft" />
      </ShowcaseSection>

第三个展示区块对比三种样式变体。用同一个 type(primary)展示,方便用户看出样式差异:

  • filled:深色背景 + 白色文字,最醒目
  • outlined:透明背景 + 彩色边框,中等醒目
  • soft:浅色背景 + 彩色文字,最柔和

选择哪种变体取决于你想要的视觉强度。一般来说,错误提示可以用 filled 强调,普通提示用 soft 就够了。

      <ShowcaseSection title="可关闭" description="点击关闭按钮隐藏提示">
        <Alert type="info" message="这是一条可关闭的提示" closable onClose={() => {}} />
        <View style={{ height: 12 }} />
        <Alert type="warning" title="警告" message="这是一条可关闭的警告提示" closable onClose={() => {}} />
      </ShowcaseSection>

第四个展示区块展示可关闭的 Alert。closable 属性让 Alert 右边出现关闭按钮。

Demo 里的 onClose={() => {}} 是空函数,实际使用时应该传入真正的关闭逻辑,比如:

const [visible, setVisible] = useState(true);
{visible && <Alert closable onClose={() => setVisible(false)} ... />}
      <ShowcaseSection title="带操作" description="提示框中添加操作按钮">
        <Alert
          type="info"
          title="新版本可用"
          message="发现新版本 v2.0.0,是否立即更新?"
          action={{ label: '立即更新', onPress: () => {} }}
        />
      </ShowcaseSection>
    </ComponentShowcase>
  );
};

第五个展示区块展示带操作按钮的 Alert。action 属性接收一个对象,包含 label(按钮文字)和 onPress(点击回调)。

这种 Alert 适合需要用户响应的场景:

  • 版本更新提示
  • 权限申请提示
  • 引导用户完成某个操作

实际应用场景

表单验证提示

表单提交后显示验证结果:

const FormPage = () => {
  const [error, setError] = useState<string | null>(null);
  const [success, setSuccess] = useState(false);

  const handleSubmit = async () => {
    try {
      await api.submit(formData);
      setSuccess(true);
      setError(null);
    } catch (e) {
      setError(e.message);
      setSuccess(false);
    }
  };

  return (
    <View>
      {error && (
        <Alert 
          type="danger" 
          title="提交失败" 
          message={error} 
          closable 
          onClose={() => setError(null)} 
        />
      )}
      {success && (
        <Alert 
          type="success" 
          message="提交成功!" 
          closable 
          onClose={() => setSuccess(false)} 
        />
      )}
      {/* 表单内容 */}
    </View>
  );
};

这个例子展示了 Alert 和表单的配合:

  • 用 useState 管理错误和成功状态
  • 提交失败时显示 danger 类型的 Alert,带标题和详细错误信息
  • 提交成功时显示 success 类型的 Alert
  • 都设置了 closable,让用户可以关闭提示

页面顶部通知

在页面顶部显示重要通知:

const HomePage = () => {
  const [showNotice, setShowNotice] = useState(true);

  return (
    <View style={styles.container}>
      {showNotice && (
        <Alert
          type="warning"
          title="系统维护通知"
          message="系统将于今晚 22:00-24:00 进行维护,届时部分功能可能无法使用。"
          closable
          onClose={() => setShowNotice(false)}
          variant="filled"
        />
      )}
      {/* 页面内容 */}
    </View>
  );
};

这种场景适合用 filled 变体,让通知更醒目。用户看完后可以关闭,不影响后续使用。

功能引导提示

引导用户使用新功能:

const FeaturePage = () => {
  const [dismissed, setDismissed] = useState(false);

  if (dismissed) return null;

  return (
    <Alert
      type="info"
      title="新功能上线"
      message="现在支持语音输入了,点击下方按钮体验。"
      action={{
        label: '立即体验',
        onPress: () => {
          navigation.navigate('VoiceInput');
          setDismissed(true);
        },
      }}
      closable
      onClose={() => setDismissed(true)}
    />
  );
};

这个例子结合了 action 和 closable:

  • 用户可以点击"立即体验"跳转到新功能
  • 也可以点击关闭按钮忽略这个提示
  • 两种操作都会让提示消失

网络状态提示

显示网络连接状态:

const NetworkAlert = () => {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsOnline(state.isConnected);
    });
    return () => unsubscribe();
  }, []);

  if (isOnline) return null;

  return (
    <Alert
      type="danger"
      message="网络连接已断开,请检查网络设置"
      variant="filled"
      action={{
        label: '重试',
        onPress: () => NetInfo.fetch(),
      }}
    />
  );
};

网络断开时显示 danger 类型的 Alert,用 filled 变体强调严重性。提供"重试"按钮让用户可以手动刷新网络状态。

设计建议

类型选择

选择 Alert 类型时,要考虑信息的性质:

  • info:一般性提示,不需要用户特别关注。比如"您有 3 条未读消息"
  • success:操作成功的反馈。比如"保存成功"、“提交成功”
  • warning:需要用户注意但不是错误。比如"密码强度较弱"、“即将过期”
  • danger:错误或危险操作。比如"登录失败"、“删除后无法恢复”

不要滥用 danger 类型。如果所有提示都是红色的,用户会对红色脱敏,真正的错误反而被忽略。

变体选择

三种变体的使用场景:

  • soft:默认选择,适合大多数场景。视觉上柔和,不会打扰用户
  • outlined:需要一点存在感但不想太抢眼。适合表单内的提示
  • filled:需要强调的重要信息。适合错误提示、系统通知

一个页面上不要同时出现太多 filled 样式的 Alert,会让页面看起来很"吵"。

内容编写

Alert 的文案要简洁明了:

  • 标题用 2-4 个字的关键词
  • message 用一句话说清楚
  • 如果信息太多,考虑用 Modal 而不是 Alert

好的例子:

  • 标题:“保存成功”,message:“您的修改已保存”
  • 标题:“网络错误”,message:“请检查网络连接后重试”

不好的例子:

  • 标题:“操作结果通知”,message:“您刚才进行的保存操作已经成功完成,系统已经将您的数据保存到服务器上…”

和 Toast 的区别

Alert 和 Toast 都是提示类组件,但使用场景不同:

  • Alert:持久展示,需要用户主动关闭或一直显示。适合重要信息、需要用户响应的提示
  • Toast:自动消失,通常 2-3 秒后自动隐藏。适合轻量级的操作反馈

举个例子:

  • “保存成功”——用 Toast,用户看一眼就够了
  • “您的账户即将过期,请续费”——用 Alert,需要用户注意并可能采取行动

如果你不确定用哪个,问自己一个问题:这条信息用户需要仔细阅读吗?需要就用 Alert,不需要就用 Toast。

和 Modal 的区别

Alert 和 Modal 都可以展示重要信息,但交互方式不同:

  • Alert:非阻断式,用户可以继续操作页面其他内容
  • Modal:阻断式,用户必须响应后才能继续

举个例子:

  • “系统将于今晚维护”——用 Alert,用户知道就行,不需要立即响应
  • “确定要删除这条记录吗?”——用 Modal,需要用户明确确认

Alert 的 action 按钮是可选的响应,Modal 的按钮通常是必须的响应。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐