Skip to content

Instantly share code, notes, and snippets.

@johnarban
Created October 3, 2025 23:05
Show Gist options
  • Save johnarban/a88478b1c0c96401cacacf541e645c61 to your computer and use it in GitHub Desktop.
Save johnarban/a88478b1c0c96401cacacf541e645c61 to your computer and use it in GitHub Desktop.
Testing Vue Reactivity with Classes
<script setup lang="ts">
import { ref, type Ref, reactive } from 'vue'
class Person {
name: string
age: number
called = 0;
constructor(name: string, age: number) {
this.name = name
this.age = age
this.startChanging()
// this will not cause a render
// setInterval(() => {
// this.name = this.name + '_'
//}, 500)
}
getOlder() {
this.age = this.age + 1
}
get twiceAge() {
return this.age * 2
}
get secondsPast() {
let i = 0
this.name.split('').forEach(c => c=='_' ? i++ : null)
return i / 2
}
changer() {
this.name = this.name + '_'
}
startChanging() {
this.called += 1
setInterval(() => {
this.changer()
}, 2000)
}
changer2() {
this.name = this.name + '+'
}
startChanging2() {
this.called += 1
setInterval(() => {
this.changer2()
}, 2000)
}
}
type LecturerLevel = 'Lecturer' | 'Research' | 'Professor' | 'Emeritus'
class Lecturer extends Person {
subject: string
level: LecturerLevel
constructor(name: string, age: number, subject: string, level: LecturerLevel) {
super(name, age)
this.subject = subject
this.level = level
}
}
const lecturers: Ref<Lecturer[]> = ref([])
const newLect = new Lecturer('John', 34, 'Astronmy', 'Lecturer')
//https://stackoverflow.com/questions/67894487/vue-3-reactivity-not-triggered-from-inside-a-class-instance/67895251#comment120023919_67895251
const reactiveLect = reactive(newLect)
// doing it this way allows the changer to bind to the reactive version
//reactiveLect.startChanging2()
const me = {
name: 'Bob',
age: 5,
weight: 1.01,
get halfWeight() {
return this.weight / 2
},
doubleWeight() {
this.weight = this.weight * 2
},
explodeWeight() {
const self = this;
const weightInterval = setInterval(() => {
// Check if the weight is NOT a finite number, which correctly catches NaN, Infinity, and -Infinity.
const newW = self.weight ** 2;
if (!Number.isFinite(newW)) {
clearInterval(weightInterval);
return;
}
// Core logic
self.weight = newW
}, 100);
}
}
const reactiveMe = reactive(me)
</script>
<template>
<div class="lect-info">
<input v-model="reactiveLect.name">
</div>
<ul>
<div class="lect-info">
<p>{{reactiveLect.name}}</p>
<p>{{reactiveLect.age}}</p>
<p>{{reactiveLect.twiceAge}}</p>
<p>{{reactiveLect.subject}}</p>
<p>{{reactiveLect.level}}</p>
<p>{{reactiveLect.secondsPast}}</p>
</div>
</ul>
<button @click="newLect.getOlder">Get Older</button>
<button @click="reactiveLect.getOlder">Get Older</button>
<div>
</div>
<div class="lect-info">
<input v-model="reactiveMe.name">
</div>
<ul>
<div class="lect-info">
<p>{{reactiveMe.name}}</p>
<p>{{reactiveMe.age}}</p>
<p>{{reactiveMe.weight}}</p>
<p>{{reactiveMe.halfWeight}}</p>
</div>
</ul>
<button @click="me.doubleWeight">Me x2</button>
<button @click="reactiveMe.doubleWeight">Reactive Me x2</button>
<button @click="reactiveMe.explodeWeight">Reactive Me Explode</button>
</template>
<style>
ul {
list-style: none;
}
div.lect-info {
border: 1px solid black;
padding: 1em;
display: inline-block;
border-radius: 5px;
}
div.lect-info > p:first-child {
font-weight: bold;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment