react-to-svelte
npx skills add https://github.com/tsurgcom/react-to-svelte --skill react-to-svelte
Agent 安装分布
Skill 文档
React to Svelte 5 Converter
Convert React components to Svelte 5 components using modern runes syntax.
When to Use
- Converting React components to Svelte
- Migrating React projects to Svelte
- Understanding React-to-Svelte patterns
Core Conversions
1. Component Structure
React:
import React from "react";
function MyComponent() {
return <div>Hello</div>;
}
export default MyComponent;
Svelte 5:
<div>Hello</div>
2. State Management
React useState:
const [count, setCount] = useState(0);
setCount(count + 1);
Svelte $state:
<script>
let count = $state(0);
count++; // Direct mutation works
</script>
React useReducer:
const [state, dispatch] = useReducer(reducer, initialState);
Svelte:
<script>
let state = $state(initialState);
function dispatch(action) {
state = reducer(state, action);
}
</script>
3. Derived Values
React useMemo:
const doubled = useMemo(() => count * 2, [count]);
Svelte $derived:
<script>
let doubled = $derived(count * 2);
</script>
React useCallback:
const handleClick = useCallback(() => {}, []);
Svelte:
<script>
// Just a regular function - no need for useCallback
function handleClick() {}
</script>
4. Side Effects
React useEffect:
useEffect(() => {
console.log("mounted or updated");
return () => console.log("cleanup");
}, [dep]);
Svelte $effect:
<script>
$effect(() => {
console.log('mounted or updated');
return () => console.log('cleanup');
});
</script>
React useLayoutEffect:
useLayoutEffect(() => {}, []);
Svelte $effect.pre:
<script>
$effect.pre(() => {});
</script>
5. Props
React:
function Button({ label, onClick, disabled = false }) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
Svelte $props:
<script>
let { label, onclick, disabled = false } = $props();
</script>
<button {onclick} {disabled}>{label}</button>
With TypeScript:
<script lang="ts">
interface Props {
label: string;
onclick: () => void;
disabled?: boolean;
}
let { label, onclick, disabled = false }: Props = $props();
</script>
6. Pure Components
React memo:
const MemoizedComponent = React.memo(Component);
Svelte:
<!-- No equivalent needed - Svelte components are pure by default -->
7. Event Handling
React:
<button onClick={handleClick} onMouseEnter={handleHover}>
Svelte:
<button onclick={handleClick} onmouseenter={handleHover}>
Event object:
<button onclick={(e) => handleClick(e)}>
8. Conditional Rendering
React:
{
isVisible && <Modal />;
}
{
isLoading ? <Spinner /> : <Content />;
}
Svelte:
{#if isVisible}
<Modal />
{/if}
{#if isLoading}
<Spinner />
{:else}
<Content />
{/if}
React ternary with null:
{
condition ? <A /> : null;
}
Svelte:
{#if condition}
<A />
{/if}
9. List Rendering
React:
{
items.map((item, index) => <li key={item.id}>{item.name}</li>);
}
Svelte:
{#each items as item, index (item.id)}
<li>{item.name}</li>
{/each}
With index only:
{#each items as item, i}
<li>{i}: {item.name}</li>
{/each}
10. Context
React:
const ThemeContext = createContext("light");
const theme = useContext(ThemeContext);
Svelte:
<!-- Provider.svelte -->
<script>
import { setContext } from 'svelte';
setContext('theme', 'light');
</script>
<!-- Consumer.svelte -->
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
</script>
11. Refs
React:
const inputRef = useRef(null);
<input ref={inputRef} />;
Svelte:
<script>
let inputRef = $state<HTMLInputElement | null>(null);
</script>
<input bind:this={inputRef} />
12. Forms and Two-Way Binding
React:
const [value, setValue] = useState("");
<input value={value} onChange={(e) => setValue(e.target.value)} />;
Svelte bind:
<script>
let value = $state('');
</script>
<input bind:value />
Checkbox:
<input type="checkbox" bind:checked={isChecked} />
13. Slots vs Children
React:
function Card({ children, header }) {
return (
<div>
<header>{header}</header>
{children}
</div>
);
}
Svelte:
<!-- Card.svelte -->
<div>
<header>{@render header?.()}</header>
{@render children?.()}
</div>
<!-- Usage -->
<Card>
{#snippet header()}
<h1>Title</h1>
{/snippet}
<p>Content</p>
</Card>
14. Styles
React (CSS-in-JS):
<div style={{ color: 'red', fontSize: 14 }}>
Svelte (scoped styles):
<div class="red-text">Content</div>
<style>
.red-text {
color: red;
font-size: 14px;
}
</style>
Dynamic styles:
<div style:color={dynamicColor} style:font-size="{size}px">
Advanced Patterns
Custom Hooks to Functions
React custom hook:
function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = () => setCount((c) => c + 1);
return { count, increment };
}
Svelte:
// counter.svelte.js
export function createCounter(initial = 0) {
let count = $state(initial);
return {
get count() {
return count;
},
increment: () => count++,
};
}
<script>
import { createCounter } from './counter.svelte.js';
const counter = createCounter(0);
</script>
<button onclick={counter.increment}>
Count: {counter.count}
</button>
Higher-Order Components
React HOC:
function withAuth(Component) {
return function Wrapped(props) {
const isAuth = useAuth();
return isAuth ? <Component {...props} /> : <Login />;
};
}
Svelte (use wrapper component):
<!-- WithAuth.svelte -->
<script>
let { component: Component, ...props } = $props();
const isAuth = $derived(checkAuth());
</script>
{#if isAuth}
<Component {...props} />
{:else}
<Login />
{/if}
Accessibility (a11y)
ARIA Attributes
React:
<div aria-label="Close dialog" aria-expanded={isOpen}>
Svelte:
<div aria-label="Close dialog" aria-expanded={isOpen}>
Keyboard Events
React:
<div onKeyDown={handleKeyDown} tabIndex={0} role="button">
Svelte:
<div onkeydown={handleKeyDown} tabindex="0" role="button">
Focus Management
React:
const buttonRef = useRef(null);
useEffect(() => {
buttonRef.current?.focus();
}, []);
Svelte:
<script>
let buttonRef = $state<HTMLButtonElement | null>(null);
$effect(() => {
buttonRef?.focus();
});
</script>
<button bind:this={buttonRef}>Focus me</button>
Screen Reader Announcements
React:
<div aria-live="polite" aria-atomic="true">
{announcement}
</div>
Svelte:
<div aria-live="polite" aria-atomic="true">
{announcement}
</div>
Svelte a11y Warnings
Svelte provides compile-time accessibility warnings:
- Missing alt attributes on images
- Invalid ARIA attributes
- Missing keyboard handlers on interactive elements
- Form elements without labels
Reduced Motion
React:
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Svelte (using CSS):
<style>
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
</style>
Migration Checklist
- Rename file from
.jsx/.tsxto.svelte - Remove React imports
- Convert function to Svelte structure (script/template/style)
- Convert
useStateto$state - Convert
useEffectto$effect - Convert
useMemoto$derived - Convert props to
$props() - Convert event handlers (camelCase to lowercase)
- Convert conditional rendering to
{#if}blocks - Convert
.map()to{#each}blocks - Convert
useContexttogetContext - Convert
useReftobind:this - Add scoped styles
- Review accessibility attributes
- Test component behavior
Common Pitfalls
- Direct mutation in Svelte works – Don’t try to maintain immutability habits from React
- Effects run immediately – Unlike React’s delayed effects,
$effectruns synchronously - Props are read-only – Use callbacks for child-to-parent communication
- No virtual DOM – Don’t rely on React’s reconciliation behavior
- Scoped styles by default – Use
:global()for global styles
Examples
Counter Component
React:
import { useState } from "react";
function Counter({ initial = 0, step = 1 }) {
const [count, setCount] = useState(initial);
return (
<button onClick={() => setCount((c) => c + step)}>Count: {count}</button>
);
}
Svelte:
<script>
let { initial = 0, step = 1 } = $props();
let count = $state(initial);
</script>
<button onclick={() => count += step}>
Count: {count}
</button>
Fetch Data Component
React:
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then((r) => r.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
Svelte:
<script>
let { userId } = $props();
let user = $state(null);
let loading = $state(true);
$effect(() => {
loading = true;
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => {
user = data;
loading = false;
});
});
</script>
{#if loading}
<div>Loading...</div>
{:else}
<div>{user?.name}</div>
{/if}
Success Criteria
- Component renders correctly in Svelte
- All state updates work as expected
- Props are properly typed (if using TypeScript)
- Accessibility attributes are preserved
- Component follows Svelte 5 idioms and best practices