Crie seu primeiro app
Construa o Pocket notes: busque em uma lista pequena, abra uma nota, edite em um modal nativo e receba o texto salvo. Um leave guard protege o rascunho. O Back retorna à mesma lista, com a busca preservada.
O tutorial usa rotas explícitas e APIs públicas. Pressupõe conhecimento básico de componentes Vue e um projeto NativeScript-Vue preparado. As notas são fictícias e ficam em memória; o exemplo não implementa persistência, autenticação ou backend.
1. Crie os arquivos
Depois de instalar o router, coloque estes sete arquivos completos no diretório src do app. Substitua a entrada e a raiz do starter pelos arquivos abaixo; mantenha seus recursos nativos e configuração de build. O código é incorporado diretamente da fonte executável do tutorial, então todos os idiomas usam a mesma implementação. Os textos da interface do exemplo estão em inglês.
src/
app.ts
App.vue
router.ts
notes.ts
pages/
Home.vue
Note.vue
Edit.vueO RouterView da raiz cria um Frame nativo. Cada componente de rota renderiza uma Page. Não envolva esse RouterView em outro Frame.
src/router.ts — / contém a lista, :id seleciona uma nota e modal: true dá ao editor seu próprio Frame. props: true passa os params da rota para a 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 — instale o router antes de iniciar o app.
import { createApp } from 'nativescript-vue'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).start()src/App.vue — mantenha o container de navegação montado.
<template>
<RouterView />
</template>2. Adicione os dados e a lista
src/notes.ts — dados compartilhados pela lista, detalhe e editor. Salvar altera esse array reativo; não grava em 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 pertence a esta página. Abrir uma nota cobre Home; não destrói a página nem sua busca.
<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>A lista com dois itens usa Buttons nativos comuns. Em um catálogo grande, use uma lista virtualizada. O TextField é uma busca simples para começar; busca nativa no header é uma evolução posterior.
3. Abra o detalhe e espere um resultado
src/pages/Note.vue — a prop id pertence àquela entrada da rota. pushForResult<string> espera pelo editor; fechar sem resultado devolve undefined. A verificação explícita também permite salvar uma string vazia intencionalmente.
<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>O detalhe de origem continua montado enquanto o modal está aberto. O tipo genérico descreve o contrato entre essas duas páginas; não valida dados arbitrários em runtime.
4. Proteja o rascunho e salve
src/pages/Edit.vue — o rascunho é local ao editor. O leave guard condicional pergunta apenas enquanto o texto difere da nota salva. Salvar permite a saída temporariamente e devolve o rascunho por 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 navegação passando pelo guard. Keep editing mantém o editor montado com o rascunho. Discard fecha sem alterar a nota. Salvar devolve o valor ao detalhe, onde o código da aplicação atualiza notes.
Leave guards tratam a remoção de uma página, não sua cobertura por outra. No iOS, um guard ativo altera quais gestos podem fechar ou remover a página. Consulte a matriz de plataformas dos guards em vez de presumir que todo gesto pode esperar um diálogo.
5. Execute e confira o fluxo completo
No projeto NativeScript-Vue que você criou:
ns run ios
# ou:
ns run android- Digite
coastem Find a note. Apenas Along the coast permanece. - Abra a nota e toque em Edit in a modal.
- Altere o texto. Toque em Cancel → Keep editing e confirme que o rascunho continua lá.
- Toque em Save note. O detalhe original mostra o texto devolvido.
- Use o Back nativo. Home ainda contém
coaste a lista continua filtrada. - Abra o editor de novo, altere o texto e escolha Cancel → Discard. O último texto salvo permanece inalterado.
Se preferir a versão do repositório, siga a preparação do checkout/recursos e execute npm run tutorial:ios ou npm run tutorial:android no diretório demo. Passe -- --device <id> para selecionar o aparelho. O tutorial usa o ID do demo, então sua instalação substitui a outra entrada nesse aparelho.
O que construir depois
Agora você tem uma pilha preservada, uma pilha modal, um rascunho local e um resultado tratado pela aplicação. Reveja o modelo interativo de pilhas para separar esses ciclos de vida.
Continue com o Fieldnotes para adicionar drawer, busca nativa da página, ações compartilhadas entre header/menu e foco explícito de navegação. Escolha abas ou split quando o app precisar de outra estrutura. Adicione armazenamento persistente/restauração quando o estado precisar sobreviver ao encerramento do processo.