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

effects.mddocs/

0

# Effects System

1

2

The effects system provides automatic dependency tracking and side effect execution. Effects automatically re-run when their reactive dependencies change, making it easy to create responsive applications.

3

4

## Capabilities

5

6

### effect()

7

8

Creates a reactive effect that automatically tracks dependencies and re-runs when they change. Returns a runner function that can manually trigger the effect.

9

10

```typescript { .api }

11

/**

12

* Creates a reactive effect that tracks dependencies and re-runs on changes

13

* @param fn - The effect function to run

14

* @param options - Configuration options

15

* @returns A runner function that can manually trigger the effect

16

*/

17

function effect<T = any>(

18

fn: () => T,

19

options?: ReactiveEffectOptions

20

): ReactiveEffectRunner<T>;

21

22

interface ReactiveEffectRunner<T = any> {

23

(): T;

24

effect: ReactiveEffect;

25

}

26

27

interface ReactiveEffectOptions extends DebuggerOptions {

28

scheduler?: EffectScheduler;

29

allowRecurse?: boolean;

30

onStop?: () => void;

31

}

32

33

type EffectScheduler = (fn: () => void) => void;

34

```

35

36

**Usage Examples:**

37

38

```typescript

39

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

40

41

// Basic effect

42

const count = ref(0);

43

44

const runner = effect(() => {

45

console.log(`Count is: ${count.value}`);

46

});

47

// Immediately logs: "Count is: 0"

48

49

count.value = 1; // Logs: "Count is: 1"

50

count.value = 2; // Logs: "Count is: 2"

51

52

// Manual execution

53

runner(); // Logs: "Count is: 2"

54

55

// Effect with reactive object

56

const state = reactive({ message: "Hello", count: 0 });

57

58

effect(() => {

59

console.log(`${state.message} - Count: ${state.count}`);

60

});

61

// Logs: "Hello - Count: 0"

62

63

state.message = "Hi"; // Logs: "Hi - Count: 0"

64

state.count = 5; // Logs: "Hi - Count: 5"

65

66

// Effect with return value

67

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

68

69

const logUser = effect(() => {

70

const currentUser = user.value;

71

console.log(`User: ${currentUser.name}, Age: ${currentUser.age}`);

72

return currentUser;

73

});

74

75

user.value = { name: "Bob", age: 30 }; // Logs and returns new user

76

```

77

78

### Effect with Custom Scheduler

79

80

Control when effects run by providing a custom scheduler:

81

82

```typescript

83

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

84

85

const count = ref(0);

86

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

87

88

// Effect with custom scheduler

89

effect(

90

() => {

91

console.log(`Count: ${count.value}`);

92

},

93

{

94

scheduler: (fn) => {

95

// Batch updates instead of running immediately

96

updates.push(fn);

97

}

98

}

99

);

100

101

// Changes are scheduled, not executed immediately

102

count.value = 1; // No immediate log

103

count.value = 2; // No immediate log

104

count.value = 3; // No immediate log

105

106

// Execute batched updates

107

updates.forEach(fn => fn());

108

// Logs: "Count: 3" (only once, with final value)

109

110

// Async scheduler example

111

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

112

113

effect(

114

() => {

115

console.log(`Async count: ${count.value}`);

116

},

117

{

118

scheduler: (fn) => {

119

asyncUpdates.push(fn);

120

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

121

const updates = asyncUpdates.splice(0);

122

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

123

});

124

}

125

}

126

);

127

```

128

129

### stop()

130

131

Stops a reactive effect, preventing it from running when dependencies change.

132

133

```typescript { .api }

134

/**

135

* Stops a reactive effect

136

* @param runner - The effect runner returned by effect()

137

*/

138

function stop(runner: ReactiveEffectRunner): void;

139

```

140

141

**Usage Examples:**

142

143

```typescript

144

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

145

146

const count = ref(0);

147

148

const runner = effect(() => {

149

console.log(`Count: ${count.value}`);

150

});

151

// Logs: "Count: 0"

152

153

count.value = 1; // Logs: "Count: 1"

154

155

// Stop the effect

156

stop(runner);

157

158

count.value = 2; // No log (effect stopped)

159

count.value = 3; // No log (effect stopped)

160

161

// Stopped effects can still be manually executed

162

runner(); // Logs: "Count: 3"

163

```

164

165

### Tracking Control

166

167

Control dependency tracking behavior with tracking functions:

168

169

```typescript { .api }

170

/**

171

* Temporarily pauses dependency tracking

172

*/

173

function pauseTracking(): void;

174

175

/**

176

* Re-enables effect tracking (if it was paused)

177

*/

178

function enableTracking(): void;

179

180

/**

181

* Resets the previous global effect tracking state

182

*/

183

function resetTracking(): void;

184

```

185

186

**Usage Examples:**

187

188

