欢迎光临
我们一直在努力

【现代声明式UI学与练】第5课 条件渲染与组合——如何根据状态渲染不同 UI、如何组合多个条件、如何避免条件渲染的陷阱

一、本课目标

学完这一课,你要建立六个认知:

  • 条件渲染的本质:条件渲染是"根据状态选择不同的 UI 描述",是 视图 = 函数(状态) 在分支逻辑上的体现。
  • 三种基本写法:if/else、三元表达式、逻辑与。
  • 多条件组合:多状态组合时,渲染函数按优先级选择 UI 描述。
  • 条件渲染与 key:同一位置的不同分支需要 key 区分,否则状态可能错乱。
  • 条件渲染的性能:避免不必要的重渲染,避免条件判断过于复杂。
  • 实战能力:能写出清晰、可维护、无 bug 的条件渲染。
  • 本课核心线索:

    视图 = 函数(状态)
    ↓
    状态有多个分支 → 函数根据分支返回不同 UI 描述
    ↓
    条件渲染:if/else、三元、逻辑与
    ↓
    多条件组合:按优先级选择
    ↓
    条件渲染与 key:同一位置的不同分支需要区分
    ↓
    性能考量:避免不必要的重渲染
    ↓
    实战:清晰、可维护、无 bug


    二、条件渲染的本质

    2.1 一句话说透

    条件渲染就是"根据状态选择不同的 UI 描述":

    视图 = 函数(状态)
    ↓ 状态有多个分支
    ↓ 函数根据状态分支返回不同 UI 描述

    关键理解: 条件渲染不是"手动显示/隐藏控件",而是"函数根据状态返回不同的 UI 描述"。状态变化,函数重新执行,返回新的 UI 描述。

    2.2 条件渲染 vs 命令式显示/隐藏

    命令式:手动显示/隐藏。

    // JavaScript
    // 命令式:手动控制可见性
    if (isLoading) {
    spinner.style.display = 'block'
    content.style.display = 'none'
    } else {
    spinner.style.display = 'none'
    content.style.display = 'block'
    }

    声明式:根据状态返回不同描述。

    // TSX (React Native)
    // 声明式:根据状态返回不同 UI 描述
    function View({ isLoading }) {
    if (isLoading) return <Spinner />
    return <Content />
    }

    关键区别:

    维度 命令式 声明式
    做法 手动显示/隐藏控件 根据状态返回不同描述
    状态与 UI 手动同步 自动映射
    代码 多条命令 一个分支

    2.3 条件渲染的三层含义

    第一层:条件是状态的一部分。

    状态:isLoading
    → true → 返回 <Spinner />
    → false → 返回 <Content />

    第二层:分支是函数的自然表达。

    函数(状态) = if (isLoading) <Spinner /> else <Content />

    第三层:状态变化触发分支重算。

    isLoading: true → false
    → 函数重新执行
    → 返回 <Content />
    → 框架对比新旧描述,如果有差异就通知底层更新

    2.4 各框架条件渲染对照

    框架 条件渲染方式
    React Native if/else、三元、&&
    SwiftUI if/else、switch、@ViewBuilder
    ArkUI if/else、ForEach + 条件
    Compose if/else、when
    Flutter if/else、三元、??

    关键结论: 五种框架的条件渲染本质相同——根据状态选择不同的 UI 描述。差异只在语法。


    三、条件渲染的基本写法

    3.1 if/else 写法

    适用场景: 两个或多个互斥分支。

    React Native:

    // TSX (React Native)
    function View({ isLoading, error, data }) {
    // 多分支:if/else
    if (isLoading) return <Spinner />
    if (error) return <Error message={error} />
    return <Content data={data} />
    }

    SwiftUI:

    // Swift (SwiftUI)
    struct ContentView: View {
    let isLoading: Bool
    let error: String?
    let data: [Item]

    var body: some View {
    // 多分支:if/else if/else
    if isLoading {
    Spinner()
    } else if let error = error {
    ErrorView(message: error)
    } else {
    ListView(data: data)
    }
    }
    }

    ArkUI:

    // ArkTS (ArkUI)
    @Component
    struct ContentView {
    @Prop isLoading: boolean
    @Prop error: string
    @Prop data: Item[]

    build() {
    // 多分支:if/else if/else
    if (this.isLoading) {
    Spinner()
    } else if (this.error) {
    ErrorView({ message: this.error })
    } else {
    ListView({ data: this.data })
    }
    }
    }

    Compose:

    // Kotlin (Jetpack Compose)
    @Composable
    fun ContentView(
    isLoading: Boolean,
    error: String?,
    data: List<Item>
    ) {
    // 多分支:if/else if/else
    if (isLoading) {
    Spinner()
    } else if (error != null) {
    ErrorView(message = error)
    } else {
    ListView(data = data)
    }
    }

    Flutter:

    // Dart (Flutter)
    Widget build(BuildContext context) {
    // 多分支:if/else if/else
    if (isLoading) {
    return Spinner();
    } else if (error != null) {
    return ErrorView(message: error!);
    } else {
    return ListView(data: data);
    }
    }

    3.2 三元表达式写法

    适用场景: 两个分支,且适合内联。

    React Native:

    // TSX (React Native)
    function Toggle({ isOn }) {
    // 三元:两个分支
    return (
    <Text>{isOn ? '开' : '关'}</Text>
    )
    }

    SwiftUI:

    // Swift (SwiftUI)
    struct ToggleView: View {
    let isOn: Bool

    var body: some View {
    // 三元:两个分支
    Text(isOn ? "开" : "关")
    }
    }

    ArkUI:

    // ArkTS (ArkUI)
    @Component
    struct ToggleView {
    @Prop isOn: boolean

    build() {
    // 三元:两个分支
    Text(this.isOn ? '开' : '关')
    }
    }

    Compose:

    // Kotlin (Jetpack Compose)
    @Composable
    fun ToggleView(isOn: Boolean) {
    // 三元:两个分支(Kotlin 用 if 表达式)
    Text(if (isOn) "开" else "关")
    }

    Flutter:

    // Dart (Flutter)
    Widget build(BuildContext context) {
    // 三元:两个分支
    return Text(isOn ? '开' : '关');
    }

    3.3 逻辑与写法

    适用场景: 条件为真时渲染,为假时不渲染。

    React Native:

    // TSX (React Native)
    function List({ items }) {
    return (
    <View>
    {/* 逻辑与:items 为空时显示 */}
    {items.length === 0 && <Text>暂无数据</Text>}

    {/* 逻辑与:有数据时渲染列表 */}
    {items.length > 0 && (
    <View>
    {items.map(item => <Item key={item.id} {…item} />)}
    </View>
    )}
    </View>
    )
    }

    SwiftUI:

    // Swift (SwiftUI)
    struct ListView: View {
    let items: [Item]

    var body: some View {
    VStack {
    // SwiftUI 没有逻辑与,用 if
    if items.isEmpty {
    Text("暂无数据")
    }

    if !items.isEmpty {
    ForEach(items) { item in
    ItemView(item: item)
    }
    }
    }
    }
    }

    ArkUI:

    // ArkTS (ArkUI)
    @Component
    struct ListView {
    @Prop items: Item[]

    build() {
    Column() {
    // ArkUI 用 if
    if (this.items.length === 0) {
    Text('暂无数据')
    }

    if (this.items.length > 0) {
    ForEach(this.items, (item: Item) => {
    ItemView({ item: item })
    }, (item: Item) => item.id.toString())
    }
    }
    }
    }

    Compose:

    // Kotlin (Jetpack Compose)
    @Composable
    fun ListView(items: List<Item>) {
    Column {
    // Compose 用 if
    if (items.isEmpty()) {
    Text("暂无数据")
    }

    if (items.isNotEmpty()) {
    items.forEach { item ->
    key(item.id) {
    ItemView(item)
    }
    }
    }
    }
    }

    Flutter:

    // Dart (Flutter)
    Widget build(BuildContext context) {
    return Column(
    children: [
    // Dart 用 collection if
    if (items.isEmpty) Text('暂无数据'),

    if (items.isNotEmpty)
    ...items.map((item) => ItemView(
    key: ValueKey(item.id),
    item: item,
    )),
    ],
    );
    }

    3.4 三种写法的选择

    写法 适用场景 优点 缺点
    if/else 多分支、互斥 清晰、易读 不能内联
    三元 两分支、内联 简洁 嵌套可读性差
    逻辑与 条件为真时渲染 简洁 假值陷阱

    选择原则:

    1. 多分支 → if/else
    2. 两分支且适合内联 → 三元
    3. 条件为真时渲染,为假时不渲染 → 逻辑与
    4. 嵌套过深 → 提取为独立组件或函数

    3.5 各框架条件渲染对照

    框架 if/else 三元 逻辑与
    React Native if/else a ? b : c a && b
    SwiftUI if/else a ? b : c 不支持,用 if
    ArkUI if/else a ? b : c 不支持,用 if
    Compose if/else if (a) b else c 不支持,用 if
    Flutter if/else a ? b : c collection if


    四、多条件组合

    4.1 多状态的优先级

    当多个状态同时存在时,渲染函数按优先级选择 UI 描述。

    // TSX (React Native)
    function View({ isLoading, error, data }) {
    // 优先级:加载中 > 错误 > 正常
    if (isLoading) return <Spinner />
    if (error) return <Error message={error} />
    return <Content data={data} />
    }

    优先级设计原则:

    1. 加载态优先于错误态
    → 正在加载时,不显示错误

    2. 错误态优先于正常态
    → 有错误时,不显示内容

    3. 空态优先于正常态
    → 数据为空时,显示"暂无数据"

    4. 权限态优先于内容态
    → 无权限时,不显示内容

    4.2 优先级的具体设计

    示例:一个完整的页面状态设计。

    // TSX (React Native)
    function Page({ isLoading, error, data, hasPermission, isEmpty }) {
    // 1. 权限检查(最高优先级)
    if (!hasPermission) return <NoPermission />

    // 2. 加载中
    if (isLoading) return <Spinner />

    // 3. 错误
    if (error) return <Error message={error} />

    // 4. 空态
    if (isEmpty) return <Empty />

    // 5. 正常内容
    return <Content data={data} />
    }

    优先级流程图:

    ┌─────────────────┐
    │ hasPermission? │
    └────────┬────────┘
    │ 否
    ↓
    <NoPermission />

    │ 是
    ↓
    ┌─────────────────┐
    │ isLoading? │
    └────────┬────────┘
    │ 是
    ↓
    <Spinner />

    │ 否
    ↓
    ┌─────────────────┐
    │ error? │
    └────────┬────────┘
    │ 是
    ↓
    <Error />

    │ 否
    ↓
    ┌─────────────────┐
    │ isEmpty? │
    └────────┬────────┘
    │ 是
    ↓
    <Empty />

    │ 否
    ↓
    <Content />

    4.3 多状态组合的常见错误

    错误一:状态之间互相矛盾。

    // TSX (React Native)
    // 错误:isLoading 和 error 同时为 true 时,显示什么?
    function View({ isLoading, error, data }) {
    return (
    <View>
    {isLoading && <Spinner />}
    {error && <Error message={error} />}
    {!isLoading && !error && <Content data={data} />}
    </View>
    )
    }

    问题: 如果 isLoading 和 error 同时为 true,会同时显示 Spinner 和 Error。

    修正: 用 if/else 表达优先级。

    // TSX (React Native)
    function View({ isLoading, error, data }) {
    if (isLoading) return <Spinner />
    if (error) return <Error message={error} />
    return <Content data={data} />
    }

    错误二:条件判断过于复杂。

    // TSX (React Native)
    // 错误:条件判断过于复杂
    function View({ user, post, comment }) {
    return (
    <View>
    {user && user.isLoggedIn && post && post.isPublished && !comment && (
    <Content />
    )}
    </View>
    )
    }

    修正: 提取为派生数据。

    // TSX (React Native)
    function View({ user, post, comment }) {
    // 派生数据:把复杂条件提取出来
    const shouldShowContent =
    user?.isLoggedIn &&
    post?.isPublished &&
    !comment

    return (
    <View>
    {shouldShowContent && <Content />}
    </View>
    )
    }

    错误三:嵌套三元过深。

    // TSX (React Native)
    // 错误:嵌套三元过深
    function View({ status }) {
    return (
    <Text>
    {status === 'loading' ? '加载中' :
    status === 'error' ? '错误' :
    status === 'empty' ? '暂无数据' :
    '正常'}
    </Text>
    )
    }

    修正: 用 if/else 或 switch。

    // TSX (React Native)
    function View({ status }) {
    if (status === 'loading') return <Text>加载中</Text>
    if (status === 'error') return <Text>错误</Text>
    if (status === 'empty') return <Text>暂无数据</Text>
    return <Text>正常</Text>
    }

    4.4 各框架多条件组合对照

    React Native:

    // TSX (React Native)
    if (isLoading) return <Spinner />
    if (error) return <Error />
    return <Content />

    SwiftUI:

    // Swift (SwiftUI)
    if isLoading {
    Spinner()
    } else if let error = error {
    ErrorView(message: error)
    } else {
    Content()
    }

    ArkUI:

    // ArkTS (ArkUI)
    if (this.isLoading) {
    Spinner()
    } else if (this.error) {
    ErrorView({ message: this.error })
    } else {
    Content()
    }

    Compose:

    // Kotlin (Jetpack Compose)
    when {
    isLoading -> Spinner()
    error != null -> ErrorView(message = error)
    else -> Content()
    }

    Flutter:

    // Dart (Flutter)
    if (isLoading) {
    return Spinner();
    } else if (error != null) {
    return ErrorView(message: error!);
    } else {
    return Content();
    }


    五、条件渲染与 key

    5.1 同一位置的不同分支需要 key

    问题: 同一位置的不同分支,框架可能认为是同一个元素,导致状态错乱。

    // TSX (React Native)
    // 问题:同一位置切换分支,状态可能错乱
    function View({ isOn }) {
    return (
    <View>
    {isOn ? <OnComponent /> : <OffComponent />}
    </View>
    )
    }

    问题分析:

    isOn: true → false

    框架对比:
    旧:位置 0 是 <OnComponent />
    新:位置 0 是 <OffComponent />
    → 框架认为:位置 0 的元素类型变了 → 销毁旧的,创建新的
    → 状态正确

    但如果两个分支都是同类型元素:
    旧:位置 0 是 <Text>开</Text>
    新:位置 0 是 <Text>关</Text>
    → 框架认为:位置 0 的 Text 内容变了 → 复用,更新内容
    → 如果 Text 有内部状态,状态可能错乱

    修正: 给不同分支加 key。

    // TSX (React Native)
    function View({ isOn }) {
    return (
    <View>
    {isOn
    ? <OnComponent key="on" />
    : <OffComponent key="off" />
    }
    </View>
    )
    }

    5.2 key 在条件渲染中的应用

    示例:切换登录/注册表单。

    // TSX (React Native)
    function AuthForm({ mode }) {
    return (
    <View>
    {mode === 'login' ? (
    <LoginForm key="login" />
    ) : (
    <RegisterForm key="register" />
    )}
    </View>
    )
    }

    加 key 的效果: 切换模式时,框架销毁旧表单,创建新表单。表单的输入框状态被清空,符合用户预期。

    不加 key 的效果: 如果 LoginForm 和 RegisterForm 结构相似,框架可能复用元素,输入框状态被保留,不符合用户预期。

    5.3 各框架条件渲染的 key 对照

    React Native:

    // TSX (React Native)
    {mode === 'login' ? <LoginForm key="login" /> : <RegisterForm key="register" />}

    SwiftUI:

    // Swift (SwiftUI)
    // SwiftUI 自动处理 if/else 分支的身份
    if mode == .login {
    LoginForm()
    } else {
    RegisterForm()
    }
    // 如需强制重建,用 .id()
    if mode == .login {
    LoginForm().id("login")
    } else {
    RegisterForm().id("register")
    }

    ArkUI:

    // ArkTS (ArkUI)
    // ArkUI 的 if/else 自动处理分支
    if (this.mode === 'login') {
    LoginForm()
    } else {
    RegisterForm()
    }

    Compose:

    // Kotlin (Jetpack Compose)
    // Compose 的 if/else 自动处理分支
    if (mode == "login") {
    LoginForm()
    } else {
    RegisterForm()
    }
    // 如需强制重建,用 key()
    key(mode) {
    if (mode == "login") {
    LoginForm()
    } else {
    RegisterForm()
    }
    }

    Flutter:

    // Dart (Flutter)
    // Flutter 需要手动指定 key
    mode == 'login'
    ? LoginForm(key: ValueKey('login'))
    : RegisterForm(key: ValueKey('register'))

    5.4 key 与条件渲染的配合

    原则:

    1. 如果两个分支是不同类型的组件 → 框架自动区分,不需要 key
    2. 如果两个分支是相同类型的组件 → 需要 key 区分
    3. 如果希望切换时状态清空 → 加 key
    4. 如果希望切换时状态保留 → 不加 key

    示例:

    // TSX (React Native)
    // 场景:同一位置切换两个 Text 组件
    function View({ isOn }) {
    return (
    <View>
    {isOn
    ? <Text key="on">开</Text>
    : <Text key="off">关</Text>
    }
    </View>
    )
    }

    分析: 两个分支都是 Text 组件。如果不加 key,框架认为位置 0 的 Text 内容变了,复用元素。如果 Text 有内部状态(比如动画),状态可能错乱。加 key 后,框架知道这是两个不同的元素,正确销毁和创建。


    六、条件渲染的性能考量

    6.1 避免不必要的重渲染

    问题: 条件渲染的组件在状态变化时重新渲染。

    // TSX (React Native)
    // 问题:count 变化,ComplexView 重新渲染
    function View({ count }) {
    return (
    <View>
    <Text>Count: {count}</Text>
    {count > 0 && <ComplexView />}
    </View>
    )
    }

    分析: count 从 1 变到 2,count > 0 仍然为 true,但 ComplexView 重新渲染了。

    修正: 使用 React.memo 或 EquatableView。

    // TSX (React Native)
    const ComplexView = React.memo(function ComplexView() {
    // 复杂布局
    return <View>…</View>
    })

    function View({ count }) {
    return (
    <View>
    <Text>Count: {count}</Text>
    {count > 0 && <ComplexView />}
    </View>
    )
    }

    6.2 条件判断的开销

    问题: 条件判断过于复杂,每次渲染都重新计算。

    // TSX (React Native)
    // 问题:复杂条件每次渲染都重新计算
    function View({ items, filter, sort }) {
    return (
    <View>
    {items
    .filter(item => item.category === filter)
    .sort((a, b) => a[sort] – b[sort])
    .length > 0 && (
    <Content />
    )}
    </View>
    )
    }

    修正: 用 useMemo 缓存计算结果。

    // TSX (React Native)
    function View({ items, filter, sort }) {
    const filteredItems = useMemo(() => {
    return items
    .filter(item => item.category === filter)
    .sort((a, b) => a[sort] – b[sort])
    }, [items, filter, sort])

    return (
    <View>
    {filteredItems.length > 0 && <Content />}
    </View>
    )
    }

    6.3 条件渲染的粒度

    问题: 条件渲染粒度过粗,导致不必要的重渲染。

    // TSX (React Native)
    // 问题:整个 View 重新渲染
    function View({ isLoading, data }) {
    if (isLoading) return <Spinner />
    return (
    <View>
    <Header />
    <Content data={data} />
    <Footer />
    </View>
    )
    }

    修正: 把条件渲染粒度细化到需要的部分。

    // TSX (React Native)
    function View({ isLoading, data }) {
    return (
    <View>
    <Header />
    {isLoading ? <Spinner /> : <Content data={data} />}
    <Footer />
    </View>
    )
    }

    分析: Header 和 Footer 不依赖 isLoading,条件渲染粒度细化后,isLoading 变化只影响 Content 部分。

    6.4 条件渲染的性能检查清单

    条件渲染性能检查清单:

    1. 条件渲染的组件是否使用了 memo/equatable?
    → 避免不必要的重渲染

    2. 条件判断是否过于复杂?
    → 用 useMemo 缓存计算结果

    3. 条件渲染的粒度是否过粗?
    → 细化到需要的部分

    4. 条件渲染的分支是否有内部状态?
    → 有 → 用 key 区分
    → 没有 → 不需要 key

    5. 条件渲染的分支是否频繁切换?
    → 频繁切换 → 用动画过渡
    → 不频繁 → 直接切换

    6.5 各框架条件渲染性能对照

    框架 性能优化 API 关键差异
    React Native React.memo、useMemo 需要手动优化
    SwiftUI EquatableView、@ViewBuilder 声明式,自动优化
    ArkUI @Reusable 需要手动优化
    Compose remember、derivedStateOf 自动跳过不变的重组
    Flutter const、RepaintBoundary 需要手动优化


    七、常见误区

    7.1 逻辑与的假值陷阱

    React Native:

    // TSX (React Native)
    // 错误:count 为 0 时,会渲染 0
    {count && <Text>Count: {count}</Text>}

    // 正确:用明确的条件
    {count > 0 && <Text>Count: {count}</Text>}

    问题: 在 JavaScript 中,0 && 返回 0,React 会渲染 0。

    修正: 用明确的条件判断。

    // TSX (React Native)
    // 正确:用三元或明确的条件
    {count > 0 ? <Text>Count: {count}</Text> : null}

    7.2 条件渲染的 key 遗漏

    // TSX (React Native)
    // 错误:切换分支时状态错乱
    {isOn ? <Text>开</Text> : <Text>关</Text>}

    // 正确:加 key 区分
    {isOn ? <Text key="on">开</Text> : <Text key="off">关</Text>}

    7.3 嵌套三元过深

    // TSX (React Native)
    // 错误:嵌套三元过深
    {status === 'loading' ? <Spinner /> :
    status === 'error' ? <Error /> :
    status === 'empty' ? <Empty /> :
    <Content />}

    // 正确:用 if/else 或提取函数
    function renderContent() {
    if (status === 'loading') return <Spinner />
    if (status === 'error') return <Error />
    if (status === 'empty') return <Empty />
    return <Content />
    }

    7.4 条件判断放在错误的位置

    // TSX (React Native)
    // 错误:条件判断放在子组件内部
    function Parent({ isLoading }) {
    return <Child isLoading={isLoading} />
    }

    function Child({ isLoading }) {
    if (isLoading) return <Spinner />
    return <Content />
    }

    // 正确:条件判断放在父组件
    function Parent({ isLoading }) {
    if (isLoading) return <Spinner />
    return <Child />
    }

    function Child() {
    return <Content />
    }

    分析: 如果 Child 有其他 props 变化,即使 isLoading 没变,Child 也会重新渲染。把条件判断放在父组件,isLoading 为 true 时不渲染 Child,避免不必要的重渲染。

    7.5 条件渲染与列表 key 混淆

    // TSX (React Native)
    // 错误:把条件渲染的 key 和列表的 key 混淆
    {items.map(item => (
    item.isVisible && <Item key={item.id} {…item} />
    ))}

    // 问题:item.isVisible 为 false 时,返回 false,React 会渲染 false
    // 正确:用 filter
    {items.filter(item => item.isVisible).map(item => (
    <Item key={item.id} {…item} />
    ))}

    7.6 条件渲染的状态不同步

    // TSX (React Native)
    // 错误:条件渲染的状态不同步
    const [isOn, setIsOn] = useState(false)
    const [label, setLabel] = useState('关')

    const toggle = () => {
    setIsOn(!isOn)
    setLabel(isOn ? '关' : '开') // 容易漏
    }

    // 正确:单一数据源
    const [isOn, setIsOn] = useState(false)
    const label = isOn ? '开' : '关' // 派生数据

    7.7 条件渲染的默认分支遗漏

    // TSX (React Native)
    // 错误:没有默认分支,status 异常时什么都不显示
    function View({ status }) {
    if (status === 'loading') return <Spinner />
    if (status === 'error') return <Error />
    if (status === 'success') return <Content />
    // status 为其他值时,返回 undefined
    }

    // 正确:加默认分支
    function View({ status }) {
    if (status === 'loading') return <Spinner />
    if (status === 'error') return <Error />
    if (status === 'success') return <Content />
    return <Empty /> // 默认分支
    }


    八、习题(15 道)

    题目 1(选择题)

    条件渲染的本质是什么?

    A. 手动显示/隐藏控件
    B. 根据状态选择不同的 UI 描述
    C. 循环创建视图
    D. 命令式更新界面

    参考答案:B

    解读: 条件渲染是"根据状态选择不同的 UI 描述",是 视图 = 函数(状态) 在分支逻辑上的体现。A、D 是命令式思维,C 是列表渲染。


    题目 2(判断题)

    条件渲染中,同一位置的不同分支必须加 key 区分。

    参考答案:错误

    解读: 如果两个分支是不同类型的组件,框架自动区分,不需要 key。只有两个分支是相同类型组件,且希望切换时状态清空,才需要加 key。


    题目 3(填空题)

    条件渲染的三种基本写法是:、、______。

    参考答案:if/else;三元表达式;逻辑与

    解读: if/else 适合多分支互斥场景;三元适合两分支且适合内联;逻辑与适合条件为真时渲染,为假时不渲染。


    题目 4(简答题)

    多条件组合时,如何设计优先级?

    参考答案: 多条件组合时,渲染函数按优先级选择 UI 描述。优先级设计原则:

  • 权限态优先于内容态
  • 加载态优先于错误态
  • 错误态优先于正常态
  • 空态优先于正常态
  • 用 if/else 表达优先级,从上到下依次判断,第一个为 true 的分支被渲染。


    题目 5(代码分析题)

    下面代码有什么问题?如何修正?

    // TSX (React Native)
    function View({ isLoading, error, data }) {
    return (
    <View>
    {isLoading && <Spinner />}
    {error && <Error message={error} />}
    {!isLoading && !error && <Content data={data} />}
    </View>
    )
    }

    参考答案: 如果 isLoading 和 error 同时为 true,会同时显示 Spinner 和 Error,不符合预期。

    修正: 用 if/else 表达优先级。

    // TSX (React Native)
    function View({ isLoading, error, data }) {
    if (isLoading) return <Spinner />
    if (error) return <Error message={error} />
    return <Content data={data} />
    }


    题目 6(代码改错题)

    下面代码有什么问题?如何修正?

    // TSX (React Native)
    {count && <Text>Count: {count}</Text>}

    参考答案: 当 count 为 0 时,0 && 返回 0,React 会渲染 0。

    修正: 用明确的条件判断。

    // TSX (React Native)
    {count > 0 && <Text>Count: {count}</Text>}


    题目 7(跨框架对比题)

    写出 React Native、SwiftUI、Compose 中条件渲染的核心写法。

    参考答案:

    React Native:

    // TSX (React Native)
    if (isLoading) return <Spinner />
    if (error) return <Error />
    return <Content />

    SwiftUI:

    // Swift (SwiftUI)
    if isLoading {
    Spinner()
    } else if let error = error {
    ErrorView(message: error)
    } else {
    Content()
    }

    Compose:

    // Kotlin (Jetpack Compose)
    when {
    isLoading -> Spinner()
    error != null -> ErrorView(message = error)
    else -> Content()
    }

    解读: 三种框架的条件渲染本质相同——根据状态选择不同的 UI 描述。差异只在语法。


    题目 8(选择题)

    以下哪种写法适合"条件为真时渲染,为假时不渲染"?

    A. if/else
    B. 三元表达式
    C. 逻辑与
    D. switch

    参考答案:C

    解读: 逻辑与(a && b)适合条件为真时渲染,为假时不渲染。if/else 适合多分支互斥,三元适合两分支内联,switch 适合多分支。


    题目 9(判断题)

    条件渲染的组件在状态变化时一定会重新渲染。

    参考答案:错误

    解读: 条件渲染的组件在状态变化时不一定重新渲染。如果条件结果没变,且组件使用了 React.memo 或 EquatableView,组件可能跳过渲染。


    题目 10(填空题)

    SwiftUI 中 if/else 分支由 ______ 处理,不需要手动加 key。

    参考答案:@ViewBuilder

    解读: SwiftUI 的 @ViewBuilder 自动处理 if/else 分支的身份。如果需要强制重建,可以用 .id() 修饰符。


    题目 11(简答题)

    为什么条件渲染中,条件判断应该放在父组件而不是子组件?

    参考答案: 如果条件判断放在子组件,父组件的其他 props 变化时,子组件会重新渲染,即使条件没变。

    把条件判断放在父组件,条件为 true 时才渲染子组件,条件为 false 时不渲染子组件,避免不必要的重渲染。

    // 错误:条件判断放在子组件
    function Parent({ isLoading }) {
    return <Child isLoading={isLoading} />
    }

    // 正确:条件判断放在父组件
    function Parent({ isLoading }) {
    if (isLoading) return <Spinner />
    return <Child />
    }


    题目 12(代码分析题)

    下面代码有什么问题?如何修正?

    // TSX (React Native)
    function View({ items, filter, sort }) {
    return (
    <View>
    {items
    .filter(item => item.category === filter)
    .sort((a, b) => a[sort] – b[sort])
    .length > 0 && (
    <Content />
    )}
    </View>
    )
    }

    参考答案: 复杂条件每次渲染都重新计算,性能浪费。

    修正: 用 useMemo 缓存计算结果。

    // TSX (React Native)
    function View({ items, filter, sort }) {
    const filteredItems = useMemo(() => {
    return items
    .filter(item => item.category === filter)
    .sort((a, b) => a[sort] – b[sort])
    }, [items, filter, sort])

    return (
    <View>
    {filteredItems.length > 0 && <Content />}
    </View>
    )
    }


    题目 13(设计题)

    设计一个页面状态系统,要求:

    · 权限检查优先
    · 加载中显示 Spinner
    · 错误显示 Error
    · 空数据显示 Empty
    · 正常显示 Content

    写出状态设计和条件渲染逻辑。

    参考答案:

    // TSX (React Native)
    function Page({ isLoading, error, data, hasPermission, isEmpty }) {
    // 优先级:权限 > 加载 > 错误 > 空态 > 正常
    if (!hasPermission) return <NoPermission />
    if (isLoading) return <Spinner />
    if (error) return <Error message={error} />
    if (isEmpty) return <Empty />
    return <Content data={data} />
    }

    状态:isLoading、error、data、hasPermission、isEmpty
    条件渲染:按优先级从上到下判断

    解读: 用 if/else 表达优先级,从上到下依次判断,第一个为 true 的分支被渲染。这样保证任何时候只显示一个分支,不会出现多个状态同时显示的问题。


    题目 14(设计题)

    设计一个登录/注册切换表单,要求:

    · 点击"登录"显示登录表单
    · 点击"注册"显示注册表单
    · 切换时清空输入框

    写出状态设计、条件渲染、key 选择。

    参考答案:

    // TSX (React Native)
    function AuthPage() {
    const [mode, setMode] = useState('login')

    return (
    <View>
    <View>
    <Button title="登录" onPress={() => setMode('login')} />
    <Button title="注册" onPress={() => setMode('register')} />
    </View>

    {mode === 'login' ? (
    <LoginForm key="login" />
    ) : (
    <RegisterForm key="register" />
    )}
    </View>
    )
    }

    状态:mode
    条件渲染:三元表达式
    key:“login” 和 “register”

    解读: 加 key 后,切换模式时框架销毁旧表单,创建新表单。表单的输入框状态被清空,符合用户预期。不加 key 时,如果两个表单结构相似,框架可能复用元素,输入框状态被保留,不符合用户预期。


    题目 15(思考题)

    为什么说"条件渲染是 视图 = 函数(状态) 在分支逻辑上的体现"?

    参考答案: 视图 = 函数(状态) 的核心是"函数根据状态返回 UI 描述"。条件渲染把这个理念扩展到分支逻辑:

    函数(状态) = if (状态A) UI描述A
    else if (状态B) UI描述B
    else UI描述C

    条件渲染的三层含义:

  • 条件是状态的一部分:isLoading 是状态,error 是状态,data 是状态。
  • 分支是函数的自然表达:if/else 是函数的自然分支,不是"手动显示/隐藏控件"。
  • 状态变化触发分支重算:状态变化 → 函数重新执行 → 选择新的分支 → 返回新 UI 描述。
  • 从渲染管线角度:

    1. 状态变化(isLoading: true → false)
    2. 框架标记需要重新渲染
    3. 重新执行函数,根据新状态选择分支
    4. 得到新的 UI 描述
    5. 框架对比新旧描述,如果有差异就通知底层更新

    如果不用条件渲染,而是用命令式显示/隐藏:

    1. 状态变化
    2. 手动找到控件
    3. 手动改 display 属性
    4. 容易漏、容易错

    终极结论: 条件渲染是 视图 = 函数(状态) 在分支逻辑上的自然延伸。状态是分支条件,函数是分支选择器,视图是分支结果。理解这一点,就能写出清晰、可维护、无 bug 的条件渲染。


    九、下节预告

    第 6 课:副作用与生命周期——如何管理副作用、如何与外部系统交互、如何避免副作用陷阱。

    赞(0)
    未经允许不得转载:171主机测评 » 【现代声明式UI学与练】第5课 条件渲染与组合——如何根据状态渲染不同 UI、如何组合多个条件、如何避免条件渲染的陷阱
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址