Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update examples to use SFCs #2174

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 79 additions & 91 deletions packages/docs/guide/advanced/composition-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,84 @@

<VueSchoolLink
href="https://vueschool.io/lessons/router-and-the-composition-api"
title="Learn how to use Vue Router with the composition API"
title="Learn how to use Vue Router with the Composition API"
/>

The introduction of `setup` and Vue's [Composition API](https://vuejs.org/guide/extras/composition-api-faq.html), open up new possibilities but to be able to get the full potential out of Vue Router, we will need to use a few new functions to replace access to `this` and in-component navigation guards.
The introduction of Vue's [Composition API](https://vuejs.org/guide/extras/composition-api-faq.html) opened up new possibilities, but to be able to get the full potential out of Vue Router, we will need to use a few new functions to replace access to `this` and in-component navigation guards.

## Accessing the Router and current Route inside `setup`

Because we don't have access to `this` inside of `setup`, we cannot directly access `this.$router` or `this.$route` anymore. Instead we use the `useRouter` and `useRoute` functions:
Because we don't have access to `this` inside of `setup`, we cannot directly access `this.$router` or `this.$route`. Instead, we use the `useRouter` and `useRoute` composables:

```js
```vue
<script setup>
import { useRouter, useRoute } from 'vue-router'

export default {
setup() {
const router = useRouter()
const route = useRoute()

function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
...query,
},
})
}
},
const router = useRouter()
const route = useRoute()

function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
...query,
},
})
}
</script>
```

The `route` object is a reactive object, so any of its properties can be watched and you should **avoid watching the whole `route`** object. In most scenarios, you should directly watch the param you are expecting to change
The `route` object is a reactive object. In most scenarios, you should **avoid watching the whole `route`** object. Instead, you can directly watch the properties you are expecting to change:

```js
```vue
<script setup>
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'

export default {
setup() {
const route = useRoute()
const userData = ref()

// fetch the user information when params change
watch(
() => route.params.id,
async newId => {
userData.value = await fetchUser(newId)
}
)
},
}
const route = useRoute()
const userData = ref()

// fetch the user information when params change
watch(
() => route.params.id,
async newId => {
userData.value = await fetchUser(newId)
}
)
</script>
```

Note we still have access to `$router` and `$route` in templates, so there is no need to return `router` or `route` inside of `setup`.
Note we still have access to `$router` and `$route` in templates, so there's no need to use `useRouter` or `useRoute` if we only need those object in the template.

## Navigation Guards

While you can still use in-component navigation guards with a `setup` function, Vue Router exposes update and leave guards as Composition API functions:
Vue Router exposes update and leave guards as Composition API functions:

```js
```vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})

const userData = ref()

// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
// same as beforeRouteLeave option but with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})

const userData = ref()

// same as beforeRouteUpdate option but with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
</script>
```

Composition API guards can also be used in any component rendered by `<router-view>`, they don't have to be used directly on the route component like in-component guards.
Expand All @@ -94,40 +88,34 @@ Composition API guards can also be used in any component rendered by `<router-vi

Vue Router exposes the internal behavior of RouterLink as a composable. It accepts a reactive object like the props of `RouterLink` and exposes low-level properties to build your own `RouterLink` component or generate custom links:

```js
```vue
<script setup>
import { RouterLink, useLink } from 'vue-router'
import { computed } from 'vue'

export default {
name: 'AppLink',

props: {
// add @ts-ignore if using TypeScript
...RouterLink.props,
inactiveClass: String,
},

setup(props) {
const {
// the resolved route object
route,
// the href to use in a link
href,
// boolean ref indicating if the link is active
isActive,
// boolean ref indicating if the link is exactly active
isExactActive,
// function to navigate to the link
navigate
} = useLink(props)

const isExternalLink = computed(
() => typeof props.to === 'string' && props.to.startsWith('http')
)

return { isExternalLink, href, navigate, isActive }
},
}
const props = defineProps({
// add @ts-ignore if using TypeScript
...RouterLink.props,
inactiveClass: String,
})

const {
// the resolved route object
route,
// the href to use in a link
href,
// boolean ref indicating if the link is active
isActive,
// boolean ref indicating if the link is exactly active
isExactActive,
// function to navigate to the link
navigate
} = useLink(props)

