Questa pagina è stata tradotta automaticamente e può contenere errori. Leggi l'originale in inglese

Documentazione

Componenti nativi

Un componente nativo è una vista che l’app host registra con un nome: una mappa, un’anteprima della fotocamera, una libreria di grafici, qualsiasi UIView su iOS, android.view.View su Android, NSView su macOS, gtk::Widget su Linux, oppure un disegno Direct2D o una HWND figlia su Windows. L’app NSH lo usa come qualsiasi altro elemento: un’app Rust lo crea con native(name) di nsh_core::prelude, un’app TSX lo dichiara con nativeComponent() di nsh. NSH lo dispone insieme al resto dell’app, gli invia le prop, consegna i suoi eventi agli handler e inoltra i comandi da un ref.

Poiché NSH esegue il rendering con le viste proprie del sistema, un componente nativo è semplicemente una sottovista in più nella stessa gerarchia: non esiste alcun problema di sovrapposizione dei livelli, e il componente viene ritagliato, scorre e riceve i tocchi come un <View>.

#Come si integra

  1. L’app host registra una factory con un nome, ad esempio "embed.badge", sul proprio NshAppView (iOS) o NshView (Android). Ogni vista ha il proprio registro; non esiste nulla di globale.
  2. L’app NSH dichiara un componente per quel nome con le prop e gli eventi che usa.
  3. Quando l’elemento viene creato, NSH crea una vista contenitore (decorata, dimensionata e sottoposta a hit-testing come qualsiasi vista), poi chiede alla factory un componente e inserisce la vista del componente nel contenitore, riempiendolo.
  4. Il componente riceve ogni prop subito dopo la sua creazione, poi riceve di nuovo ciascuna prop quando cambia.
  5. Il componente emette eventi e può comunicare la propria dimensione; l’app chiama i suoi comandi tramite un ref.

#Usare il componente nell’app

In Rust, native(name) crea l’elemento: .prop(name, value) gli assegna una prop (un serde_json::Value, oppure un signal o una closure che ne restituisce uno, inviata di nuovo quando cambia), .on_native_event(..) ascolta i suoi eventi come NativeEvent { name, data }, e i metodi di un element_ref() applicato con .element_ref(&r) inviano i suoi comandi. In TSX, nativeComponent() dichiara un componente per il nome con le prop e gli eventi che usa.

use nsh_core::prelude::*;
use serde_json::json;

const COLORS: [&str; 4] = ["#8250df", "#0969da", "#1a7f37", "#cf222e"];

#[component]
pub fn App() -> impl IntoElement {
    let taps = signal(0_u64);
    let badge = element_ref();
    view()
        .flex_1()
        .p(px(20.0))
        .pt_safe_area_plus(px(20.0))
        .pb_safe_area_plus(px(20.0))
        .gap(px(16.0))
        .bg(rgb(0xf6f8fa))
        .child(text("Rendered by nsh").text_size(px(22.0)).font_weight(FontWeight::BOLD))
        .child(
            // The host app's badge: a label it draws, sized to its text.
            native("embed.badge")
                .element_ref(&badge)
                .prop("text", move || match taps.get() {
                    0 => json!("Tap the native badge"),
                    n => json!(format!("Tapped {n}×")),
                })
                .prop("color", move || json!(COLORS[taps.get() as usize % COLORS.len()]))
                .on_native_event(move |e: &NativeEvent| {
                    if &*e.name == "tap" {
                        taps.set(e.data["count"].as_u64().unwrap_or(0));
                        badge.command("pulse", json!(null));
                    }
                })
                .self_start()
                .rounded(px(8.0))
                .overflow_hidden(),
        )
        .child(text(move || format!("Rust saw {} taps", taps.get())))
}

La versione TSX è examples/embedded/App.tsx, che viene eseguita da entrambi gli esempi di integrazione nel repository.

#nativeComponent(name, spec)

function nativeComponent<P extends object = {}, E extends object = {}>(
  name: string,
  spec: NativeComponentSpec<P, E>,
): (props: NativeComponentProps<P, E>) => JSX.Element;
ParametroTipoDescrizione
namestringIl nome registrato dall’app host. Usa un prefisso tuo ("maps.view", "acme.chart") in modo che i nomi restino univoci.
spec.propsreadonly (keyof P & string)[]Le prop del componente. Solo le prop elencate qui vengono inviate al lato nativo.
spec.eventsreadonly (keyof E & string)[]Gli eventi che emette. Facoltativo.

