Crea tu primera aplicación
Construye Pocket notes: busca en una lista, abre una nota, edítala en un modal nativo y devuelve el texto guardado. Un guard de salida protege el borrador sin guardar. Al volver, la lista conserva su búsqueda.
Este tutorial usa rutas explícitas y APIs públicas. Presupone conocimientos básicos de componentes Vue de un solo archivo y un proyecto NativeScript-Vue preparado. Las notas son ficticias y se almacenan en memoria; el ejemplo no implementa persistencia, autenticación ni un backend.
1. Crea los archivos
Después de instalar el router, coloca estos siete archivos completos en el directorio src de la aplicación. Sustituye la entrada y la raíz del proyecto inicial por los archivos siguientes; conserva sus recursos nativos y su configuración de compilación. El código se incorpora directamente desde el tutorial ejecutable, por lo que todas las traducciones comparten la implementación. Los textos de la aplicación de ejemplo están en inglés.
src/
app.ts
App.vue
router.ts
notes.ts
pages/
Home.vue
Note.vue
Edit.vueEl RouterView de la raíz crea un Frame nativo. Cada componente de ruta renderiza una Page. No envuelvas este RouterView en otro Frame.
src/router.ts: / contiene la lista, :id selecciona una nota y modal: true da al editor su propio Frame. props: true pasa los parámetros de ruta a la página.
import { createRouter } from 'vue-router-ns'
export const router = createRouter({
routes: [
{ path: '/', component: () => import('./pages/Home.vue') },
{
path: '/notes/:id',
component: () => import('./pages/Note.vue'),
props: true,
},
{
path: '/notes/:id/edit',
component: () => import('./pages/Edit.vue'),
props: true,
modal: true,
},
],
})src/app.ts: instala el router antes de iniciar la aplicación.
import { createApp } from 'nativescript-vue'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).start()src/App.vue: mantén montado el host de navegación.
<template>
<RouterView />
</template>2. Añade los datos y la lista
src/notes.ts: datos compartidos por la lista, el detalle y el editor. Guardar modifica este array reactivo; no escribe en disco.
import { reactive } from 'nativescript-vue'
export const notes = reactive([
{ id: 'coast', title: 'Along the coast', body: 'A quiet path beside the sea.' },
{ id: 'forest', title: 'Into the forest', body: 'Follow the shade and slow down.' },
])src/pages/Home.vue: query pertenece a esta página. Abrir una nota cubre Home; no destruye la página ni su búsqueda.
<script setup lang="ts">
import { computed, ref } from 'nativescript-vue'
import { useRouter } from 'vue-router-ns'
import { notes } from '../notes'
const router = useRouter()
const query = ref('')
const filtered = computed(() =>
notes.filter((note) => note.title.toLowerCase().includes(query.value.toLowerCase())),
)
</script>
<template>
<Page>
<ActionBar title="Pocket notes" />
<StackLayout padding="24">
<Label text="Your first native flow" fontSize="26" textWrap="true" />
<TextField
v-model="query"
hint="Find a note"
accessibilityLabel="Find a note"
marginTop="16"
/>
<Button
v-for="note in filtered"
:key="note.id"
:text="note.title"
@tap="router.push(`/notes/${note.id}`)"
/>
<Label v-if="!filtered.length" text="No notes found." />
</StackLayout>
</Page>
</template>Esta lista de dos elementos usa componentes Button nativos. Para un catálogo grande, usa una lista virtualizada. TextField proporciona una búsqueda sencilla; la búsqueda nativa en el encabezado puede añadirse después.
3. Abre el detalle y espera un resultado
src/pages/Note.vue: la prop id pertenece a esta entrada de ruta. pushForResult<string> espera al editor; cerrar sin resultado devuelve undefined. La comprobación explícita permite guardar intencionadamente una cadena vacía.
<script setup lang="ts">
import { useRouter } from 'vue-router-ns'
import { notes } from '../notes'
const props = defineProps<{ id: string }>()
const router = useRouter()
const note = notes.find((item) => item.id === props.id)
async function edit() {
const result = await router.pushForResult<string>(`/notes/${props.id}/edit`)
if (note && result !== undefined) note.body = result
}
</script>
<template>
<Page>
<ActionBar :title="note?.title ?? 'Note not found'">
<NavigationButton
text="Back"
android.systemIcon="ic_menu_back"
@tap="router.back()"
/>
</ActionBar>
<StackLayout v-if="note" padding="24">
<Label :text="note.body" fontSize="22" textWrap="true" marginBottom="24" />
<Button text="Edit in a modal" @tap="edit" />
</StackLayout>
<Label v-else text="This note does not exist." margin="24" />
</Page>
</template>El detalle de origen sigue montado mientras el modal está abierto. El tipo genérico describe el contrato entre estas dos páginas; no valida datos arbitrarios en tiempo de ejecución.
4. Protege el borrador y guarda
src/pages/Edit.vue: el borrador pertenece al editor. El guard de salida solo pide confirmación cuando el borrador difiere de la nota guardada. Guardar permite temporalmente la salida y devuelve el borrador mediante back({ result }).
<script setup lang="ts">
import { computed, ref } from 'nativescript-vue'
import { Dialogs } from '@nativescript/core'
import { onBeforeRouteLeave, useRouter } from 'vue-router-ns'
import { notes } from '../notes'
const props = defineProps<{ id: string }>()
const router = useRouter()
const note = notes.find((item) => item.id === props.id)
const draft = ref(note?.body ?? '')
const saving = ref(false)
const dirty = computed(() => !!note && !saving.value && draft.value !== note.body)
onBeforeRouteLeave(
() =>
Dialogs.confirm({
title: 'Discard changes?',
message: 'Your unsaved text will be lost.',
okButtonText: 'Discard',
cancelButtonText: 'Keep editing',
}),
{ when: dirty },
)
async function save() {
if (!note) return
saving.value = true
try {
await router.back({ result: draft.value })
} finally {
saving.value = false
}
}
</script>
<template>
<Page>
<ActionBar title="Edit note">
<ActionItem text="Cancel" ios.position="left" @tap="router.back()" />
</ActionBar>
<StackLayout padding="24">
<TextView
v-model="draft"
accessibilityLabel="Note draft"
automationText="Note draft"
height="180"
fontSize="20"
/>
<Label :text="dirty ? 'Unsaved changes' : 'No unsaved changes'" marginTop="12" />
<Button text="Save note" :isEnabled="!!note && !saving" @tap="save" />
</StackLayout>
</Page>
</template>Cancel solicita salir a través del guard. Keep editing mantiene el editor montado con su borrador. Discard lo cierra sin cambiar la nota. Guardar devuelve el valor al detalle, donde el código de la aplicación actualiza notes.
Un guard de salida protege la eliminación de una página, no el hecho de cubrirla. En iOS, un guard activo cambia qué gestos pueden cerrar o retirar la página. Consulta la matriz de comportamiento por plataforma; no todos los gestos pueden esperar a un diálogo.
5. Ejecuta y comprueba el flujo completo
En el proyecto NativeScript-Vue que has creado:
ns run ios
# or:
ns run android- Escribe
coasten Find a note. Solo queda Along the coast. - Abre esa nota y pulsa Edit in a modal.
- Cambia el texto. Pulsa Cancel → Keep editing y comprueba que se conserva el borrador.
- Pulsa Save note. El detalle original muestra el texto devuelto.
- Vuelve con el control nativo. Home sigue mostrando
coasty la misma lista filtrada. - Abre otra vez el editor, cambia el texto y elige Cancel → Discard. El último texto guardado no cambia.
Si prefieres la versión del repositorio, sigue la preparación del checkout y los recursos y ejecuta npm run tutorial:ios o npm run tutorial:android desde demo. Pasa -- --device <id> para elegir un dispositivo. El tutorial comparte el identificador de aplicación de la demo; instalarlo sustituye la otra variante de la demo en ese dispositivo.
Qué construir después
Ahora tienes un stack retenido, otro modal, un borrador local de página y un resultado gestionado por la aplicación. Consulta el modelo interactivo para explorar sus ciclos de vida.
Continúa con Fieldnotes para añadir un drawer, búsqueda nativa perteneciente a cada página, acciones compartidas entre encabezados y menús, y gestión explícita del foco. Elige pestañas o navegación dividida cuando necesites otra estructura. Añade almacenamiento y restauración persistentes cuando el estado deba sobrevivir a la terminación del proceso.