元素

View

<View> 是每个界面的基本构件:一个原生容器(iOS 和 Mac Catalyst 上是 UIView,Android 上是 ViewGroup),用 flexbox 或 grid 布局子元素,绘制背景、边框、阴影和渐变,并处理按压。按钮、卡片、列表行和整个界面都是 view。

el-view.tsx 在线编辑 在新标签页打开
import { mount, signal, Text, View } from "nsh";

function App() {
  const presses = signal(0);
  const size = signal("");
  return (
    <View style={{ flex: 1, backgroundColor: "#f6f8fa", padding: 20, paddingTop: 24, gap: 16 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: "#1f2328" }}>view</Text>

      <View
        onPress={() => presses.update((n) => n + 1)}
        activeOpacity={0.5}
        style={{ backgroundColor: "#0969da", borderRadius: 12, padding: 16, alignItems: "center" }}
      >
        <Text style={{ color: "#ffffff", fontSize: 16, fontWeight: "600" }}>activeOpacity · pressed {presses()}×</Text>
      </View>

      <View
        onPress={() => presses.update((n) => n + 1)}
        underlayColor="#d0d7de"
        style={{ backgroundColor: "#ffffff", borderRadius: 12, padding: 16, borderWidth: 1, borderColor: "#d0d7de" }}
      >
        <Text style={{ color: "#1f2328", fontSize: 16 }}>underlayColor (tap and hold)</Text>
      </View>

      <View class="flex-row gap-3">
        <View class="flex-1 h-20 rounded-xl bg-rose-500 active:bg-rose-700 items-center justify-center" onPress={() => {}}>
          <Text class="text-white font-semibold">active:</Text>
        </View>
        <View class="flex-1 h-20 rounded-xl bg-emerald-500 items-center justify-center shadow-lg">
          <Text class="text-white font-semibold">shadow-lg</Text>
        </View>
        <View
          class="flex-1 h-20 rounded-xl items-center justify-center"
          style={{ backgroundImage: "linear-gradient(135deg, #8250df, #0969da)" }}
        >
          <Text class="text-white font-semibold">gradient</Text>
        </View>
      </View>

      <View
        onLayout={(e) => size.set(`${Math.round(e.width)} × ${Math.round(e.height)} pt`)}
        style={{ flex: 1, borderRadius: 12, borderWidth: 2, borderStyle: "dashed", borderColor: "#8c959f", alignItems: "center", justifyContent: "center" }}
      >
        <Text style={{ color: "#57606a" }}>onLayout: {size()}</Text>
      </View>
    </View>
  );
}

mount(App);
import { mount, signal, Text, View } from "nsh";

function App() {
  const liked = signal(false);
  return (
    <View style={{ flex: 1, padding: 24, gap: 16, backgroundColor: "#f6f8fa" }}>
      <View
        style={{
          backgroundColor: "#ffffff",
          borderRadius: 16,
          padding: 16,
          boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)",
          flexDirection: "row",
          alignItems: "center",
          gap: 12,
        }}
      >
        <View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: "#8250df" }} />
        <Text style={{ flex: 1, fontSize: 16 }}>一张卡片</Text>
        <View onPress={() => liked.set(!liked())} activeOpacity={0.5} hitSlop={8}>
          <Text style={{ fontSize: 22 }}>{liked() ? "♥" : "♡"}</Text>
        </View>
      </View>
    </View>
  );
}

mount(App);

#布局

view 用 flexbox 布局子元素,默认值为:flexDirection: "column"、flexShrink: 0、alignContent: "flex-start"、position: "relative"。display: "grid" 切换为 CSS grid,display: "block" 切换为块布局。尺寸单位是点。参见布局和样式参考。

Tailwind 的 flex 类遵循 CSS 而不是样式的默认值:class="flex" 是横向排列。需要纵向排列时用 flex-col。

#属性

除通用属性外,<View> 没有自己的属性:style(ViewStyle)、class、classList、disabled,按压属性(onPress、onLongPress、onPressIn、onPressOut、delayLongPress、activeOpacity、underlayColor、underlayOpacity、hitSlop、pointerEvents),onLayout、ref、nativeID、testID,手势属性(pan、pinch、rotation 及其事件),触摸与响应者属性,无障碍属性,以及 ios / android / desktop。

#按压反馈

带有按压处理函数的 view 就是可按压的。手指按下期间,有三种反馈在原生侧运行,不经过 JS:

// 整个视图淡化。
<View onPress={open} activeOpacity={0.6}>…</View>

// 显示背景色并淡化子元素(默认 0.85)。
<View onPress={open} underlayColor="#e5e7eb" underlayOpacity={0.7}>…</View>

// 用 active: 类在按下时改变任意样式。
<View onPress={open} class="bg-blue-600 active:bg-blue-800 active:scale-95">…</View>

在 Android 上,android={{ ripple: { color: "#0000001f" } }} 会加上 Material 水波纹(可用 borderless、radius 和 foreground 调整)。

disabled 会从原生视图上移除按压监听(既不响应按压,也没有反馈),并应用 disabled: 类:

<View onPress={submit} disabled={!valid()} class="bg-green-600 disabled:opacity-40">…</View>

#事件

事件载荷触发时机
onPress无在视图内完成一次点击。
onLongPress无手指按住达到 delayLongPress(500 ms)。
onPressIn / onPressOut无手指按下 / 抬起或移出。
onLayout{ x, y, width, height }相对父视图的 frame 发生变化。
onPanStart / onPanEndPanEvent通过 pan 绑定的拖动开始 / 结束。
onPinchStart / onPinchEnd{ scale, velocity }通过 pinch 绑定的捏合开始 / 结束。
onRotationStart / onRotationEnd{ rotation, velocity }通过 rotation 绑定的旋转开始 / 结束。
onAccessibilityAction{ actionName }执行了一个自定义无障碍操作。

触摸与响应者事件:参见通用属性。

#ref 方法

普通 view 没有自己的命令。ref 拿到的是 YNode,例如可以传给 AccessibilityInfo.setAccessibilityFocus(nsh/accessibility)。

#文本中的视图

放在 <Text> 里的 <View> 是行内附件:它坐落在基线上、随文字一起换行,并且可以单独响应按压。参见 text。

#平台说明

  • boxShadow 以及阴影属性(shadowColor、shadowOpacity、shadowRadius、shadowOffset)在两个平台上都会绘制;android={{ elevation }} 则改用 Android 的 Material 阴影。在 Android 上阴影需要背景色(背景透明的视图不会投下阴影)。
  • view 上的 filter:iOS 只支持 opacity()、不超过 1 的 brightness() 和 drop-shadow()(图片支持所有颜色滤镜和 blur());Android 从 API 31 起全部支持,低于该版本只支持颜色滤镜。
  • backdropFilter: "blur(…)" 在 iOS 上是系统材质(近似效果),在 Android 上需要 API 31。
  • Android 不支持 skewX / skewY 变换。
  • borderCurve: "continuous" 在两个平台上都会绘制平滑过渡到边的圆角(如同 iOS 应用图标)。
  • cursor 样式在有指针的场合生效(Mac Catalyst、连接指针设备的 iPad、桌面平台)。