I parametri di tipo descrivono le prop (P) e il payload di ciascun evento (E). Il componente restituito accetta:

  • le prop del componente P;
  • un handler per evento: l’evento tap è gestito da onTap, regionChange da onRegionChange (la prima lettera viene messa in maiuscolo e viene anteposto on);
  • tutto ciò che accetta un <View> tranne i children: style, class / classList (Tailwind), layout, prop di accessibilità, testID, onLayout, ref. Queste appartengono al contenitore che NSH disegna, non al componente.

Un componente nativo è una foglia: non accetta children.

#Prop

Ogni prop propria viene inviata come JSON, quindi il suo valore può essere qualsiasi valore JSON: stringhe, numeri, booleani, null, array e oggetti semplici. Le prop sono reattive come tutte le altre: quando la closure di una prop Rust (.prop("text", move || json!(label.get()))) o un text={label()} TSX legge un signal, il nuovo valore viene inviato quando il signal cambia, e solo allora.

Cosa succede nell’appCosa riceve il componente
L’elemento viene creatoOgni prop propria impostata, subito dopo la creazione del componente
Il valore di una prop cambiaQuella prop con il nuovo valore
Una prop diventa undefinedQuella prop senza valore (nil in Swift, null in Java)
Una prop collegata a un valore animato si muovesetAnimatedNumber / setAnimatedColor, nei frame in cui cambia

Le prop trasportano valori JSON; mantieni l’interfaccia limitata ai dati (le funzioni e le istanze di classe restano nell’app).

#Prop animate

Una prop può anche essere un valore animato: un AnimatedValue, oppure un nodo derivato da uno di essi (interpolate, il scrollValue o il pullValue di una scroll view). In tal caso non viene inviata come JSON: NSH la collega, e al componente il valore viene consegnato sul thread UI a ogni frame in cui cambia, senza passare da JS, così può seguire un trascinamento o un pull to refresh.

const pull = createAnimatedValue(0);
<Scroll pullValue={pull} refreshIndicator={{ height: 72 }} refreshing={refreshing()} onRefresh={refresh}>
  <Spinner progress={pull.interpolate({ inputRange: [0, 72], outputRange: [0, 1] })} />
</Scroll>

Il componente riceve i numeri con setAnimatedNumber(name, value) e i colori con setAnimatedColor(name, argb) (0xAARRGGBB); per impostazione predefinita entrambi chiamano setProp con il numero, quindi un componente che non li implementa riceve comunque il valore. I componenti Rust implementano set_animated_number e set_animated_color, e il codice Rust collega una prop con native(name).bind_animated_prop("progress", &value). Assegnare di nuovo alla prop un valore semplice la scollega e invia quel valore. Il web non ha componenti registrati.

#Eventi

Il componente emette un evento per nome con un payload JSON facoltativo. NSH lo consegna al thread del motore e chiama l’handler corrispondente con il payload (in Rust, l’unico listener on_native_event con il name e il data dell’evento):

use serde_json::json;

native("acme.map")
    .prop("latitude", json!(52.52))
    .prop("longitude", json!(13.405))
    .on_native_event(move |e: &NativeEvent| match &*e.name {
        "regionChange" => center.set((
            e.data["latitude"].as_f64().unwrap_or_default(),
            e.data["longitude"].as_f64().unwrap_or_default(),
        )),
        "markerPress" => open(e.data["id"].as_str().unwrap_or_default()),
        _ => {}
    })
    .flex_1()

Gli eventi sono asincroni: il thread principale non attende mai il codice dell’app. Un componente che deve decidere qualcosa sul momento (se questo gesto può iniziare, se questa regione può cambiare) deve deciderlo in modo nativo, a partire dalle prop ricevute in precedenza.

#Comandi

Le chiamate imperative (zoom su un punto, avvio della riproduzione, impulso di un’animazione) passano dal ref dell’elemento (element_ref() in Rust, ref in TSX):

let map = element_ref();

native("acme.map")
    .element_ref(&map)
    .prop("latitude", json!(52.52))
    .prop("longitude", json!(13.405))
    .flex_1()

map.command("zoomTo", json!({ "level": 3 }));

command(name, args) (TSX: command(name, args?)) invia il nome e gli argomenti (qualsiasi valore JSON) al metodo command del componente. I comandi vengono applicati in ordine insieme alle modifiche effettuate prima di essi, quindi un comando emesso subito dopo una modifica di prop vede la nuova prop.

