Custom Abstractions
A lot of the components you will find in the package
@threlte/extras are abstractions on
top of the <T> component. These abstractions provide
extra functionality like automatically invalidating the frame or providing
default values or extra props.
A common use case for custom abstractions is to create a component that is a
fixed entity in your Threlte app which you want to reuse in multiple places or
which exposes a specific behavior. As an example, let’s create a component that
is made up from multiple <T> components resembling a tile of some kind:
<script> import { T } from '@threlte/threlte' import { MathUtils } from 'three'
let { children } = $props()</script>
<T.Group> <!-- 2x2 Tile --> <T.Mesh rotation.x={-90 * MathUtils.DEG2RAD}> <T.PlaneGeometry args={[2, 2]} /> <T.MeshStandardMaterial /> </T.Mesh>
{@render children()}</T.Group>Let’s see what implementing that component looks like:
<script> import Tile from './Tile.svelte'</script>
<Tile />The <Tile> component is now available in the scene and can be reused as many
times as you want.
Now we’d like to assign a different position to every <Tile> in order to
position it in the scene. We can do that by passing a position prop to the
<Tile> component:
<script> import Tile from './Tile.svelte'</script>
<Tile position={[0, 0, 0]} /><Tile position={[2, 0, 0]} /><Tile position={[4, 0, 0]} />That doesn’t work yet. The component <Tile> internally needs to make use of
the position prop to set the position of its children. We can do that by
spreading rest on the <T.Group>
component at the root hierarchy of <Tile>:
<script> import { T } from '@threlte/threlte' import { MathUtils } from 'three'
let { children, ...props } = $props()</script>
<T.Group {...props}> <!-- 2x2 Floor --> <T.Mesh rotation.x={-90 * MathUtils.DEG2RAD}> <T.PlaneGeometry args={[2, 2]} /> <T.MeshStandardMaterial /> </T.Mesh>
{@render children()}</T.Group>The last thing we need to do is to add types to our custom abstraction so that
IDEs like VS Code can provide autocompletion and type checking. We will create a
types.ts file next to the Tile.svelte file and add the following content:
import type { Props } from '@threlte/core'import type { Group } from 'three'
export type TileProps = Props<Group>As of now it’s necessary to declare the props in a separate file.
If you declare them inside the <script> block, the Svelte compiler
flattens the resulting type, removing JSDoc comments and possibly
breaking it.
The generic type Props<Type> is a utility type that extracts all possible
props that a <T> component accepts – in this case <T.Group>. It also creates
a bindable ref prop that can be used to bind to the group created by the
<T.Group> component.
We need to import the TileProps type in our Tile.svelte file and make use of
the ref prop. For that we:
- Add the
lang="ts"attribute to the<script>block - Import the
TilePropstype - Add the bindable prop
refto the props - Bind
refto the bindable proprefof the root<T.Group>component - Pass
refto thechildren()block
<script lang="ts"> import { T } from '@threlte/threlte' import { MathUtils } from 'three' import type { TileProps } from './types'
let { children, ref = $bindable(), ...props }: TileProps = $props()</script>
<T.Group {...props} bind:ref> <!-- 2x2 Floor --> <T.Mesh rotation.x={-90 * MathUtils.DEG2RAD}> <T.PlaneGeometry args={[2, 2]} /> <T.MeshStandardMaterial /> </T.Mesh>
{@render children({ ref })}</T.Group>Now we can use the <Tile> component in our scene and get autocompletion and
type checking.
<script> import Tile from './Tile.svelte' import { TransformControls } from '@threlte/extras'</script>
<Tile position={[2, 0, 2]}> {#snippet children({ ref })} <TransformControls object={ref} /> {/snippet}</Tile>