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

reactive-objects.mddocs/

0

# Reactive Objects

1

2

Reactive objects provide deep reactivity tracking for complex data structures using JavaScript Proxies. They automatically track property access and mutations, enabling fine-grained reactivity for objects, arrays, Maps, Sets, and other collections.

3

4

## Capabilities

5

6

### reactive()

7

8

Creates a reactive proxy of an object with deep reactive conversion. All nested objects and arrays become reactive, and refs are automatically unwrapped.

9

10

```typescript { .api }

11

/**

12

* Creates a reactive proxy with deep reactive conversion

13

* @param target - Object to make reactive

14

* @returns Reactive proxy that tracks all property access and changes

15

*/

16

function reactive<T extends object>(target: T): Reactive<T>;

17

18

type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});

19

```

20

21

**Usage Examples:**

22

23

```typescript

24

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

25

26

// Basic reactive object

27

const state = reactive({

28

count: 0,

29

message: "Hello Vue"

30

});

31

32

effect(() => {

33

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

34

});

35

36

state.count++; // Triggers effect

37

state.message = "Hello World"; // Triggers effect

38

39

// Nested objects are automatically reactive

40

const user = reactive({

41

profile: {

42

name: "Alice",

43

settings: {

44

theme: "dark"

45

}

46

},

47

posts: []

48

});

49

50

// All levels are reactive

51

user.profile.name = "Bob"; // Triggers effects

52

user.posts.push({ title: "New Post" }); // Triggers effects

53

user.profile.settings.theme = "light"; // Triggers effects

54

55

// Arrays are reactive

56

const items = reactive([1, 2, 3]);

57

items.push(4); // Triggers effects

58

items[0] = 10; // Triggers effects

59

```

60

61

### readonly()

62

63

Creates a readonly proxy to the original object with deep readonly conversion. The proxy has the same ref-unwrapping behavior as reactive objects but prevents mutations.

64

65

```typescript { .api }

66

/**

67

* Creates a readonly proxy with deep readonly conversion

68

* @param target - Object to make readonly

69

* @returns Deep readonly proxy that prevents mutations

70

*/

71

function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>;

72

73

type DeepReadonly<T> = {

74

readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];

75

};

76

```

77

78

**Usage Examples:**

79

80

```typescript

81

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

82

83

const original = reactive({

84

count: 0,

85

user: {

86

name: "Alice"

87

}

88

});

89

90

const readonlyState = readonly(original);

91

92

// Reading works fine

93

console.log(readonlyState.count); // 0

94

console.log(readonlyState.user.name); // "Alice"

95

96

// Mutations will warn in development and be ignored

97

// readonlyState.count = 1; // Warning in dev, ignored

98

// readonlyState.user.name = "Bob"; // Warning in dev, ignored

99

100

// Original can still be mutated

101

original.count = 5; // This works and readonlyState reflects the change

102

console.log(readonlyState.count); // 5

103

```

104

105

### shallowReactive()

106

107

Creates a shallow reactive proxy where only root-level properties are reactive. Nested objects are not converted to reactive.

108

109

```typescript { .api }

110

/**

111

* Creates a shallow reactive proxy - only root level properties are reactive

112

* @param target - Object to make shallow reactive

113

* @returns Shallow reactive proxy

114

*/

115

function shallowReactive<T extends object>(target: T): ShallowReactive<T>;

116

117

type ShallowReactive<T> = T & ReactiveMarker;

118

```

119

120

**Usage Examples:**

121

122

```typescript

123

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

124

125

const state = shallowReactive({

126

count: 0,

127

user: {

128

name: "Alice" // This nested object is NOT reactive

129

}

130

});

131

132

effect(() => {

133

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

134

});

135

136

effect(() => {

137

console.log(`User name: ${state.user.name}`);

138

});

139

140

state.count++; // Triggers first effect

141

state.user.name = "Bob"; // Does NOT trigger second effect (shallow)

142

143

// Replacing the entire object triggers effects

144

state.user = { name: "Charlie" }; // Triggers second effect

145

```

