🖇 Generate local scopes in templates to compute data from other scoped slots
npm install vue-local-scope
When using scoped slots you often get access to data only in the template. But sometimes, you still need to apply transformation to that data, like calling a map
on an array or a filter
. Here is an example using Vue Promised to fetch information from an API endpoint:
<template>
<Promised :promise="usersPromise" v-slot="users">
<div>
<Autocomplete v-model="selectedUsers" :items="users.map(user => ({ value: user.id, label: user.name })) /">
<SelectedUsers :users="selectedUsers.map(user => users.find(u => u.id === user.value))" />
</div>
</Promised>
</template>
This approach has multiple issues:
- The
map
functions are called everytime the component renders even if the arrayusers
didn't change - If you need the mapped version of
users
in multiple places you will duplicate the code and calls ofmap
- There is too much code written in the template, it should definitely go in the
script
section
Vue Local Scope solve these problems with components and scoped slots.
Vue Local Scope exports two things:
- LocalScope: a functional component that pass down any prop into a scoped slot
- createLocalScope a function that returns a regular components with computed properties provided as a scoped slot
LocalScope doesn't generate any DOM node by itself, it renders whatever is passed as a scoped slot. It allows you to not duplicate your code but still present the first and third problem discussed in the Why section. You can pass any prop to it, usually applying some kind of transformation, like a map
or a reduce
, that transformation is only applied once everytime the template renders, and it allows you to have a local variable based on anything that exists in the template. This is useful for data coming from a v-slot
:
<template>
<div>
<DataProvider v-slot="items">
<LocalScope
:names="items.map(i => i.name)"
:ids="items.map(i => i.id)"
v-slot="{ names, ids }"
>
<!-- we are able to use the mapped names three times but we only run map once -->
<DisplayNames :names="names" @handle-change="updateNames(ids, names)" />
<p>{{ names }}</p>
<p>{{ ids }}</p>
</LocalScope>
</DataProvider>
</div>
</template>
<script>
import { LocalScope } from 'vue-local-scope'
export default {
// other options
components: { LocalScope },
}
</script>
Because LocalScope
is a functional component, you can return any amount of elements but it will call map
everytime something in the same template changes.
createLocalScope
is a function that generates a component to hold computed properties and provide them in a scoped slot. It is less convenient than LocalScope but because it generates a stateful component it benefits from caching in computed properties. It also exposes the data through a scoped slot:
<template>
<div>
<!-- Here we are intentionally using the same variable name `others` to shadow the variable inside NamesAndIdsScope -->
<DataProvider v-slot="{ items, others }">
<NamesAndIdsScope :items="items" :others="others" v-slot="{ name, ids, others }">
<DisplayNames :names="names" @handle-change="updateNames(ids, names)" />
<p>{{ names }}</p>
<p>{{ ids }}</p>
<p>{{ others }}</p>
</NamesAndIdsScope>
</DataProvider>
</div>
</template>
<script>
import { createLocalScope } from 'vue-local-scope'
const NamesAndIdsScope = createLocalScope({
names: ({ items }) => items.map(i => i.name),
ids: ({ items }) => items.map(i => i.id),
// we don't need to transform items but we need it as a prop
items: false,
// we can also override a value directly
// others is a prop and will appear in the `v-slot` variable as `others`
others: ({ others }) => others.filter(o => !o.skip),
})
export default {
// other options
components: { NamesAndIdsScope },
}
</script>
In this case we do get the benefit from computed properties caching but NamesAndIdsScope
creates a root element to group the content under it.
computed
: object of transformations applied to propspropsOptions
optional object to definepropOptions
for each key incomputed