or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

computed.mdeffect-scopes.mdeffects.mdindex.mdreactive-objects.mdrefs.mdutilities.mdwatchers.md

watchers.mddocs/

0

# Watchers

1

2

Watchers provide a powerful way to observe reactive data changes and execute callbacks with access to both old and new values. They offer more control than effects for handling specific data changes.

3

4

## Capabilities

5

6

### watch()

7

8

Watches one or more reactive data sources and invokes a callback function when the sources change.

9

10

```typescript { .api }

11

/**

12

* Watch reactive data sources and invoke callback on changes

13

* @param source - The reactive source(s) to watch

14

* @param cb - Callback function called when source changes

15

* @param options - Configuration options

16

* @returns WatchHandle with control methods

17

*/

18

function watch<T>(

19

source: WatchSource<T> | WatchSource<T>[] | WatchEffect | object,

20

cb?: WatchCallback<T> | null,

21

options?: WatchOptions

22

): WatchHandle;

23

24

type WatchSource<T = any> = Ref<T, any> | ComputedRef<T> | (() => T);

25

26

type WatchCallback<V = any, OV = any> = (

27

value: V,

28

oldValue: OV,

29

onCleanup: OnCleanup

30

) => any;

31

32

type OnCleanup = (cleanupFn: () => void) => void;

33

34

interface WatchHandle extends WatchStopHandle {

35

pause: () => void;

36

resume: () => void;

37

stop: () => void;

38

}

39

```

40

41

**Usage Examples:**

42

43

```typescript

44

import { ref, reactive, watch } from "@vue/reactivity";

45

46

// Watch a single ref

47

const count = ref(0);

48

49

const stopWatcher = watch(count, (newValue, oldValue) => {

50

console.log(`Count changed from ${oldValue} to ${newValue}`);

51

});

52

53

count.value = 1; // Logs: "Count changed from 0 to 1"

54

count.value = 2; // Logs: "Count changed from 1 to 2"

55

56

// Stop watching

57

stopWatcher.stop();

58

count.value = 5; // No log (watcher stopped)

59

60

// Watch reactive object property

61

const user = reactive({ name: "Alice", age: 25 });

62

63

watch(

64

() => user.name,

65

(newName, oldName) => {

66

console.log(`User name changed from ${oldName} to ${newName}`);

67

}

68

);

69

70

user.name = "Bob"; // Logs: "User name changed from Alice to Bob"

71

72

// Watch entire reactive object

73

watch(

74

user,

75

(newUser, oldUser) => {

76

console.log("User object changed:", newUser);

77

},

78

{ deep: true }

79

);

80

81

user.age = 26; // Triggers watcher with deep option

82

```

83

84

### Watch Multiple Sources

85

86

Watch multiple reactive sources simultaneously:

87

88

```typescript

89

import { ref, watch } from "@vue/reactivity";

90

91

const firstName = ref("John");

92

const lastName = ref("Doe");

93

94

// Watch multiple sources

95

watch(

96

[firstName, lastName],

97

([newFirst, newLast], [oldFirst, oldLast]) => {

98

console.log(

99

`Name changed from "${oldFirst} ${oldLast}" to "${newFirst} ${newLast}"`

100

);

101

}

102

);

103

104

firstName.value = "Jane";

105

// Logs: 'Name changed from "John Doe" to "Jane Doe"'

106

107

lastName.value = "Smith";

108

// Logs: 'Name changed from "Jane Doe" to "Jane Smith"'

109

110

// Watch multiple with different source types

111

const count = ref(0);

112

const doubled = computed(() => count.value * 2);

113

const message = ref("Hello");

114

115

watch(

116

[count, doubled, message],

117

([newCount, newDoubled, newMessage], [oldCount, oldDoubled, oldMessage]) => {

118

console.log({

119

count: { old: oldCount, new: newCount },

120

doubled: { old: oldDoubled, new: newDoubled },

121

message: { old: oldMessage, new: newMessage }

122

});

123

}

124

);

125

```