```typescript

189

import { ref, effect, pauseTracking, enableTracking, resetTracking } from "@vue/reactivity";

190

191

const count = ref(0);

192

const name = ref("Alice");

193

194

effect(() => {

195

console.log(`Count: ${count.value}`);

196

197

// Pause tracking temporarily

198

pauseTracking();

199

200

// This access won't be tracked

201

console.log(`Name: ${name.value}`);

202

203

// Re-enable tracking

204

enableTracking();

205

});

206

207

count.value = 1; // Logs both count and name

208

name.value = "Bob"; // Does NOT trigger effect (wasn't tracked)

209

210

// Reset tracking state

211

resetTracking();

212

```

213

214

### onEffectCleanup()

215

216

Register cleanup functions that run before an effect re-runs or when it's stopped.

217

218

```typescript { .api }

219

/**

220

* Registers a cleanup function for the current active effect

221

* @param fn - Cleanup function to register

222

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

223

*/

224

function onEffectCleanup(fn: () => void, failSilently?: boolean): void;

225

```

226

227

**Usage Examples:**

228

229

```typescript

230

import { ref, effect, onEffectCleanup, stop } from "@vue/reactivity";

231

232

const url = ref("https://api.example.com/users");

233

234

const runner = effect(() => {

235

const controller = new AbortController();

236

237

// Register cleanup to cancel request

238

onEffectCleanup(() => {

239

controller.abort();

240

console.log("Request cancelled");

241

});

242

243

// Make API request

244

fetch(url.value, { signal: controller.signal })

245

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

246

.then(data => console.log("Data:", data))

247

.catch(error => {

248

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

249

console.error("Fetch error:", error);

250

}

251

});

252

});

253

254

// Changing URL cancels previous request and starts new one

255

url.value = "https://api.example.com/posts"; // Logs: "Request cancelled"

256

257

// Stopping effect also runs cleanup

258

stop(runner); // Logs: "Request cancelled"

259

260

// Timer cleanup example

261

const interval = ref(1000);

262

263

effect(() => {

264

const timer = setInterval(() => {

265

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

266

}, interval.value);

267

268

onEffectCleanup(() => {

269

clearInterval(timer);

270

console.log("Timer cleared");

271

});

272

});

273

274

interval.value = 500; // Clears old timer, starts new one

275

```

276

277

### ReactiveEffect Class

278

279

The low-level class that powers the effect system. Usually not used directly but available for advanced use cases.

280

281

```typescript { .api }

282

/**

283

* Low-level reactive effect class

284

*/

285

class ReactiveEffect<T = any> implements Subscriber, ReactiveEffectOptions {

286

constructor(public fn: () => T);

287

288

/**

289

* Pause the effect (stop tracking dependencies)

290

*/

291

pause(): void;

292

293

/**

294

* Resume the effect (start tracking dependencies)

295

*/

296

resume(): void;

297

298

/**

299

* Run the effect function and track dependencies

300

*/

301

run(): T;

302

303

/**

304

* Stop the effect permanently

305

*/

306

stop(): void;

307

308

/**

309

* Manually trigger the effect

310

*/

311

trigger(): void;

312

313

/**

314

* Run the effect only if it's dirty (dependencies changed)

315

*/

316

runIfDirty(): void;

317

318

/**

319

* Check if the effect needs to re-run

320

*/

321

get dirty(): boolean;

322

}

323

```

324

325

**Usage Examples:**

326

327

```typescript

328

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

329

330

const count = ref(0);

331

332

// Create effect manually

333

const effectInstance = new ReactiveEffect(() => {

334

console.log(`Manual effect: ${count.value}`);

335

});

336

337

// Must manually run to start tracking

338

effectInstance.run(); // Logs: "Manual effect: 0"

339

340

count.value = 1; // Triggers effect: "Manual effect: 1"

341

342

// Pause and resume

343

effectInstance.pause();

344

count.value = 2; // No log (paused)

345

346

effectInstance.resume();

347

count.value = 3; // Logs: "Manual effect: 3"

348

349

// Check if dirty

350

console.log(effectInstance.dirty); // false (just ran)

351

count.value = 4;

352

console.log(effectInstance.dirty); // true (needs to re-run)

353

354

effectInstance.runIfDirty(); // Logs: "Manual effect: 4"

355

```

356

357

### Effect with Allow Recurse

358

359

Control whether effects can trigger themselves recursively:

360

361

```typescript

362

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

363

364

const count = ref(0);

365

366

// Effect that modifies its own dependency

367

effect(

368

() => {

369

console.log(`Count: ${count.value}`);

370

371

if (count.value < 5) {

372

count.value++; // This would normally cause infinite recursion

373

}

374

},

375

{

376

allowRecurse: true // Allow recursive triggering

377

}

378

);

379

380

// This will log:

381

// "Count: 0"

382

// "Count: 1"

383

// "Count: 2"

384

// "Count: 3"

385

// "Count: 4"

386

// "Count: 5"

387

```