146

147

### shallowReadonly()

148

149

Creates a shallow readonly proxy where only root-level properties are readonly. Nested objects can still be mutated.

150

151

```typescript { .api }

152

/**

153

* Creates a shallow readonly proxy - only root level properties are readonly

154

* @param target - Object to make shallow readonly

155

* @returns Shallow readonly proxy

156

*/

157

function shallowReadonly<T extends object>(target: T): Readonly<T>;

158

```

159

160

**Usage Examples:**

161

162

```typescript

163

import { shallowReadonly } from "@vue/reactivity";

164

165

const state = {

166

count: 0,

167

user: {

168

name: "Alice"

169

}

170

};

171

172

const readonlyState = shallowReadonly(state);

173

174

// Root level mutations are blocked

175

// readonlyState.count = 1; // Warning in dev, ignored

176

177

// But nested mutations work (shallow)

178

readonlyState.user.name = "Bob"; // This works!

179

console.log(readonlyState.user.name); // "Bob"

180

```

181

182

### isReactive()

183

184

Checks if an object is a proxy created by `reactive()` or `shallowReactive()`.

185

186

```typescript { .api }

187

/**

188

* Checks if an object is a reactive proxy

189

* @param value - Value to check

190

* @returns True if the value is a reactive proxy

191

*/

192

function isReactive(value: unknown): boolean;

193

```

194

195

**Usage Examples:**

196

197

```typescript

198

import { reactive, shallowReactive, readonly, isReactive } from "@vue/reactivity";

199

200

const reactiveObj = reactive({ count: 0 });

201

const shallowObj = shallowReactive({ count: 0 });

202

const readonlyObj = readonly({ count: 0 });

203

const plainObj = { count: 0 };

204

205

console.log(isReactive(reactiveObj)); // true

206

console.log(isReactive(shallowObj)); // true

207

console.log(isReactive(readonlyObj)); // false (readonly, not reactive)

208

console.log(isReactive(plainObj)); // false

209

```

210

211

### isReadonly()

212

213

Checks if an object is a readonly proxy created by `readonly()` or `shallowReadonly()`.

214

215

```typescript { .api }

216

/**

217

* Checks if an object is a readonly proxy

218

* @param value - Value to check

219

* @returns True if the value is a readonly proxy

220

*/

221

function isReadonly(value: unknown): boolean;

222

```

223

224

**Usage Examples:**

225

226

```typescript

227

import { reactive, readonly, shallowReadonly, isReadonly } from "@vue/reactivity";

228

229

const reactiveObj = reactive({ count: 0 });

230

const readonlyObj = readonly({ count: 0 });

231

const shallowReadonlyObj = shallowReadonly({ count: 0 });

232

const plainObj = { count: 0 };

233

234

console.log(isReadonly(reactiveObj)); // false

235

console.log(isReadonly(readonlyObj)); // true

236

console.log(isReadonly(shallowReadonlyObj)); // true

237

console.log(isReadonly(plainObj)); // false

238

```

239

240

### isShallow()

241

242

Checks if an object is a shallow reactive or readonly proxy.

243

244

```typescript { .api }

245

/**

246

* Checks if an object is a shallow proxy (reactive or readonly)

247

* @param value - Value to check

248

* @returns True if the value is a shallow proxy

249

*/

250

function isShallow(value: unknown): boolean;

251

```

252

253

**Usage Examples:**

254

255

```typescript

256

import { reactive, shallowReactive, readonly, shallowReadonly, isShallow } from "@vue/reactivity";

257

258

const reactiveObj = reactive({ count: 0 });

259

const shallowReactiveObj = shallowReactive({ count: 0 });

260

const readonlyObj = readonly({ count: 0 });

261

const shallowReadonlyObj = shallowReadonly({ count: 0 });

262

263

console.log(isShallow(reactiveObj)); // false

264

console.log(isShallow(shallowReactiveObj)); // true

265

console.log(isShallow(readonlyObj)); // false

266

console.log(isShallow(shallowReadonlyObj)); // true

267

```