126

127

### Watch Options

128

129

Configure watcher behavior with various options:

130

131

```typescript { .api }

132

interface WatchOptions<Immediate = boolean> extends DebuggerOptions {

133

immediate?: Immediate;

134

deep?: boolean | number;

135

once?: boolean;

136

scheduler?: WatchScheduler;

137

onWarn?: (msg: string, ...args: any[]) => void;

138

}

139

140

type WatchScheduler = (fn: () => void, isFirstRun: boolean) => void;

141

```

142

143

**Usage Examples:**

144

145

```typescript

146

import { ref, reactive, watch } from "@vue/reactivity";

147

148

const count = ref(0);

149

const user = reactive({ profile: { name: "Alice" } });

150

151

// Immediate execution

152

watch(

153

count,

154

(newValue, oldValue) => {

155

console.log(`Count: ${newValue} (was ${oldValue})`);

156

},

157

{ immediate: true }

158

);

159

// Immediately logs: "Count: 0 (was undefined)"

160

161

// Deep watching

162

watch(

163

user,

164

(newUser, oldUser) => {

165

console.log("Deep change detected in user");

166

},

167

{ deep: true }

168

);

169

170

user.profile.name = "Bob"; // Triggers watcher due to deep option

171

172

// Watch only once

173

watch(

174

count,

175

(newValue) => {

176

console.log(`First change to: ${newValue}`);

177

},

178

{ once: true }

179

);

180

181

count.value = 1; // Logs and then watcher is automatically stopped

182

count.value = 2; // No log (watcher stopped after first trigger)

183

184

// Custom scheduler

185

const updates: (() => void)[] = [];

186

187

watch(

188

count,

189

(newValue) => {

190

console.log(`Scheduled update: ${newValue}`);

191

},

192

{

193

scheduler: (fn) => {

194

updates.push(fn);

195

// Execute updates in next tick

196

Promise.resolve().then(() => {

197

const currentUpdates = updates.splice(0);

198

currentUpdates.forEach(update => update());

199

});

200

}

201

}

202

);

203

```

204

205

### Watch with Cleanup

206

207

Handle cleanup for async operations or subscriptions:

208

209

```typescript

210

import { ref, watch } from "@vue/reactivity";

211

212

const userId = ref(1);

213

214

watch(

215

userId,

216

async (newId, oldId, onCleanup) => {

217

// Setup abort controller for fetch

218

const controller = new AbortController();

219

220

// Register cleanup function

221

onCleanup(() => {

222

controller.abort();

223

console.log(`Cancelled request for user ${newId}`);

224

});

225

226

try {

227

const response = await fetch(`/api/users/${newId}`, {

228

signal: controller.signal

229

});

230

const user = await response.json();

231

console.log("Loaded user:", user);

232

} catch (error) {

233

if (error.name !== "AbortError") {

234

console.error("Failed to load user:", error);

235

}

236

}

237

}

238

);

239

240

// Changing userId cancels previous request

241

userId.value = 2; // Logs: "Cancelled request for user 2"

242

userId.value = 3; // Logs: "Cancelled request for user 3"

243

244

// Timer cleanup example

245

const interval = ref(1000);

246

247

watch(interval, (newInterval, oldInterval, onCleanup) => {

248

const timer = setInterval(() => {

249

console.log(`Timer tick (${newInterval}ms)`);

250

}, newInterval);

251

252

onCleanup(() => {

253

clearInterval(timer);

254

console.log(`Cleared ${newInterval}ms timer`);

255

});

256

});

257

```

258

259

### getCurrentWatcher()

260

261

Get the current active watcher context:

262

263

```typescript { .api }

264

/**

265

* Returns the current active watcher effect if there is one

266

* @returns Current active watcher or undefined

267

*/

268

function getCurrentWatcher(): ReactiveEffect<any> | undefined;

269

```

270

271

**Usage Examples:**

272

273