const isExternalLink = computed(
() => typeof props.to === 'string' && props.to.startsWith('http')
)
</script>
```

Note that the RouterLink's `v-slot` gives access to the same properties as the `useLink` composable.
118 changes: 82 additions & 36 deletions packages/docs/guide/advanced/data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ Technically, both are valid choices - it ultimately depends on the user experien

## Fetching After Navigation

When using this approach, we navigate and render the incoming component immediately, and fetch data in the component's `created` hook. It gives us the opportunity to display a loading state while the data is being fetched over the network, and we can also handle loading differently for each view.
When using this approach, we navigate and render the incoming component immediately, and fetch data in the component itself. It gives us the opportunity to display a loading state while the data is being fetched over the network, and we can also handle loading differently for each view.

Let's assume we have a `Post` component that needs to fetch the data for a post based on `$route.params.id`:
Let's assume we have a `Post` component that needs to fetch the data for a post based on `route.params.id`:

```html
::: code-group

```vue [Composition API]
<template>
<div class="post">
<div v-if="loading" class="loading">Loading...</div>
Expand All @@ -27,9 +29,54 @@ Let's assume we have a `Post` component that needs to fetch the data for a post
</div>
</div>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { getPost } from './api.js'

const route = useRoute()

const loading = ref(false)
const post = ref(null)
const error = ref(null)

// watch the params of the route to fetch the data again
watch(() => route.params.id, fetchData, { immediate: true })

async function fetchData(id) {
error.value = post.value = null
loading.value = true

try {
// replace `getPost` with your data fetching util / API wrapper
post.value = await getPost(id)
} catch (err) {
error.value = err.toString()
} finally {
loading.value = false
}
}
</script>
```

```js
```vue [Options API]
<template>
<div class="post">
<div v-if="loading" class="loading">Loading...</div>

<div v-if="error" class="error">{{ error }}</div>

<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>

<script>
import { getPost } from './api.js'

export default {
data() {
return {
Expand All @@ -41,37 +88,37 @@ export default {
created() {
// watch the params of the route to fetch the data again
this.$watch(
() => this.$route.params,
() => {
this.fetchData()
},
() => this.$route.params.id,
this.fetchData,
// fetch the data when the view is created and the data is
// already being observed
{ immediate: true }
)
},
methods: {
fetchData() {
async fetchData(id) {
this.error = this.post = null
this.loading = true
// replace `getPost` with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {

try {
// replace `getPost` with your data fetching util / API wrapper
this.post = await getPost(id)
} catch (err) {
this.error = err.toString()
} finally {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
},
},
}
</script>
```

:::

## Fetching Before Navigation

With this approach we fetch the data before actually navigating to the new
route. We can perform the data fetching in the `beforeRouteEnter` guard in the incoming component, and only call `next` when the fetch is complete. The callback passed to `next` will be called **after the component is mounted**:
With this approach we fetch the data before actually navigating to the new route. We can perform the data fetching in the `beforeRouteEnter` guard in the incoming component, and only call `next` when the fetch is complete. The callback passed to `next` will be called **after the component is mounted**:

```js
export default {
Expand All @@ -81,29 +128,28 @@ export default {
error: null,
}
},
beforeRouteEnter(to, from, next) {
getPost(to.params.id, (err, post) => {
// `setData` is a method defined below
next(vm => vm.setData(err, post))
})
async beforeRouteEnter(to, from, next) {
try {
const post = await getPost(to.params.id)
// `setPost` is a method defined below
next(vm => vm.setPost(post))
} catch (err) {
// `setError` is a method defined below
next(vm => vm.setError(err))
}
},
// when route changes and this component is already rendered,
// the logic will be slightly different.
async beforeRouteUpdate(to, from) {
beforeRouteUpdate(to, from) {
this.post = null
try {
this.post = await getPost(to.params.id)
} catch (error) {
this.error = error.toString()
}
getPost(to.params.id).then(this.setPost).catch(this.setError)
},
methods: {
setData(error, post) {
if (error) {
this.error = error
} else {
this.post = post
}
setPost(post) {
this.post = post
},
setError(err) {
this.error = err.toString()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/guide/advanced/dynamic-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The page will still show the `Article` component, we need to manually call `rout

```js
router.addRoute({ path: '/about', component: About })
// we could also use this.$route or route = useRoute() (inside a setup)
// we could also use this.$route or useRoute()
router.replace(router.currentRoute.value.fullPath)
```

Expand Down
Loading