#Dimensionamento

Il contenitore viene disposto come un <View>:

  • Dove lo stile specifica una dimensione (width, height, flex: 1, alignSelf: "stretch", …), vince quella dimensione.
  • Dove lo stile lascia aperta una dimensione, il contenitore assume la dimensione comunicata dal componente con setSize, come un’immagine assume la propria dimensione naturale. Un componente che non comunica mai una dimensione misura 0 lungo le dimensioni aperte.

Comunica la dimensione in punti (iOS) o dp (Android) ogni volta che il contenuto cambia. Il badge qui sopra misura la propria etichetta ogni volta che text cambia e comunica la dimensione dell’etichetta più il padding, quindi alignSelf: "flex-start" rende il badge largo esattamente quanto il suo testo.

#Registrazione su iOS (Swift)

Implementa NshComponent e registra una factory sul NshAppView:

import NativeShell
import UIKit

/// `embed.badge`: props `text` and `color`; emits `tap`; command `pulse`.
final class Badge: NshComponent {
    let view = UIView()
    private let label = UILabel()
    private let context: NshComponentContext
    private var taps = 0

    init(_ context: NshComponentContext) {
        self.context = context
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 17)
        label.textAlignment = .center
        label.frame = view.bounds
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(label)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapped)))
    }

    func setProp(_ name: String, _ value: Any?) {
        switch name {
        case "text":
            label.text = value as? String
            let size = label.intrinsicContentSize
            context.setSize(CGSize(width: size.width + 32, height: size.height + 20))
        case "color":
            view.backgroundColor = (value as? String).flatMap(UIColor.init(hex:)) ?? .systemPurple
        default:
            break
        }
    }

    func command(_ name: String, _ args: Any?) {
        guard name == "pulse" else { return }
        view.alpha = 0.3
        UIView.animate(withDuration: 0.6) { self.view.alpha = 1 }
    }

    @objc private func tapped() {
        taps += 1
        context.emit("tap", ["count": taps])
    }
}

// Before loading the bundle:
app.register(component: "embed.badge") { context in Badge(context) }

(UIColor(hex:) è un piccolo helper dell’esempio, non fa parte di NativeShell.)

APIDescrizione
NshAppView.register(component:_:)Registra una factory (NshComponentContext) -> NshComponent con un nome. Crea un componente per ogni elemento creato da quel momento in poi. Registrare di nuovo un nome sostituisce la factory.
protocol NshComponentvar view: UIView (riempie il contenitore), setProp(_:_:), command(_:_:) (facoltativo: l’implementazione predefinita ignora i comandi). Isolato sul main actor.
setProp(_ name: String, _ value: Any?)I valori sono oggetti JSON di Foundation: String, NSNumber, [Any], [String: Any], NSNull; nil quando la prop è stata rimossa.
NshComponentContext.emit(_:_:)Emette un evento; i dati devono essere qualcosa che JSONSerialization accetta (oppure nil). Thread-safe.
NshComponentContext.setSize(_:)Comunica la dimensione propria del componente in punti. Thread-safe.

#Registrazione su Android (Java / Kotlin)

Implementa com.longbridge.nsh.NshComponent e registra una factory sul NshView:

import com.longbridge.nsh.NshComponent;
import com.longbridge.nsh.NshComponentContext;

final class Badge implements NshComponent {
    private final TextView label;
    private final NshComponentContext context;
    private final float density;
    private int taps;

    Badge(Context app, NshComponentContext context) {
        this.context = context;
        density = app.getResources().getDisplayMetrics().density;
        label = new TextView(app);
        label.setTextColor(Color.WHITE);
        label.setTextSize(17);
        label.setGravity(Gravity.CENTER);
        label.setOnClickListener(v -> context.emit("tap", Collections.singletonMap("count", ++taps)));
    }

    @Override
    public View view() {
        return label;
    }

    @Override
    public void setProp(String name, Object value) {
        if (name.equals("text")) {
            label.setText(value instanceof String ? (String) value : "");
            label.measure(0, 0);
            // Sizes are in dp.
            context.setSize(label.getMeasuredWidth() / density + 32, label.getMeasuredHeight() / density + 12);
        } else if (name.equals("color")) {
            label.setBackgroundColor(value instanceof String ? Color.parseColor((String) value) : 0xff8250df);
        }
    }