268

269

### isProxy()

270

271

Checks if an object is any type of Vue-created proxy (reactive, readonly, shallow reactive, or shallow readonly).

272

273

```typescript { .api }

274

/**

275

* Checks if an object is any type of Vue proxy

276

* @param value - Value to check

277

* @returns True if the value is any type of Vue proxy

278

*/

279

function isProxy(value: any): boolean;

280

```

281

282

**Usage Examples:**

283

284

```typescript

285

import { reactive, readonly, shallowReactive, isProxy } from "@vue/reactivity";

286

287

const reactiveObj = reactive({ count: 0 });

288

const readonlyObj = readonly({ count: 0 });

289

const shallowObj = shallowReactive({ count: 0 });

290

const plainObj = { count: 0 };

291

292

console.log(isProxy(reactiveObj)); // true

293

console.log(isProxy(readonlyObj)); // true

294

console.log(isProxy(shallowObj)); // true

295

console.log(isProxy(plainObj)); // false

296

```

297

298

### toRaw()

299

300

Returns the raw, original object of a Vue-created proxy. Useful for getting the original object without proxy behavior.

301

302

```typescript { .api }

303

/**

304

* Returns the raw original object of a Vue-created proxy

305

* @param observed - A Vue proxy object

306

* @returns The original object without proxy wrapper

307

*/

308

function toRaw<T>(observed: T): T;

309

```

310

311

**Usage Examples:**

312

313

```typescript

314

import { reactive, readonly, toRaw } from "@vue/reactivity";

315

316

const original = { count: 0, user: { name: "Alice" } };

317

const reactiveObj = reactive(original);

318

const readonlyObj = readonly(reactiveObj);

319

320

console.log(toRaw(reactiveObj) === original); // true

321

console.log(toRaw(readonlyObj) === original); // true

322

323

// Useful for passing non-reactive objects to third-party APIs

324

function saveToAPI(data: any) {

325

// Send raw object without reactivity

326

fetch("/api/save", {

327

method: "POST",

328

body: JSON.stringify(toRaw(data))

329

});

330

}

331

332

saveToAPI(reactiveObj); // Sends original object

333

```

334

335

### markRaw()

336

337

Marks an object so that it will never be converted to a proxy. Useful for objects that should remain non-reactive for performance or compatibility reasons.

338

339

```typescript { .api }

340

/**

341

* Marks an object to never be converted to a proxy

342

* @param value - Object to mark as raw

343

* @returns The same object with a skip marker

344

*/

345

function markRaw<T extends object>(value: T): Raw<T>;

346

347

type Raw<T> = T & { [RawSymbol]?: true };

348

```

349

350

**Usage Examples:**

351

352

```typescript

353

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

354

355

// Mark an object to prevent reactivity

356

const nonReactiveObj = markRaw({

357

largeDataSet: new Array(10000).fill(0),

358

thirdPartyInstance: new SomeLibrary()

359

});

360

361

const state = reactive({

362

count: 0,

363

data: nonReactiveObj // This won't be made reactive

364

});

365

366

// state.count is reactive, but state.data is not

367

state.count++; // Triggers effects

368

state.data.largeDataSet.push(1); // Does NOT trigger effects (marked raw)

369

370

// Useful for large objects or third-party instances

371

const map = markRaw(new Map());

372

const reactiveState = reactive({

373

myMap: map // Map stays non-reactive for performance

374

});

375

```

376

377

### toReactive()

378

379

Utility function that returns a reactive proxy if the value is an object, otherwise returns the value itself.

380

381

```typescript { .api }

382

/**

383

* Returns a reactive proxy if the value is an object, otherwise the value itself

384

* @param value - Value to potentially make reactive

385

* @returns Reactive proxy or original value

386

*/

387

const toReactive: <T extends unknown>(value: T) => T;

388

```