```typescript

274

import { ref, watch, getCurrentWatcher } from "@vue/reactivity";

275

276

const count = ref(0);

277

278

watch(count, () => {

279

const currentWatcher = getCurrentWatcher();

280

if (currentWatcher) {

281

console.log("Inside watcher context");

282

console.log("Watcher flags:", currentWatcher.flags);

283

}

284

});

285

286

count.value = 1; // Logs watcher context info

287

```

288

289

### onWatcherCleanup()

290

291

Register cleanup functions from within watchers:

292

293

```typescript { .api }

294

/**

295

* Register cleanup callback on the current active watcher

296

* @param cleanupFn - Cleanup function to register

297

* @param failSilently - If true, won't warn when no active watcher

298

* @param owner - The effect to attach cleanup to

299

*/

300

function onWatcherCleanup(

301

cleanupFn: () => void,

302

failSilently?: boolean,

303

owner?: ReactiveEffect | undefined

304

): void;

305

```

306

307

**Usage Examples:**

308

309

```typescript

310

import { ref, watch, onWatcherCleanup } from "@vue/reactivity";

311

312

const searchTerm = ref("");

313

314

watch(searchTerm, (term) => {

315

if (!term) return;

316

317

const controller = new AbortController();

318

319

// Register cleanup using onWatcherCleanup

320

onWatcherCleanup(() => {

321

controller.abort();

322

console.log(`Search cancelled for: ${term}`);

323

});

324

325

// Perform search

326

fetch(`/api/search?q=${term}`, { signal: controller.signal })

327

.then(response => response.json())

328

.then(results => console.log("Results:", results))

329

.catch(error => {

330

if (error.name !== "AbortError") {

331

console.error("Search failed:", error);

332

}

333

});

334

});

335

```

336

337

### traverse()

338

339

Deeply traverse an object for dependency tracking:

340

341

```typescript { .api }

342

/**

343

* Deeply traverses an object for dependency tracking

344

* @param value - The value to traverse

345

* @param depth - Maximum traversal depth

346

* @param seen - Map to avoid circular references

347

* @returns The traversed value

348

*/

349

function traverse(

350

value: unknown,

351

depth?: number,

352

seen?: Map<unknown, number>

353

): unknown;

354

```

355

356

**Usage Examples:**

357

358

```typescript

359

import { reactive, watch, traverse } from "@vue/reactivity";

360

361

const deepObject = reactive({

362

level1: {

363

level2: {

364

level3: {

365

value: "deep"

366

}

367

}

368

}

369

});

370

371

// Custom deep watching with traverse

372

watch(

373

() => traverse(deepObject, 2), // Limit depth to 2 levels

374

() => {

375

console.log("Object changed (depth 2)");

376

}

377

);

378

379

deepObject.level1.level2.value = "changed"; // Triggers watcher

380

deepObject.level1.level2.level3.value = "deep change"; // Doesn't trigger (depth > 2)

381

```

382

383

### Watch Error Handling

384

385

Handle errors in watchers with proper error codes:

386

387

```typescript { .api }

388

enum WatchErrorCodes {

389

WATCH_GETTER = 2,

390

WATCH_CALLBACK,

391

WATCH_CLEANUP

392

}

393

```

394

395

**Usage Examples:**

396

397

```typescript

398

import { ref, watch } from "@vue/reactivity";

399

400

const count = ref(0);

401

402

// Error handling in watcher callback

403

watch(

404

count,

405

(newValue) => {

406

try {

407

if (newValue < 0) {

408

throw new Error("Count cannot be negative");

409

}

410

console.log("Valid count:", newValue);

411

} catch (error) {

412

console.error("Watcher error:", error.message);

413

}

414

}

415

);

416

417

count.value = 5; // Logs: "Valid count: 5"

418

count.value = -1; // Logs: "Watcher error: Count cannot be negative"

419

420

// Error handling in getter

421

watch(

422

() => {

423

if (count.value > 100) {

424

throw new Error("Count too high");

425

}

426

return count.value;

427

},

428

(newValue) => {

429

console.log("Count within limits:", newValue);

430

},

431

{

432

onWarn: (msg, ...args) => {

433

console.warn("Watch warning:", msg, ...args);

434

}

435

}

436

);

437

```