    @Override
    public void command(String name, Object args) {
        if (!name.equals("pulse")) return;
        label.setAlpha(0.3f);
        label.animate().alpha(1).setDuration(600).start();
    }
}

// Before loading the bundle:
nsh.registerComponent("embed.badge", context -> new Badge(this, context));

Lo stesso in Kotlin:

nsh.registerComponent("embed.badge") { context -> Badge(this, context) }
APIDescrizione
NshView.registerComponent(String name, NshComponent.Factory factory)Registra una factory con un nome; crea un componente per ogni elemento creato da quel momento in poi (thread UI).
interface NshComponentView view(), void setProp(String, Object), default void command(String, Object), default void destroy() (l’elemento non esiste più: è stato rimosso, oppure la sua app è stata fermata da NshView.destroy() o sostituita da un altro caricamento; chiamato una volta per componente). Chiamato sul thread UI.
Valori di setPropString, Number, Boolean, java.util.List, java.util.Map, oppure null quando rimossa (o JSON null).
NshComponentContext.emit(String event, Object data)Emette un evento; data con gli stessi tipi Java, oppure null. Qualsiasi thread.
NshComponentContext.setSize(float width, float height)Comunica la dimensione propria del componente in dp. Qualsiasi thread.

#Registrazione da Rust

Le app che incorporano il runtime iOS da Rust (nsh-platform-ios) registrano un NativeComponent su un AppView:

use std::rc::Rc;
use nsh_core::JsonValue;
use nsh_platform_ios::{AppView, ComponentContext, NativeComponent};
use objc2_ui_kit::UIView;

struct Badge {
    view: objc2::rc::Retained<UIView>,
    context: ComponentContext,
}

impl NativeComponent for Badge {
    fn view(&self) -> &UIView {
        &self.view
    }

    fn set_prop(&mut self, name: &str, value: Option<&JsonValue>) {
        // ... update the view, then report its size in points:
        self.context.set_size(120.0, 40.0);
    }

    fn command(&mut self, name: &str, args: &JsonValue) {
        // optional
    }
}

fn register(app: &AppView, make_view: impl Fn() -> objc2::rc::Retained<UIView> + 'static) {
    app.register_component(
        "embed.badge",
        Rc::new(move |context: ComponentContext| -> Box<dyn NativeComponent> {
            Box::new(Badge { view: make_view(), context })
        }),
    );
}

ComponentContext::emit(name, data) invia un evento (data è un serde_json::Value) e set_size(width, height) comunica la dimensione; entrambi funzionano da qualsiasi thread.

#Registrazione su macOS (Rust)

Su macOS il componente è un NSView, registrato sul AppView di nsh-platform-macos:

use std::rc::Rc;
use nsh_core::JsonValue;
use nsh_platform_macos::{
    objc2::{rc::Retained, MainThreadMarker},
    objc2_app_kit::{NSTextField, NSView},
    objc2_foundation::NSString,
    AppView, ComponentContext, NativeComponent,
};

struct Badge {
    label: Retained<NSTextField>,
    context: ComponentContext,
}

impl NativeComponent for Badge {
    fn view(&self) -> Retained<NSView> {
        Retained::into_super(Retained::into_super(self.label.clone()))
    }

    fn set_prop(&mut self, name: &str, value: Option<&JsonValue>) {
        if name == "text" {
            let text = value.and_then(|v| v.as_str()).unwrap_or("");
            self.label.setStringValue(&NSString::from_str(text));
            self.context.set_size(120.0, 40.0);
        }
    }

    fn command(&mut self, name: &str, args: &JsonValue) {
        // optional
    }
}

fn register(app: &AppView, mtm: MainThreadMarker) {
    app.register_component(
        "embed.badge",
        Rc::new(move |context: ComponentContext| -> Box<dyn NativeComponent> {
            Box::new(Badge { label: NSTextField::labelWithString(&NSString::new(), mtm), context })
        }),
    );
}

La vista riempie il contenitore che NSH dispone; ComponentContext::emit e set_size (punti) funzionano da qualsiasi thread, come su iOS. Registra prima di AppView::load; un registro appartiene a un solo AppView (e alle viste create dallo stesso AppOptions).

#Registrazione su Linux (Rust)

Su Linux il componente è un widget GTK, registrato sul AppView di nsh-platform-linux:

