元素
Text
<Text> 绘制一个段落。它是唯一能显示字符串的元素:<View> 的字符串子节点什么也不会画。段落在原生侧排版(iOS 和 Mac Catalyst 上用 CoreText,Android 上用 StaticLayout),并在引擎线程上测量,主线程只负责绘制。
import { mount, signal, Text, View } from "nsh";
const LONG =
"Text is typeset natively (CoreText on iOS, StaticLayout on Android) and measured on the engine thread, so the main thread only draws it.";
function App() {
const tapped = signal("nothing yet");
const lines = signal(2);
const link = { color: "#0969da", textDecorationLine: "underline" } as const;
return (
<View style={{ flex: 1, backgroundColor: "#ffffff", padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: "#1f2328" }}>text</Text>
<Text style={{ fontSize: 16, lineHeight: 24, color: "#1f2328" }}>
{"Nested text elements are spans: "}
<Text style={{ fontWeight: "700" }}>bold</Text>
{", "}
<Text style={{ fontStyle: "italic", color: "#8250df" }}>italic</Text>
{", and "}
<Text style={link} onPress={() => tapped.set("the first link")}>
links
</Text>
{" that take presses of "}
<Text style={link} onPress={() => tapped.set("the second link")}>
their own
</Text>
.
</Text>
<Text style={{ fontSize: 13, color: "#6e7781" }}>Pressed: {tapped()}</Text>
<View style={{ height: 1, backgroundColor: "#d0d7de" }} />
<Text numberOfLines={lines()} style={{ fontSize: 15, color: "#1f2328" }}>
{LONG}
</Text>
<View style={{ flexDirection: "row", gap: 8 }}>
<View
onPress={() => lines.set(lines() === 2 ? 0 : 2)}
activeOpacity={0.6}
style={{ backgroundColor: "#ddf4ff", borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8 }}
>
<Text style={{ color: "#0969da" }}>{lines() === 2 ? "Show all" : "numberOfLines 2"}</Text>
</View>
</View>
<Text numberOfLines={1} ellipsizeMode="middle" style={{ fontSize: 15, color: "#57606a" }}>
{LONG}
</Text>
<Text style={{ fontSize: 17, color: "#1f2328" }}>
<Text style={{ textDecorationLine: "line-through", textDecorationColor: "#cf222e" }}>struck</Text>{" "}
<Text style={{ textTransform: "uppercase", letterSpacing: 2 }}>spaced</Text>{" "}
<Text style={{ fontVariant: ["tabular-nums"] }}>0123456789</Text>
</Text>
<Text
style={{
fontSize: 28,
fontWeight: "800",
color: "#ffffff",
backgroundColor: "#54aeff",
paddingHorizontal: 10,
borderRadius: 8,
textShadowColor: "rgba(0,0,0,0.5)",
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 4,
}}
>
Text shadow
</Text>
</View>
);
}
mount(App);<Text style={{ fontSize: 16, lineHeight: 24, color: "#1f2328" }}>
继续之前请阅读<text style={{ fontWeight: "700" }}>条款</Text>。
</Text>
#片段
<Text> 里的 <Text> 是同一段落中的一个片段(span),而不是单独的视图。片段继承段落的文本样式(color、fontSize、fontWeight 等),并覆盖自己设置的部分。整个文本块是一个原生视图。
<Text style={{ fontSize: 17 }}>
<Text style={{ fontWeight: "300" }}>细体 </Text>
<Text style={{ fontWeight: "bold" }}>粗体 </Text>
<Text style={{ fontStyle: "italic", color: "#8250df" }}>斜体</Text>
</Text>
#链接
带按压处理函数的片段就是链接:按在它的字符上由该片段处理,按在其他位置则由段落处理。
<Text style={{ fontSize: 15 }} onPress={() => log("整句")}>
{"继续即表示你同意"}
<Text style={{ color: "#0969da", textDecorationLine: "underline" }} onPress={() => open("terms")}>
服务条款
</Text>
。
</Text>
#行内视图和图片
<Text> 中的其他元素(<View>、<Image>,即使位于片段内)都是行内附件:按自身尺寸布局,坐落在基线上,并随文字一起换行。它们可以单独响应按压。
<Text style={{ fontSize: 16, lineHeight: 26 }}>
{"由 "}
<Image src={logo} style={{ width: 18, height: 18, borderRadius: 4 }} />
{" nsh 构建。状态 "}
<View style={{ backgroundColor: "#1a7f37", borderRadius: 9, paddingHorizontal: 7, height: 18, justifyContent: "center" }}>
<Text style={{ color: "#ffffff", fontSize: 11, fontWeight: "700" }}>LIVE</Text>
</View>
</Text>
被 numberOfLines 或省略号截掉的附件会被隐藏。复制可选中的文本时不包含附件。
#空白字符
JSX 文本遵循 React 的规则:除文本首尾外,每行都会去掉首尾空白,只含空白的行被丢弃,剩下的行用一个空格连接。同一行内的空格会保留({a} {b} 保留中间的空格)。片段需要前导或尾随空格时,写 {" "} 或字符串表达式。
#属性
除通用属性外(style 的类型为 TextStyle):
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
numberOfLines | number | 0 | 最多显示的行数;0 表示不限。 |
ellipsizeMode | "tail" | "head" | "middle" | "clip" | "tail" | 超出 numberOfLines 的文本在哪里截断。 |
allowFontScaling | boolean | true | 跟随系统的文字大小设置。 |
maxFontSizeMultiplier | number | 0 | 系统缩放的上限;0 表示不限。 |
adjustsFontSizeToFit | boolean | false | 缩小字号,直到文本能放进其行数(numberOfLines)和盒子。 |
minimumFontScale | number | adjustsFontSizeToFit 最多缩小到的比例(0..1;至少 0.1)。 | |
selectable | boolean | false | 长按高亮整个文本块并提供“复制”。 |
onTextLayout | (e: { lines: TextLayoutLine[] }) => void | 每次布局后给出排好的各行。 |
Tailwind 类 select-text 等同于 selectable。
#文本样式
TextStyle 在视图样式之上增加了:color、fontSize、fontWeight("normal"、"bold" 或 100–900)、fontStyle、fontFamily、lineHeight、letterSpacing、textAlign、textAlignVertical、textTransform、textDecorationLine、textDecorationColor、textDecorationStyle、textDecorationThickness、textUnderlineOffset、textShadowColor、textShadowOffset、textShadowRadius、fontVariant 和 writingDirection。参见样式参考和文本。
fontFamily 接受系统字体族,或导入字体文件得到的名称:
import { Text } from "nsh";
import Inter from "./fonts/Inter-Regular.ttf";
<Text style={{ fontFamily: Inter, fontSize: 17 }}>Inter</Text>;
字体会在第一帧之前注册。参见字体。
#事件
| 事件 | 载荷 | 触发时机 |
|---|---|---|
onTextLayout | { lines: TextLayoutLine[] } | 段落每次布局之后。 |
onPress、onLongPress 等 | 无 | 与 view 相同;在片段上时,指按在其字符上的按压。 |
onLayout | { x, y, width, height } | 文本块的 frame 发生变化。 |
TextLayoutLine 为 { text, x, y, width, height, ascender, descender, capHeight, xHeight },坐标位于文本的内容盒中:
const lines = signal(0);
<Text onTextLayout={(e) => lines.set(e.lines.length)}>{article}</Text>;
#ref 方法
没有自己的方法。
#平台说明
selectable复制的是整个文本块,不支持选择部分范围。iOS 显示编辑菜单,Android 显示浮动操作模式。- 在 iOS 上,非实线单线的文本装饰线(
double、dotted、dashed、wavy)由 nsh 自行绘制。 - iOS 上的文本不绘制渐变背景和内阴影。
nsh/device的fontScale()给出当前系统的文字缩放比例。