438

439

### Advanced Watch Patterns

440

441

#### Debounced Watcher

442

443

```typescript

444

import { ref, watch } from "@vue/reactivity";

445

446

const searchTerm = ref("");

447

448

function debouncedWatch<T>(

449

source: () => T,

450

callback: (value: T) => void,

451

delay: number = 300

452

) {

453

let timeoutId: number;

454

455

return watch(

456

source,

457

(newValue) => {

458

clearTimeout(timeoutId);

459

timeoutId = setTimeout(() => {

460

callback(newValue);

461

}, delay);

462

}

463

);

464

}

465

466

// Debounced search

467

debouncedWatch(

468

() => searchTerm.value,

469

(term) => {

470

console.log("Searching for:", term);

471

// Perform search...

472

},

473

500

474

);

475

476

searchTerm.value = "vue"; // Wait 500ms

477

searchTerm.value = "vuejs"; // Cancels previous, wait 500ms

478

```

479

480

#### Conditional Watcher

481

482

```typescript

483

import { ref, watch, computed } from "@vue/reactivity";

484

485

const isEnabled = ref(false);

486

const count = ref(0);

487

488

// Watch that can be enabled/disabled

489

const conditionalWatcher = computed(() => {

490

if (!isEnabled.value) return null;

491

return count.value;

492

});

493

494

watch(conditionalWatcher, (newValue, oldValue) => {

495

if (newValue !== null) {

496

console.log(`Count changed to: ${newValue}`);

497

}

498

});

499

500

count.value = 1; // No log (not enabled)

501

isEnabled.value = true;

502

count.value = 2; // Logs: "Count changed to: 2"

503

isEnabled.value = false;

504

count.value = 3; // No log (disabled)

505

```

506

507

## Types

508

509

```typescript { .api }

510

// Core watch types

511

type WatchSource<T = any> = Ref<T, any> | ComputedRef<T> | (() => T);

512

513

type WatchCallback<V = any, OV = any> = (

514

value: V,

515

oldValue: OV,

516

onCleanup: OnCleanup

517

) => any;

518

519

type OnCleanup = (cleanupFn: () => void) => void;

520

521

type WatchEffect = (onCleanup: OnCleanup) => void;

522

523

// Watch handles

524

interface WatchStopHandle {

525

(): void;

526

}

527

528

interface WatchHandle extends WatchStopHandle {

529

pause: () => void;

530

resume: () => void;

531

stop: () => void;

532

}

533

534

// Watch options

535

interface WatchOptions<Immediate = boolean> extends DebuggerOptions {

536

immediate?: Immediate;

537

deep?: boolean | number;

538

once?: boolean;

539

scheduler?: WatchScheduler;

540

onWarn?: (msg: string, ...args: any[]) => void;

541

}

542

543

type WatchScheduler = (fn: () => void, isFirstRun: boolean) => void;

544

545

// Error codes

546

enum WatchErrorCodes {

547

WATCH_GETTER = 2,

548

WATCH_CALLBACK,

549

WATCH_CLEANUP

550

}

551

552

// Internal types

553

interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {

554

immediate?: Immediate;

555

deep?: boolean | number;

556

once?: boolean;

557

scheduler?: WatchScheduler;

558

augmentJob?: (job: SchedulerJob) => void;

559

call?: (

560

fn: Function,

561

type: string,

562

args?: unknown[]

563

) => void;

564

}

565

566

interface SchedulerJob extends Function {

567

id?: number;

568

pre?: boolean;

569

active?: boolean;

570

computed?: boolean;

571

allowRecurse?: boolean;

572

ownerInstance?: any;

573

}

574

```