React hooks & patterns, Vue 3 Composition API, Nuxt 3, Svelte 5 β components, state, routing & essentials
Framework / Frontend// Function component
interface Props {
name: string;
count?: number;
children: React.ReactNode;
}
const Greeting = ({ name, count = 0, children }: Props) => {
return (
<div className="card">
<h1>Hello, {name}!</h1>
<p>Count: {count}</p>
{children}
</div>
);
};
// Conditional rendering
{isLoggedIn ? <Dashboard /> : <Login />}
{error && <ErrorBanner message={error} />}
// Lists
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
// Events
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<input onChange={(e) => setQuery(e.target.value)} />
// Styles
<div style={{ color: "red", fontSize: 16 }}>Styled</div>
<div className={`card ${isActive ? "active" : ""}`}>Toggle</div>import { useState, useEffect, useRef, useMemo, useCallback, useContext } from "react";
// ββ useState ββ
const [count, setCount] = useState(0);
const [users, setUsers] = useState<User[]>([]);
setCount(prev => prev + 1); // functional update
// ββ useEffect ββ
useEffect(() => {
fetchUsers().then(setUsers);
}, []); // [] = run once on mount
useEffect(() => {
const timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer); // cleanup
}, []);
// ββ useRef ββ
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// ββ useMemo ββ (cached computation)
const sorted = useMemo(
() => users.sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
// ββ useCallback ββ (cached function)
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
// ββ useContext ββ
const ThemeContext = createContext<"light" | "dark">("light");
const theme = useContext(ThemeContext);
// Custom hook
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url).then(r => r.json()).then(d => {
setData(d);
setLoading(false);
});
}, [url]);
return { data, loading };
}// ββ State management with useReducer ββ
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
case "reset": return 0;
}
}
const [count, dispatch] = useReducer(reducer, 0);
// ββ Suspense + lazy loading ββ
const Dashboard = lazy(() => import("./Dashboard"));
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
// ββ Server state with TanStack Query ββ
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
const { data, isLoading } = useQuery({
queryKey: ["users"],
queryFn: () => fetch("/api/users").then(r => r.json()),
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newUser) => fetch("/api/users", { method: "POST", body: JSON.stringify(newUser) }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
});import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/users">Users</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
function UserDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
return <button onClick={() => navigate("/users")}>Back</button>;
}<!-- Single File Component (.vue) -->
<script setup lang="ts">
import { ref, computed } from "vue";
interface Props { title: string; count?: number; }
const props = withDefaults(defineProps<Props>(), { count: 0 });
const emit = defineEmits<{ update: [value: number] }>();
const name = ref("Vue");
const greeting = computed(() => `Hello, ${name.value}!`);
function increment() {
emit("update", props.count + 1);
}
</script>
<template>
<div class="card">
<h1>{{ greeting }}</h1>
<p>{{ props.title }}</p>
<!-- Two-way binding -->
<input v-model="name" />
<!-- Conditional -->
<p v-if="count > 10">High!</p>
<p v-else-if="count > 5">Medium</p>
<p v-else>Low</p>
<!-- List -->
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<!-- Event -->
<button @click="increment">+1</button>
<!-- Slots -->
<slot name="header">Default header</slot>
</div>
</template>
<style scoped>
.card { padding: 1rem; }
</style>import { ref, reactive, computed, watch, onMounted, toRefs } from "vue";
// ref (primitives) vs reactive (objects)
const count = ref(0); // access via count.value
const state = reactive({ name: "Alice", age: 25 });
// Computed
const doubled = computed(() => count.value * 2);
// Watch
watch(count, (newVal, oldVal) => {
console.log(`${oldVal} β ${newVal}`);
});
watch(() => state.name, (newName) => {
console.log("Name changed:", newName);
});
// Lifecycle
onMounted(() => { fetchData(); });
// ββ Composable (custom hook equivalent) ββ
export function useFetch<T>(url: string) {
const data = ref<T | null>(null);
const loading = ref(true);
const error = ref<Error | null>(null);
onMounted(async () => {
try {
const res = await fetch(url);
data.value = await res.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
});
return { data, loading, error };
}
// Provide / Inject (dependency injection)
import { provide, inject } from "vue";
provide("theme", "dark"); // parent
const theme = inject("theme"); // childimport { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: () => import("./views/Home.vue") },
{ path: "/users", component: () => import("./views/Users.vue") },
{ path: "/users/:id", component: () => import("./views/UserDetail.vue"), props: true },
{ path: "/:pathMatch(.*)*", component: NotFound },
],
});
// Navigation guard
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { path: "/login" };
}
});
// In component
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
console.log(route.params.id);
router.push("/users");// npx nuxi@latest init my-app
// File-based routing: pages/index.vue β /
// pages/users/[id].vue β /users/:id
// ββ pages/users/[id].vue ββ
<script setup lang="ts">
const route = useRoute();
// Auto-imported data fetching
const { data: user, pending } = await useFetch(
`/api/users/${route.params.id}`
);
</script>
<template>
<div v-if="pending">Loading...</div>
<div v-else>{{ user?.name }}</div>
</template>
// ββ server/api/users/[id].get.ts ββ (API route)
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id");
return { id, name: "Alice" };
});
// ββ server/api/users.post.ts ββ
export default defineEventHandler(async (event) => {
const body = await readBody(event);
return { id: 1, ...body };
});
// Layouts: layouts/default.vue
// Middleware: middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
if (!useAuth().isLoggedIn) return navigateTo("/login");
});
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt"],
runtimeConfig: {
apiSecret: "", // server only
public: { apiBase: "" }, // client + server
},
});<!-- Component.svelte (Svelte 5 with runes) -->
<script lang="ts">
// Props
let { name, count = 0 }: { name: string; count?: number } = $props();
// Reactive state
let message = $state("Hello");
let users = $state<User[]>([]);
// Derived (computed)
let greeting = $derived(`${message}, ${name}!`);
let userCount = $derived(users.length);
// Effects
$effect(() => {
console.log("Count changed:", count);
});
// Functions
function addUser(user: User) {
users.push(user); // $state is deeply reactive
}
// Lifecycle
import { onMount } from "svelte";
onMount(async () => {
const res = await fetch("/api/users");
users = await res.json();
});
</script>
<!-- Template -->
<h1>{greeting}</h1>
<input bind:value={message} />
{#if userCount > 0}
<ul>
{#each users as user (user.id)}
<li>{user.name}</li>
{/each}
</ul>
{:else}
<p>No users yet.</p>
{/if}
<button onclick={() => count++}>Count: {count}</button>
<!-- Slots -->
<slot>Default content</slot>
<style>
h1 { color: tomato; }
</style>// ββ Stores (shared state) ββ
// stores/counter.ts
import { writable, derived } from "svelte/store";
export const count = writable(0);
export const doubled = derived(count, $c => $c * 2);
// In component
import { count } from "./stores/counter";
count.set(5);
count.update(n => n + 1);
<p>{$count}</p> <!-- auto-subscribe with $ prefix -->
// ββ SvelteKit ββ
// npx sv create my-app
// File-based routing:
// src/routes/+page.svelte β /
// src/routes/users/+page.svelte β /users
// src/routes/users/[id]/+page.svelte β /users/:id
// ββ src/routes/users/+page.server.ts ββ (data loader)
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ fetch }) => {
const res = await fetch("/api/users");
return { users: await res.json() };
};
// ββ +page.svelte ββ
<script lang="ts">
let { data } = $props(); // typed from load function
</script>
{#each data.users as user}
<p>{user.name}</p>
{/each}
// ββ API route: src/routes/api/users/+server.ts ββ
import { json } from "@sveltejs/kit";
export async function GET() {
return json([{ id: 1, name: "Alice" }]);
}
export async function POST({ request }) {
const body = await request.json();
return json({ id: 1, ...body }, { status: 201 });
}| Feature | React | Vue 3 | Svelte 5 |
|---|---|---|---|
| Reactivity | Hooks (explicit) | Proxy-based (ref/reactive) | Runes ($state) |
| Template | JSX | HTML template + directives | HTML template + {#if} |
| Bundle | ~40KB runtime | ~33KB runtime | No runtime (compiled) |
| Meta Framework | Next.js / Remix | Nuxt 3 | SvelteKit |
| Ecosystem | Largest | Large, growing | Growing fast |