1
0

1.declarative-vs-imperative.md 8.4 KB


title: Declarative vs Imperative

description: Discover how TresJS transforms imperative Three.js code into declarative Vue components, making 3D development more intuitive and maintainable.

Understanding the Paradigm Shift

TresJS fundamentally changes how you create 3D scenes by transforming Three.js's imperative approach into a declarative, component-based system. This shift makes 3D development more intuitive for Vue developers while maintaining the full power of Three.js.

Imperative Three.js Approach

In traditional Three.js development, you write imperative code - explicit instructions telling the computer exactly what to do step by step:

import * as THREE from 'three'

// Create scene, camera, and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

// Create geometry and material
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00FF00 })
const cube = new THREE.Mesh(geometry, material)

// Add to scene and position
scene.add(cube)
camera.position.z = 5

// Render loop
function animate() {
  requestAnimationFrame(animate)
  cube.rotation.x += 0.01
  cube.rotation.y += 0.01
  renderer.render(scene, camera)
}
animate()

Challenges with Imperative Code

  • Manual State Management: You must manually track and update object states.
  • Complex Cleanup: Requires explicit disposal of geometries, materials, and resources.
  • Verbose Setup: Lots of boilerplate code for basic scenes.
  • Hard to Maintain: Difficult to modify or extend as complexity grows.

Declarative TresJS Approach

TresJS transforms this into declarative code - you describe what you want, not how to achieve it:

<script setup lang="ts">
import { ref } from 'vue'

const cubeRef = ref()

const { onBeforeRender } = useLoop()
function onLoop() {
  if (cubeRef.value) {
    cubeRef.value.rotation.x += 0.01
    cubeRef.value.rotation.y += 0.01
  }
}
</script>

<template>
  <TresCanvas @loop="onLoop">
    <TresPerspectiveCamera :position="[0, 0, 5]" />
    <TresMesh ref="cubeRef">
      <TresBoxGeometry />
      <TresMeshBasicMaterial color="green" />
    </TresMesh>
  </TresCanvas>
</template>

Benefits of Declarative Code

  • Reactive State Management: Vue's reactivity automatically handles updates.
  • Automatic Cleanup: TresJS manages resource disposal automatically.
  • Intuitive Syntax: HTML-like template syntax, which is familiar to Vue developers.
  • Easier Maintenance: The component structure makes your code more modular and reusable.

Side-by-Side Comparison

Let's compare how common 3D operations are handled in both approaches:

Creating a Scene with Lighting

::code-group

// Setup scene, camera, renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

// Add lights
const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
scene.add(ambientLight)

const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1)
directionalLight.position.set(5, 5, 5)
scene.add(directionalLight)

// Create and add objects
const geometry = new THREE.SphereGeometry(1, 32, 32)
const material = new THREE.MeshStandardMaterial({ color: 0xFF6B35 })
const sphere = new THREE.Mesh(geometry, material)
scene.add(sphere)

// Render loop
function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}
animate()
<script setup lang="ts">
import { ref } from 'vue'
</script>

<template>
  <TresCanvas @loop="onLoop">
    <TresPerspectiveCamera :position="[0, 0, 5]" />

    <!-- Lighting -->
    <TresAmbientLight :intensity="0.5" />
    <TresDirectionalLight :position="[5, 5, 5]" />

    <!-- 3D Object -->
    <TresMesh>
      <TresSphereGeometry />
      <TresMeshStandardMaterial color="#ff6b35" />
    </TresMesh>
  </TresCanvas>
</template>

::

Reactive Property Updates

::code-group

let currentColor = 0xFF0000
let sphere

function changeColor() {
  currentColor = currentColor === 0xFF0000 ? 0x00FF00 : 0xFF0000
  sphere.material.color.setHex(currentColor)
}

// Manual event handling
document.addEventListener('click', changeColor)
<script setup lang="ts">
import { ref } from 'vue'

const isRed = ref(true)
const color = computed(() => isRed.value ? '#ff0000' : '#00ff00')

const toggleColor = () => {
  isRed.value = !isRed.value
}
</script>

<template>
  <TresMesh @click="toggleColor">
    <TresSphereGeometry />
    <TresMeshStandardMaterial :color="color" />
  </TresMesh>
</template>

::

Why Declarative is Better for 3D

1. Predictable State Management

Vue's reactivity system ensures that 3D objects always reflect the current state of your data.

2. Component Reusability

Create reusable 3D components that can be easily composed and customized.

<script setup lang="ts">
interface Props {
  position?: [number, number, number]
  color?: string
  radius?: number
}

const props = withDefaults(defineProps<Props>(), {
  position: () => [0, 0, 0],
  color: '#ff6b35',
  radius: 1
})
</script>

<template>
  <TresMesh :position="props.position">
    <TresSphereGeometry :args="[props.radius]" />
    <TresMeshStandardMaterial :color="props.color" />
  </TresMesh>
</template>

3. Easier Debugging

The Vue DevTools integration allows you to inspect 3D objects and their states visually. The Vue DevTools integration allows you to inspect 3D objects and their states visually.

4. Better Developer Experience

  • Type safety with TypeScript
  • IDE autocomplete and IntelliSense
  • Hot module replacement during development (almost every time)
  • Hot module replacement during development (almost every time)

    Best of Both Worlds

TresJS doesn't force you to choose between paradigms. You can combine both approaches when needed using the primitive component for direct Three.js integration:

<script setup lang="ts">
import { BoxGeometry, Mesh, MeshStandardMaterial } from 'three'

// Create Three.js objects imperatively when needed
const customGeometry = new BoxGeometry(2, 2, 2)
const customMaterial = new MeshStandardMaterial({
  color: 0x00FF00,
  metalness: 0.5,
  roughness: 0.1
})
const customMesh = new Mesh(customGeometry, customMaterial)
</script>

<template>
  <TresCanvas>
    <TresPerspectiveCamera :position="[0, 0, 5]" />
    <TresAmbientLight />
    <TresDirectionalLight :position="[5, 5, 5]" />

    <!-- Declarative approach -->
    <TresMesh :position="[-2, 0, 0]">
      <TresBoxGeometry />
      <TresMeshStandardMaterial color="red" />
    </TresMesh>

    <!-- Imperative approach via primitive -->
    <primitive :object="customMesh" :position="[2, 0, 0]" />
  </TresCanvas>
</template>

::tip This hybrid approach is particularly useful when integrating existing Three.js code or when you need the full power of Three.js for complex operations. Learn more about this in our Primitives guide. ::

Key Takeaways

:::card-group ::card{title="Declarative Benefits" icon="i-lucide-lightbulb"} Write what you want, not how to achieve it. Let TresJS handle the complex Three.js operations. ::

::card{title="Reactive by Design" icon="i-lucide-refresh-cw"} Leverage Vue's reactivity system for automatic updates and seamless state management. ::

::card{title="Component-First" icon="i-lucide-layers"} Build reusable 3D components that can be composed and extended like any Vue component. ::

::card{title="Flexible Architecture" icon="i-lucide-settings"} Choose the right approach for each use case - declarative for most scenarios, imperative when needed. :: :::

The declarative approach in TresJS makes 3D development more accessible and maintainable while preserving the full power of Three.js underneath. This paradigm shift allows developers to focus on creating amazing 3D experiences rather than managing complex imperative code.