389

390

**Usage Examples:**

391

392

```typescript

393

import { toReactive } from "@vue/reactivity";

394

395

const obj = { count: 0 };

396

const num = 42;

397

const str = "hello";

398

399

const reactiveObj = toReactive(obj); // Returns reactive proxy

400

const reactiveNum = toReactive(num); // Returns 42 (primitive)

401

const reactiveStr = toReactive(str); // Returns "hello" (primitive)

402

403

console.log(isReactive(reactiveObj)); // true

404

console.log(isReactive(reactiveNum)); // false

405

console.log(isReactive(reactiveStr)); // false

406

```

407

408

### toReadonly()

409

410

Utility function that returns a readonly proxy if the value is an object, otherwise returns the value itself.

411

412

```typescript { .api }

413

/**

414

* Returns a readonly proxy if the value is an object, otherwise the value itself

415

* @param value - Value to potentially make readonly

416

* @returns Readonly proxy or original value

417

*/

418

const toReadonly: <T extends unknown>(value: T) => DeepReadonly<T>;

419

```

420

421

**Usage Examples:**

422

423

```typescript

424

import { toReadonly } from "@vue/reactivity";

425

426

const obj = { count: 0 };

427

const num = 42;

428

429

const readonlyObj = toReadonly(obj); // Returns readonly proxy

430

const readonlyNum = toReadonly(num); // Returns 42 (primitive)

431

432

console.log(isReadonly(readonlyObj)); // true

433

console.log(isReadonly(readonlyNum)); // false (primitive)

434

```

435

436

## Reactive Collections

437

438

Vue's reactivity system provides special handling for JavaScript collections:

439

440

### Arrays

441

442

Arrays are fully reactive with instrumented methods:

443

444

```typescript

445

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

446

447

const arr = reactive([1, 2, 3]);

448

449

effect(() => {

450

console.log("Array length:", arr.length);

451

console.log("First item:", arr[0]);

452

});

453

454

// All these operations trigger effects

455

arr.push(4);

456

arr.pop();

457

arr[0] = 10;

458

arr.splice(1, 1, 20);

459

arr.sort();

460

arr.reverse();

461

```

462

463

### Maps

464

465

Maps are reactive for all operations:

466

467

```typescript

468

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

469

470

const map = reactive(new Map());

471

472

effect(() => {

473

console.log("Map size:", map.size);

474

console.log("Has 'key':", map.has("key"));

475

});

476

477

map.set("key", "value"); // Triggers effects

478

map.delete("key"); // Triggers effects

479

map.clear(); // Triggers effects

480

```

481

482

### Sets

483

484

Sets are reactive for all operations:

485

486

```typescript

487

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

488

489

const set = reactive(new Set());

490

491

effect(() => {

492

console.log("Set size:", set.size);

493

console.log("Has 'item':", set.has("item"));

494

});

495

496

set.add("item"); // Triggers effects

497

set.delete("item"); // Triggers effects

498

set.clear(); // Triggers effects

499

```

500

501

## Types

502

503

```typescript { .api }

504

// Reactive object types

505

type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});

506

type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };

507

type ShallowReactive<T> = T & ReactiveMarker;

508

509

// Utility types

510

type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;

511

type Raw<T> = T & { [RawSymbol]?: true };

512

513

// Target interface for reactive objects

514

interface Target {

515

[ReactiveFlags.SKIP]?: boolean;

516

[ReactiveFlags.IS_REACTIVE]?: boolean;

517

[ReactiveFlags.IS_READONLY]?: boolean;

518

[ReactiveFlags.IS_SHALLOW]?: boolean;

519

[ReactiveFlags.RAW]?: any;

520

}

521

522

// Marker interfaces

523

interface ReactiveMarker {

524

[ReactiveMarkerSymbol]?: void;

525

}

526

```