388

389

### Effect Debugging

390

391

Use debug options to understand effect behavior:

392

393

```typescript

394

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

395

396

const count = ref(0);

397

const name = ref("Alice");

398

399

effect(

400

() => {

401

console.log(`${name.value}: ${count.value}`);

402

},

403

{

404

onTrack: (event) => {

405

console.log("Tracked:", event.key, "on", event.target);

406

},

407

onTrigger: (event) => {

408

console.log("Triggered by:", event.key, "->", event.newValue);

409

}

410

}

411

);

412

413

// Logs tracking of both dependencies

414

count.value = 1; // Logs trigger event and effect

415

name.value = "Bob"; // Logs trigger event and effect

416

```

417

418

## Types

419

420

```typescript { .api }

421

// Core effect types

422

interface ReactiveEffectRunner<T = any> {

423

(): T;

424

effect: ReactiveEffect;

425

}

426

427

interface ReactiveEffectOptions extends DebuggerOptions {

428

scheduler?: EffectScheduler;

429

allowRecurse?: boolean;

430

onStop?: () => void;

431

}

432

433

type EffectScheduler = (fn: () => void) => void;

434

435

// Effect flags for internal state management

436

enum EffectFlags {

437

ACTIVE = 1 << 0, // Effect is active

438

RUNNING = 1 << 1, // Effect is currently running

439

TRACKING = 1 << 2, // Effect is tracking dependencies

440

NOTIFIED = 1 << 3, // Effect has been notified of changes

441

DIRTY = 1 << 4, // Effect needs to re-run

442

ALLOW_RECURSE = 1 << 5, // Effect can trigger itself

443

PAUSED = 1 << 6, // Effect is paused

444

EVALUATED = 1 << 7 // Effect has been evaluated

445

}

446

447

// Debug interfaces

448

interface DebuggerOptions {

449

onTrack?: (event: DebuggerEvent) => void;

450

onTrigger?: (event: DebuggerEvent) => void;

451

}

452

453

interface DebuggerEvent {

454

effect: ReactiveEffect;

455

target: object;

456

type: TrackOpTypes | TriggerOpTypes;

457

key: any;

458

newValue?: any;

459

oldValue?: any;

460

oldTarget?: Map<any, any> | Set<any>;

461

}

462

463

interface DebuggerEventExtraInfo {

464

target: object;

465

type: TrackOpTypes | TriggerOpTypes;

466

key: any;

467

newValue?: any;

468

oldValue?: any;

469

oldTarget?: Map<any, any> | Set<any>;

470

}

471

472

// Subscriber interface (internal)

473

interface Subscriber {

474

deps: Link[];

475

flags: EffectFlags;

476

notify(): void;

477

}

478

```

479

480

## Advanced Patterns

481

482

### Conditional Effects

483

484

Create effects that only track certain dependencies based on conditions:

485

486

```typescript

487

import { ref, effect, pauseTracking, enableTracking } from "@vue/reactivity";

488

489

const mode = ref<"simple" | "advanced">("simple");

490

const basicCount = ref(0);

491

const advancedCount = ref(0);

492

493

effect(() => {

494

console.log(`Mode: ${mode.value}`); // Always tracked

495

496

if (mode.value === "simple") {

497

console.log(`Basic: ${basicCount.value}`); // Tracked only in simple mode

498

499

pauseTracking();

500

console.log(`Advanced (not tracked): ${advancedCount.value}`);

501

enableTracking();

502

} else {

503

pauseTracking();

504

console.log(`Basic (not tracked): ${basicCount.value}`);

505

enableTracking();

506

507

console.log(`Advanced: ${advancedCount.value}`); // Tracked only in advanced mode

508

}

509

});

510

511

mode.value = "advanced"; // Switches tracking

512

```

513

514

### Effect Composition

515

516

Combine multiple effects for complex reactive logic:

517

518

```typescript

519

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

520

521

const items = ref<{ id: number; completed: boolean }[]>([]);

522

const filter = ref<"all" | "completed" | "pending">("all");

523

524

// Base effect for logging changes

525

effect(() => {

526

console.log(`Items updated: ${items.value.length} total`);

527

});

528

529

// Computed for filtered items

530

const filteredItems = computed(() => {

531

switch (filter.value) {

532

case "completed":

533

return items.value.filter(item => item.completed);

534

case "pending":

535

return items.value.filter(item => !item.completed);

536

default:

537

return items.value;

538

}

539

});

540

541

// Effect that depends on filtered items

542

effect(() => {

543

console.log(`Showing ${filteredItems.value.length} items (${filter.value})`);

544

});

545

546

// Updates trigger both effects appropriately

547

items.value = [

548

{ id: 1, completed: false },

549

{ id: 2, completed: true }

550

];

551

552

filter.value = "completed"; // Only triggers filter effect

553

```