इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

If / Each

If और Each तय करते हैं कि कौन-से एलिमेंट मौजूद रहेंगे: सशर्त और दोहराई जाने वाली सामग्री। ये अपना कोई नेटिव व्यू नहीं बनाते। नेटिव रेंडरर केवल वही अपडेट करता है जो बदला है: If शर्त पलटने पर ब्रांच बदल देता है, Each सूची को दोबारा बनाने के बजाय key के आधार पर पंक्तियाँ जोड़ता, खिसकाता और हटाता है। Rust में when(..), each(..) और dynamic(..) nsh_core::prelude में हैं; TSX में <If> और <Each> हैं, जो ऐसे ग्लोबल हैं जिन्हें import करने की ज़रूरत नहीं।

el-control-flow.tsx
el-control-flow.tsxLive
import { mount, signal, Text, View } from "nsh";
import { button, buttonText, focusRing, hoverFill, Inspector, Panel, size, Stage, ui, wide } from "./palette";

interface Todo {
  id: number;
  title: string;
  done: boolean;
}

let nextId = 4;

function App() {
  const todos = signal<Todo[]>([
    { id: 1, title: "Read the elements page", done: true },
    { id: 2, title: "Try <If> and <Each>", done: false },
    { id: 3, title: "Ship the app", done: false },
  ]);
  const showDone = signal(true);
  const visible = () => (showDone() ? todos() : todos().filter((t) => !t.done));
  const toggle = (id: number) => todos.update((list) => list.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
  const add = () => todos.update((list) => [...list, { id: nextId++, title: `Task ${nextId - 1}`, done: false }]);
  const remove = (id: number) => todos.update((list) => list.filter((t) => t.id !== id));
  const actions = () => (
    <View style={{ flexDirection: "row", gap: 8 }}>
      <View onPress={add} {...button("accent")}>
        <Text style={buttonText("accent")}>Add</Text>
      </View>
      <View onPress={() => showDone.set(!showDone())} {...button()}>
        <Text style={buttonText()}>{showDone() ? "Hide done" : "Show done"}</Text>
      </View>
    </View>
  );
  const list = () => (
    <If
      when={visible().length > 0}
      else={<Text style={{ color: ui().muted, textAlign: "center", marginTop: 40 }}>Nothing to show.</Text>}
    >
      <View style={{ gap: wide() ? 6 : 8 }}>
        <Each of={visible()} key={(t: Todo) => t.id}>
          {(todo, index) => (
            <View
              style={{
                ...ui().row,
                flexDirection: "row",
                alignItems: "center",
                gap: 12,
                ...(wide() ? { borderRadius: 8, height: size.row, paddingHorizontal: 12 } : { borderRadius: 12, padding: 12 }),
              }}
            >
              <View
                onPress={() => toggle(todo().id)}
                hitSlop={8}
                style={{
                  width: wide() ? 18 : 22,
                  height: wide() ? 18 : 22,
                  borderRadius: wide() ? 9 : 11,
                  borderWidth: 2,
                  borderColor: ui().accent,
                  backgroundColor: todo().done ? ui().accent : "transparent",
                }}
                hoverStyle={{ opacity: 0.88 }}
                focusStyle={focusRing()}
                activeStyle={{ opacity: 0.7 }}
              />
              <Text
                style={{
                  flex: 1,
                  fontSize: wide() ? size.label : undefined,
                  color: todo().done ? ui().faint : ui().text,
                  textDecorationLine: todo().done ? "line-through" : "none",
                }}
              >
                {index() + 1}. {todo().title}
              </Text>
              <If when={todo().done}>
                <View onPress={() => remove(todo().id)} hitSlop={8} hoverStyle={hoverFill()} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
                  <Text style={{ color: ui().down, fontSize: wide() ? 13 : undefined }}>Remove</Text>
                </View>
              </If>
            </View>
          )}
        </Each>
      </View>
    </If>
  );
  const counts = () => `${todos().filter((t) => t.done).length} of ${todos().length} done`;

  return (
    <View style={{ flex: 1, backgroundColor: ui().page }}>
      {() =>
        wide() ? (
          <View style={{ flex: 1, padding: 20, paddingTop: 24, gap: 14 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>if / each</Text>
            <Text style={{ color: ui().muted }}>Click a circle to finish a task; Remove shows on finished ones</Text>
            <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
              <Stage width={420}>
                {/* A fixed height, so the list grows downwards instead of moving. */}
                <View style={{ height: 360 }}>{list()}</View>
              </Stage>
              <Inspector>
                {actions()}
                <Panel title="todos · showDone">
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>{counts()}</Text>
                  <Text style={{ fontSize: 13, color: ui().text }}>
                    {showDone() ? "every task shown" : `${visible().length} not done shown`}
                  </Text>
                </Panel>
              </Inspector>
            </View>
          </View>
        ) : (
          <View style={{ flex: 1, width: "100%", maxWidth: 560, alignSelf: "center", padding: 20, paddingTop: 24, gap: 12 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>if / each</Text>
            {actions()}
            {list()}
            <Text style={{ fontSize: 12, color: ui().muted, fontVariant: ["tabular-nums"] }}>{counts()}</Text>
          </View>
        )
      }
    </View>
  );
}

mount(App);
use nsh_core::prelude::*;

view()
    .child(
        when(move || user.with(|u| u.is_some()), move || {
            text(move || format!("Hello, {}", user.with(|u| u.as_ref().map(|u| u.name.clone()).unwrap_or_default())))
        })
        .otherwise(|| text("Signed out")),
    )
    .child(each(todos, |t: &Todo| t.id, |todo, _index| text(move || todo.get().title)))

TSX में ये ग्लोबल हैं (स्कोप में उसी नाम की कोई बाइंडिंग हो तो वह इनकी जगह ले लेती है)। JSX एक्सप्रेशन के अंदर आप सामान्य JavaScript (cond ? a : b, items.map(…)) और Rust में dynamic(move || ..) अब भी इस्तेमाल कर सकते हैं: ये अपने एलिमेंट हर बार दोबारा बनाते हैं जब भी उनके पढ़े हुए मान बदलते हैं, जबकि If और Each (Rust में when और each) वे एलिमेंट बनाए रखते हैं जो बने रहते हैं।

#If

शर्त सही रहने तक अपनी ब्रांच रेंडर करता है, अन्यथा दूसरी ब्रांच (या कुछ नहीं)।

प्रॉपRustटाइपविवरण
whenwhen(cond, ..)Tशर्त: Rust में एक मान, एक सिग्नल या एक क्लोजर; TSX में उसकी truthiness।
else.otherwise(|| ..)JSX.Elementजब तक when सही नहीं होता, तब तक रेंडर होता है।
childrenwhen(.., || ..)JSX.Element | (value: () => NonNullable<T>) => JSX.Elementजब तक when सही रहता है, तब तक रेंडर होता है। TSX में, फ़ंक्शन को when के मान का एक accessor मिलता है।

शर्त जिस ब्रांच की ओर पलटती है, वह ब्रांच तभी बनती है, और जो बना था वह शर्त के दूसरी ओर पलटते ही हट जाता है। TSX में जब when एक truthy मान से दूसरे truthy मान पर जाता है (user() का एक ऑब्जेक्ट से दूसरे में बदलना), तो ब्रांच बनी रहती है और उसके अंदर के एक्सप्रेशन अपने आप अपडेट हो जाते हैं।

let loading = signal(true);

when(move || !loading.get(), || profile())
    .otherwise(|| text("Loading…").text_color(rgb(0x6e7781)))

TSX में, फ़ंक्शन चाइल्ड वह मान पढ़ता है जो when ने दिया, और उसके टाइप में null या undefined नहीं होता, इसलिए ब्रांच को ! की ज़रूरत नहीं पड़ती। Rust में, ब्रांच स्वयं सिग्नल को पढ़ती है, या dynamic मान को एलिमेंट में मैप करता है:

let user = signal(None::<User>);

dynamic(move || user.get().map(|u| text(u.name)))

#Each

सूची के हर आइटम के लिए एक पंक्ति रेंडर करता है।

प्रॉपRustटाइपविवरण
ofeach(items, ..)readonly any[] | null | undefinedआइटम: Rust में एक Vec, उसका एक सिग्नल या उसे लौटाने वाला क्लोजर। null और undefined कुछ रेंडर नहीं करते।
keyeach(.., |item| key, ..)(item: any) => unknownकिसी आइटम की पहचान। TSX में डिफ़ॉल्ट: आइटम स्वयं (ऑब्जेक्ट identity से, प्रिमिटिव मान से)।
childreneach(.., .., |item, index| row)(item: () => any, index: () => number) => JSX.Elementएक पंक्ति रेंडर करता है।

पंक्ति फ़ंक्शन को मान नहीं, accessors मिलते हैं: TSX में item() और index() को कॉल करें, Rust में item.get() और index.get() को (दोनों ReadSignal हैं)। आइटम बदलने पर, जिन पंक्तियों की key अब भी मौजूद है वे बनी रहती हैं (और स्थिति बदलने पर खिसका दी जाती हैं); उनका आइटम और इंडेक्स अपडेट होता है, और केवल उन्हें पढ़ने वाले एक्सप्रेशन दोबारा चलते हैं। नई key को नई पंक्तियाँ मिलती हैं; जिन पंक्तियों की key नहीं रही, वे हटा दी जाती हैं।

#[derive(Clone, PartialEq)]
struct Todo {
    id: u32,
    title: String,
    done: bool,
}

let todos = signal(Vec::<Todo>::new());

each(todos, |t: &Todo| t.id, |todo, index| {
    view()
        .flex_row()
        .gap_2()
        .child(text(move || format!("{}.", index.get() + 1)))
        .child(
            text(move || todo.get().title)
                .style_when(move || todo.get().done, |s| s.line_through()),
        )
})

#key चुनना

  • जिन आइटम को हर अपडेट पर नए ऑब्जेक्ट से बदला जाता है (immutable अपडेट, { ...t, done: true }) उन्हें key चाहिए, आमतौर पर एक id। इसके बिना हर नया ऑब्जेक्ट एक नई पंक्ति बन जाता है और उसके एलिमेंट दोबारा बनते हैं। Rust में हमेशा key देनी होती है।
  • TSX में, स्ट्रिंग या संख्याओं के ऐरे बिना key के भी काम करते हैं: उनकी तुलना मान से होती है।
  • हर पंक्ति का अपना owner होता है: पंक्ति में बनाए गए effects और cleanups पंक्ति हटने पर समाप्त हो जाते हैं।

#Each या list

Each हर पंक्ति बनाता है। दर्जनों या कुछ सौ आइटम के लिए, किसी view या scroll view में, यह सही है। लंबे या असीमित डेटा के लिए list इस्तेमाल करें: यह व्यूपोर्ट के पास की पंक्तियाँ बनाती है और उन्हें रीसायकल करती है।

एक्सप्रेशन कैसे अपडेट होते हैं, यह जानने के लिए JSX और कंपोनेंट तथा reactivity देखें।