use std::rc::Rc;
use nsh_core::JsonValue;
use nsh_platform_linux::{gtk4 as gtk, AppView, ComponentContext, NativeComponent};
use gtk::prelude::*;

struct Badge {
    label: gtk::Label,
    context: ComponentContext,
}

impl NativeComponent for Badge {
    fn widget(&self) -> gtk::Widget {
        self.label.clone().upcast()
    }

    fn set_prop(&mut self, name: &str, value: Option<&JsonValue>) {
        if name == "text" {
            self.label.set_text(value.and_then(|v| v.as_str()).unwrap_or(""));
            self.context.set_size(120.0, 40.0);
        }
    }

    fn command(&mut self, name: &str, args: &JsonValue) {
        // optional
    }
}

fn register(app: &AppView) {
    app.register_component(
        "embed.badge",
        Rc::new(|context: ComponentContext| -> Box<dyn NativeComponent> {
            Box::new(Badge { label: gtk::Label::new(None), context })
        }),
    );
}

Il widget riempie il contenitore che NSH dispone; ComponentContext::emit e set_size (punti) funzionano da qualsiasi thread, come su iOS. Registra prima di AppView::load. Un registro appartiene a un solo AppView (e alle viste create dallo stesso AppOptions, di cui è il campo components).

#Registrazione su Windows (Rust)

Su Windows NSH disegna ogni vista da sé con Direct2D, quindi un componente o disegna nel proprio contenitore con Direct2D (draw) oppure porta una finestra propria (window: un controllo mappa, una vista di terze parti), registrata sul AppView di nsh-platform-windows:

use std::rc::Rc;
use nsh_core::JsonValue;
use nsh_platform_windows::{
    windows::Win32::Graphics::Direct2D::{
        Common::{D2D1_COLOR_F, D2D_RECT_F},
        ID2D1DeviceContext,
    },
    AppView, ComponentContext, NativeComponent,
};

/// A box filled with the color of its `on` prop.
struct Badge {
    on: bool,
    context: ComponentContext,
}

impl NativeComponent for Badge {
    fn draw(&self, dc: &ID2D1DeviceContext, size: (f32, f32)) {
        let color = if self.on {
            D2D1_COLOR_F { r: 0.2, g: 0.7, b: 0.3, a: 1.0 }
        } else {
            D2D1_COLOR_F { r: 0.6, g: 0.6, b: 0.6, a: 1.0 }
        };
        // SAFETY: Direct2D calls on the context nsh draws with.
        unsafe {
            if let Ok(brush) = dc.CreateSolidColorBrush(&color, None) {
                let rect = D2D_RECT_F { left: 0.0, top: 0.0, right: size.0, bottom: size.1 };
                dc.FillRectangle(&rect, &brush);
            }
        }
    }

    fn set_prop(&mut self, name: &str, value: Option<&JsonValue>) {
        if name == "on" {
            self.on = value.and_then(|v| v.as_bool()).unwrap_or(false);
            self.context.set_size(120.0, 40.0);
            self.context.redraw();
        }
    }

    fn command(&mut self, name: &str, args: &JsonValue) {
        // optional
    }
}

fn register(app: &AppView) {
    app.register_component(
        "embed.badge",
        Rc::new(|context: ComponentContext| -> Box<dyn NativeComponent> {
            Box::new(Badge { on: false, context })
        }),
    );
}

draw viene eseguito a ogni frame della finestra, nelle coordinate della vista (punti), ritagliato sui suoi limiti; chiama ComponentContext::redraw quando ciò che mostra cambia. Un componente disegnato viene coperto, ritagliato e trasformato come qualsiasi vista, e il suo contenitore riceve i tocchi. Un componente che restituisce una finestra figlia da window viene spostato, ritagliato e nascosto insieme alla sua vista, riceve il proprio input e si trova sopra a tutto ciò che NSH disegna (il contenuto NSH sovrapposto viene nascosto, come con le web view). ComponentContext::emit, set_size (punti) e redraw funzionano da qualsiasi thread. Registra prima di AppView::load; un registro appartiene a un solo AppView (e alle viste create dallo stesso AppOptions).

