me-core';
interface OrderState {
quantity: number;
unitPrice: number;
taxRate: number;
}
const order = useReactive<OrderState>({
quantity: 5,
unitPrice: 29.99,
taxRate: 0.08,
});
// Automatic dependency tracking: recalculates only when
// quantity, unitPrice, or taxRate changes.
const totalAmount = useComputed(() => {
const subtotal = order.quantity * order.unitPrice;
return subtotal * (1 + order.taxRate);
});
// Access pattern mimics Vue refs
console.log(totalAmount.value);
**Architecture Rationale:**
* **`useReactive` Integration:** React lacks native reactive proxies. The runtime uses `useReactive` to wrap state objects, enabling property access interception. This allows `useComputed` to register dependencies dynamically when `order.quantity` is read.
* **Lazy Evaluation:** The computation function is not executed immediately. It runs only when `totalAmount.value` is accessed and the dependencies have changed, optimizing performance for expensive calculations.
* **Ref-like Access:** The hook returns an object with a `.value` property. This maintains consistency with Vue's API surface, allowing compiled code to access derived values without changing the access pattern.
#### 2. TypeScript Preservation
Type annotations are preserved during compilation. The generic type parameter passed to `computed` in Vue is mapped directly to `useComputed`, ensuring type safety in the generated React code.
**TypeScript Example:**
```typescript
import { useReactive, useComputed } from '@vureact/runtime-core';
interface UserProfile {
firstName: string;
lastName: string;
}
const profile = useReactive<UserProfile>({
firstName: 'Alice',
lastName: 'Chen',
});
// Generic type <string> is preserved
const displayName = useComputed<string>(() => {
return `${profile.firstName} ${profile.lastName}`;
});
// TypeScript correctly infers displayName.value as string
const greeting: string = `Hello, ${displayName.value}`;
3. Writable Computed Properties
Vue supports writable computed properties via an object with get and set functions. VuReact compiles this pattern to useComputed with the same structure, enabling two-way binding scenarios in React.
Writable Example:
import { useReactive, useComputed } from '@vureact/runtime-core';
interface TemperatureState {
celsius: number;
}
const temp = useReactive<TemperatureState>({
celsius: 20,
});
const fahrenheit = useComputed({
get: () => (temp.celsius * 9) / 5 + 32,
set: (value: number) => {
// Setter updates the underlying reactive source
temp.celsius = ((value - 32) * 5) / 9;
},
});
// Reading triggers getter
console.log(fahrenheit.value); // 68
// Writing triggers setter
fahrenheit.value = 86;
console.log(temp.celsius); // 30
Key Insight: The setter function modifies the reactive source (temp.celsius), which triggers re-evaluation of the getter. This creates a synchronized two-way relationship between derived and source state, a pattern that requires manual state management in standard React.
Pitfall Guide
When working with compiled computed properties, certain patterns can lead to performance degradation or unexpected behavior.
-
Side Effects in Computed Functions
- Explanation: Computed properties should be pure functions. Introducing side effects (e.g., API calls, DOM manipulation, or mutating external state) inside the computation function causes unpredictable behavior, as the function may run multiple times or at unexpected times.
- Fix: Move side effects to
useEffect or event handlers. Keep computed functions focused solely on deriving values.
-
Circular Dependencies
- Explanation: If computed property A depends on B, and B depends on A, the runtime enters an infinite loop or throws a stack overflow error.
- Fix: Refactor the logic to break the cycle. Introduce an intermediate source of truth or restructure the dependencies so they form a directed acyclic graph.
-
Mutable Return Values
- Explanation: Returning a mutable object from a computed property can lead to bugs if the caller modifies the returned object directly. Since the computed value is cached, subsequent reads may return the mutated object instead of a fresh derivation.
- Fix: Return immutable values or deep clones. Alternatively, treat the returned object as read-only and document this contract.
-
Missing TypeScript Generics
- Explanation: While inference often works, complex computations may result in
any types if the generic parameter is omitted. This reduces type safety.
- Fix: Explicitly provide the generic type to
useComputed<T> when the return type is not immediately obvious to the compiler.
-
Over-Reliance on Reactive State
- Explanation: Wrapping every piece of state in
useReactive can increase memory usage and tracking overhead. Not all state needs to be reactive.
- Fix: Use standard React state or plain variables for data that does not drive UI updates or computed properties. Reserve
useReactive for state that is accessed by computed functions.
-
Accessing .value Incorrectly
- Explanation: Developers accustomed to React hooks might expect the hook to return the value directly. Attempting to use the hook result as a primitive value will fail.
- Fix: Always access the
.value property of the computed result. Ensure compiled code maintains this access pattern.
-
Performance Bottlenecks in Getters
- Explanation: Expensive computations inside
useComputed can block rendering if triggered frequently. While caching helps, the initial calculation or dependency change can still cause jank.
- Fix: Profile computed properties. If a computation is heavy, consider debouncing the source or breaking the logic into smaller, incremental steps.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Simple derived value | useComputed | Automatic tracking reduces boilerplate and errors. | Low |
| Expensive calc with static deps | useMemo | Native hook may have lower overhead if deps are known and stable. | Medium |
| Two-way derived state | useComputed (writable) | Provides synchronized get/set logic without manual state sync. | Low |
| Migration from Vue | useComputed | Preserves original logic structure, minimizing refactoring effort. | Low |
| Complex dependency graph | useComputed | Automatic tracking handles complex relationships better than manual arrays. | Low |
Configuration Template
Use this template to set up a computed property with full TypeScript support and error handling in a production environment.
import { useReactive, useComputed } from '@vureact/runtime-core';
interface DashboardState {
metrics: Record<string, number>;
threshold: number;
}
const dashboard = useReactive<DashboardState>({
metrics: { cpu: 45, memory: 72 },
threshold: 80,
});
const isAlertActive = useComputed<boolean>(() => {
try {
const maxMetric = Math.max(...Object.values(dashboard.metrics));
return maxMetric > dashboard.threshold;
} catch (error) {
console.error('Error computing alert state:', error);
return false;
}
});
// Usage in component
function AlertBanner() {
return (
<div className={isAlertActive.value ? 'alert' : 'normal'}>
Status: {isAlertActive.value ? 'Critical' : 'Normal'}
</div>
);
}
Quick Start Guide
- Install Runtime: Add
@vureact/runtime-core to your project dependencies.
- Import Hooks: Import
useReactive and useComputed from the runtime package.
- Define Reactive State: Wrap your source state using
useReactive.
- Create Computed Property: Call
useComputed with a function that derives values from the reactive state.
- Access Value: Use the
.value property to read the computed result in your component.
This workflow enables Vue-style derived state in React with minimal configuration, leveraging automatic tracking and type safety for robust application development.