Un componente riceve anche la rotellina del mouse e gli scroll del touchpad su di esso con wheel (un visualizzatore di immagini li usa per lo zoom). Il Wheel che riceve contiene il puntatore (x, y, in punti del componente), la distanza in punti (dx, dy, positiva verso destra e verso il basso, come le scroll view spostano il proprio contenuto) e in tacche della rotellina (notches_x, notches_y, con frazioni dai touchpad di precisione e dalle rotelle ad alta risoluzione), se è arrivato a passi fini (precise) e i modificatori premuti (shift, ctrl, alt). Restituisce true per uno scroll che ha gestito; le scroll view attorno ad esso scorrono con il resto, e per impostazione predefinita con tutte. Uno scroll che una scroll view ha iniziato resta su quella vista. Ctrl + rotellina è anche il pinch di un touchpad di precisione, e Shift + rotellina arriva così come l’ha inviato la rotellina (dy, con shift):

use nsh_platform_windows::Wheel;

impl NativeComponent for ImageView {
    // draw, set_prop, ...

    fn wheel(&mut self, event: &Wheel) -> bool {
        if !event.ctrl {
            return false; // the scroll views around it scroll
        }
        // √2 per notch, about the pointer.
        let factor = 2f32.powf(-event.notches_y / 2.0);
        self.zoom_about(event.x, event.y, factor);
        self.context.redraw();
        true
    }
}

#Registrazione dal crate dell’app

Un’app con un crate Rust proprio (nsh.json "rust", Rust) registra i componenti nativi in quel crate, così funzionano negli host che NSH costruisce per l’app: l’host di sviluppo di nsh run e nsh test --target, e l’app nsh package che produce. Una funzione con #[native_components(<platform>)] nella radice del crate (lib.rs) li registra sul Components di quella piattaforma, e l’host la chiama per ogni vista che crea, come un’app host si registra sul proprio AppView:

use std::rc::Rc;
use nsh_core::prelude::*;

#[native_components(windows)]
fn components(components: &nsh_platform_windows::Components) {
    use nsh_platform_windows::{ComponentContext, NativeComponent};
    components.register(
        "embed.badge",
        Rc::new(|context: ComponentContext| -> Box<dyn NativeComponent> {
            Box::new(Badge { on: false, context })
        }),
    );
}

La piattaforma è windows, macos, linux o ios, e i componenti sono quelli di nsh_platform_windows, nsh_platform_macos, nsh_platform_linux o nsh_platform_ios (le sezioni precedenti). La funzione viene compilata solo sulla propria piattaforma, quindi un crate ne ha una per ogni piattaforma supportata, ciascuna che registra gli stessi nomi, e dipende dal crate di ciascuna piattaforma solo per quella piattaforma, alla versione e dalla sorgente degli altri crate NSH (nsh-core e gli altri; la CLI verifica che coincidano tutti):

[target.'cfg(windows)'.dependencies]
nsh-platform-windows = { version = "0.3", default-features = false }

[target.'cfg(target_os = "macos")'.dependencies]
nsh-platform-macos = { version = "0.3", default-features = false }

L’host registra i componenti del crate quando crea ciascuna vista, prima del codice che detiene la vista e registra i propri: un nome registrato sulla vista dopo di ciò (AppView::register_component) sostituisce quello del crate.

#Regole e limiti

  • Registra prima di caricare. Le factory si applicano agli elementi creati dopo la registrazione. Registra ogni nome prima di loadBundle / loadAssetBundle / run, in modo che il primo frame li trovi.
  • Il container gestisce decorazione e tocchi. Sfondo, bordi, raggio degli angoli, ombre, opacità, trasformazioni e overflow: "hidden" si applicano al container che NSH disegna. La tua view lo riempie e riceve i tocchi che cadono al suo interno.
  • Le capabilities non si applicano. Un componente registrato è codice della app host; le capabilities dichiarate da una app non limitano ciò che questo fa. Se incorpori app di cui non ti fidi, registra solo componenti che siano sicuri da usare per loro.
  • Limita l’interfaccia ai dati. Props, payload degli eventi e argomenti dei comandi attraversano il confine come testo JSON; invia identificatori e piccoli valori, non grandi blob, a ogni modifica.
  • Un solo bundle, entrambe le piattaforme. Registra gli stessi nomi con le stesse props ed eventi su iOS e Android, così un solo bundle funziona su entrambe.
  • I nomi sotto nsh/ appartengono a NSH. I componenti di NSH (la tab bar di sistema del TabBar di nsh/mobile) sono registrati sotto nsh/; dai ai tuoi un nome basato sulla tua app o azienda (embed.badge), perché una registrazione sotto nsh/ registra un avviso nel log e mantiene quella di NSH.

#Vedi anche