Skip to main content

reactor-getting-started

Reactor essentials in one place — React-to-Reactor mental model, minimal app shape, hooks, the most-used factories, the critical gotchas, and project setup. This is the only skill you need loaded for typical Reactor work; load topical skills (`reactor-async`, `reactor-design`, etc.) only when the task explicitly calls for them.

Quellinformationen

Repository
microsoft/microsoft-ui-reactor
Letzte Quellaktivität
25. September 2026 um 18:07
Erkannte Sprache von SKILL.md
Englisch
Sterne
674
Forks
58

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
reactor-getting-started
description
Reactor essentials in one place — React-to-Reactor mental model, minimal app shape, hooks, the most-used factories, the critical gotchas, and project setup. This is the only skill you need loaded for typical Reactor work; load topical skills (`reactor-async`, `reactor-design`, etc.) only when the task explicitly calls for them.
## Coming from React? Read this first. **Reactor concepts are React's, with one C# spelling.** If you know React, you already know how Reactor works — components render an element tree, hooks manage state and effects, lists need keys, lifting state up is the same. Trust your React intuition for *shape*; verify the *names* against the table below or `references/reactor.api.txt`. | React | Reactor (C#) | |---|---| | `function App() { … }` | `class App : Component { override Element Render() { … } }` | | `useState(0)` | `var (count, setCount) = UseState(0);` | | `useReducer(reduce, init)` | `var (state, dispatch) = UseReducer<TState,TAction>(reduce, init);` | | `useEffect(fn, [dep])` | `UseEffect(fn, dep);` | | `useMemo(() => v, [dep])` | `UseMemo(() => v, dep)` | | `useCallback(fn, [dep])` | `UseCallback(fn, dep)` | | `useRef(v)` | `UseRef(v)` | | callback ref / `ref={node => …}` | `UseElementRef<T>()` + `.Ref(r)` | | `useContext(Ctx)` | `UseContext(Ctx)` | | `<Provider value={x}>{c}</Provider>` | `c.Provide(Ctx, x)` | | `<div>` (linear layout) | `FlexColumn(...)` / `FlexRow(...)` (CSS-flexbox semantics) | | `<div>` (shrink-wrap) | `VStack(...)` / `HStack(...)` | | `<span>text</span>` | `TextBlock("text")` | | `<h1>` / `<h2>` / small caption | `Heading(...)` / `SubHeading(...)` / `Caption(...)` | | `<button onClick={fn}>` | `Button("label", fn)` | | `<input value={v} onChange={e=>…}>` | `TextBox(v, setV)` | | `<select>` | `ComboBox(items, index, setIndex)` | | `<input type="checkbox">` | `CheckBox(checked, setChecked)` | | `{cond && <X/>}` | `cond ? X() : null` (null children are filtered) | | `{a ? <X/> : <Y/>}` | `a ? X() : Y()` | | `{items.map(i => <Card key={i.id} … />)}` | `items.Select(i => Component<Card,…>(…).WithKey(i.id)).ToArray()` | | `<Card {...props} />` | `Component<Card, CardProps>(new CardProps(...))` | | `key={i.id}` | `.WithKey(i.id)` | | `className="..."` (style) | `.Background(...)`, `.Padding(...)`, `.Margin(...)`, etc. | | `style={{margin: 10}}` | `.Margin(10)` | | `display: flex; flex: 1` | `FlexRow(...)` / `.Flex(grow: 1, basis: 0)` | | `gap: 8` | `VStack(8, …)`, `FlexRow(...) with { ColumnGap = 8 }` (FlexColumn → `RowGap`) | | React Query `useQuery` / `useMutation` | `UseResource` / `UseMutation` (see `reactor-async`) | | JSX | C# method calls + `using static Microsoft.UI.Reactor.Factories` | **One important difference:** in Reactor, `UseState` with a `List<T>` won't re-render on `.Add()` — same reference. Use `UseReducer` for lists (just like `useReducer` is preferred in React for complex state). > **Controlled prop note:** the factories shown here still take plain values > (`TextBox(text, setText)`, `Slider(value, ...)`) and wrap them for you. > Element-record properties behind those factories use `Optional<T>` so > `Optional<T>.Unset` can mean "the control owns this value". See > [`migration/050-optional-t.md`](../../../../docs/guide/migration/050-optional-t.md). ## Starting a new app `dotnet new reactor -n <Name>` scaffolds the canonical shape: `App.cs` (entry point + initial component, with the `using` block at the top) plus `<Name>.csproj`. Richer shells: `reactor-mvu`, `reactor-navview`, `reactor-tabview`. See the anti-probe + `mur check` notes under "Use a `.csproj` …" below for what comes out of the scaffold. For a single-file `dotnet run App.cs` demo (no `.csproj`), prepend the file-level `#:package` / `#:property` headers — see `reactor-build-and-check`'s single-file-scripts section. ## Use a `.csproj` when you need … … multiple files, **analyzers** (single-file `.cs` builds don't load them), or shared project references. `dotnet new reactor` scaffolds the canonical csproj — you don't need to author one from scratch. `UseWinUI` MUST be `true`. **No XAML files of any kind.** **Don't change the packaging mode of a project you didn't scaffold — keep whatever the scaffold produced.** Reactor works in both shapes: - **Unpackaged** — `<WindowsPackageType>None</WindowsPackageType>`, no `Package.appxmanifest`. `None` wins over `EnableMsixTooling`, so a project carrying both still builds unpackaged. Runs from any folder, no MSIX registration. Scaffold with `dotnet new reactor` and set this property. - **Packaged** — has a `Package.appxmanifest`. Either omit `WindowsPackageType` entirely (the default produces a packaged app) or set it to `MSIX`; pair it with `EnableMsixTooling=true` for the single-project MSIX tooling. Launches with package identity, which is what identity-gated APIs require. This is what the `dotnet new reactor` family produces. Neither is "the" Reactor shape, and neither is signalled by one property alone. **If a project has a `Package.appxmanifest`, it is packaged on purpose — adding `<WindowsPackageType>None</WindowsPackageType>` to it silently strips its identity, and the build still succeeds, so nothing surfaces the mistake.** Switch modes only when the user asks, and switch the whole property *set*, not a single flag — the `packaging` guide has both shapes in full, including the explicit-`MSIX` form. **After `dotnet new reactor -n <Name>`, the source you edit is `App.cs` (entry point + initial component) next to `<Name>.csproj`.** The packaged templates also scaffold MSIX packaging inputs — `Package.appxmanifest`, `app.manifest`, `Assets/`, `Properties/launchSettings.json`, `Properties/PublishProfiles/`; those are not source — leave them alone. There is no `Program.cs` and no `GlobalUsings.cs` — modify `App.cs` in place. `App.cs` has its own `using` directives at the top — see the *Required imports* section below — and that is where you add new namespaces (e.g. `using System.Linq;` when you reach for `.Select(...)`). Some templates enable implicit usings, so a namespace may already be in scope. Don't probe the `.csproj` after scaffolding unless you're adding a `PackageReference` or changing a property — `Restore succeeded.` in the scaffold stdout is the only confirmation you need. **Scaffolded apps are packaged, so `dotnet run` launches them with MSIX package identity** — the F5 equivalent. That needs **Developer Mode on** (Settings → System → For developers) to register the loose-layout package, and a concrete architecture (the template declares `x86;x64;ARM64`; AnyCPU is rejected). **The scaffolded csproj ships a Debug-only ItemGroup that adds `Microsoft.UI.Reactor.Devtools` + `Reactor.DevtoolsSupport=true`.** `Properties/launchSettings.json` carries three profiles: a default **Package** profile, an **Unpackaged** profile, and an **Unpackaged, Devtools** profile that passes `--devtools` to bring up the devtools menu. Pick the devtools profile in the launch dropdown when you want it. Release builds drop the devtools package and host-config switch so trim / AOT analyzers stay quiet — see the `packaging` guide for the full rationale before flipping either knob. **Verify your edits with `mur check`** before declaring done. From the project directory: `mur check` (no arguments) runs `dotnet build` and emits one compressed line per diagnostic with a `→ try:` suggestion when the engine recognizes the mistake; `mur check --final` is the explicit "I am done iterating" sweep that emits the full diagnostic set including suppressed iteration-mode warnings. For anything more involved than the build/fix loop — strict-mode failures, custom diagnostic gating, MSBuild passthrough flags — load the `reactor-build-and-check` skill. ## Required imports ```csharp using System; using Microsoft.UI.Reactor; using Microsoft.UI.Reactor.Core; using Microsoft.UI.Reactor.Layout; // FlexDirection, FlexJustify, FlexAlign using Microsoft.UI.Xaml; // Thickness, HorizontalAlignment, VerticalAlignment using Microsoft.UI.Xaml.Controls; // Orientation, InfoBarSeverity, etc. using static Microsoft.UI.Reactor.Factories; ``` ## Components ```csharp // Class component (primary) class Counter : Component { public override Element Render() { var (count, setCount) = UseState(0); return VStack(TextBlock($"Count: {count}"), Button("+1", () => setCount(count + 1))); } } // Function component (inline, small reusable pieces) var toggle = Memo(ctx => { var (on, setOn) = ctx.UseState(false); return ToggleSwitch(on, setOn); }); // Typed props — use records for free structural equality record UserCardProps(string Name, string Role); class UserCard : Component<UserCardProps> { public override Element Render() => VStack(TextBlock(Props.Name), Caption(Props.Role)); } Component<UserCard, UserCardProps>(new UserCardProps("Alice", "Admin")) // Memoized function component Memo(ctx => TextBlock("Stable")) // render once + own state Memo(ctx => TextBlock($"Hi, {name}"), name) // re-render when deps change ``` `Component` skips parent-triggered re-renders by default. `Component<TProps>` skips when `Equals(oldProps, newProps)`. **App entry point.** `ReactorApp.Run<MyRoot>("Title", width: W, height: H)` against a component class (the scaffolded form). `width`/`height` are optional DIPs — omit them to let the OS choose the initial window size. For a tiny demo without a class, the inline form: `ReactorApp.Run("Title", ctx => { var (m, setM) = ctx.UseState("hi"); return TextBlock(m); })`. ## Hooks **Rules:** same order every render (no hooks in `if`/`for`), only from `Render()` or function-component body. | Hook | Returns | Use for | |---|---|---| | `UseState<T>(initial)` | `(T, Action<T>)` | Primary state | | `UseReducer<T>(initial)` | `(T, Action<Func<T,T>>)` | State derived from previous (lists) | | `UseReducer<TState,TAction>(reduce, initial)` | `(TState, Action<TAction>)` | Action-style reducer | | `UseEffect(action, deps)` | — | Side effects + cleanup | | `UseMemo<T>(factory, deps)` | `T` | Memoized computation | | `UseCallback(action, deps)` | `Action` | Stable callback reference | | `UseRef<T>(initial)` | `Ref<T>` | Mutable ref across renders (access via `.Current`) | | `UseObservable<T>(source)` | `T` | Track `INotifyPropertyChanged` | | `UseExternalStore<TSnapshot>(subscribe, getSnapshot)` | `TSnapshot` | Subscribe to an external store (stable `subscribe`, cached snapshot) | | `UseCollection<T>(coll)` | `IReadOnlyList<T>` | Track `ObservableCollection` | | `UseContext<T>(ctx)` | `T` | Read tree-scoped ambient state | | `UsePersisted<T>(key, initial)` | `(T, Action<T>)` | State that survives unmount | | `UseResource<T>` / `UseInfiniteResource` / `UseMutation` | (see `reactor-async`) | Async data | | `UseValidationContext()` | `ValidationContext` | (see `reactor-forms`) | | `UseNavigation<TRoute>(initial)` | `NavigationHandle<TRoute>` | (see `reactor-navigation`) | ### UseState <!-- index:use-state --> `UseState<T>(initial)` returns `(value, setValue)`. Calling the setter with a value that differs from the current one schedules a re-render; the new value is visible on the *next* render, not on the line after the call. ```csharp var (count, setCount) = UseState(0); return VStack(8, TextBlock($"Count: {count}"), Button("Increment", () => setCount(count + 1))); ``` Two rules decide whether `UseState` is the right hook: - **Hook order is constant.** Never call a hook inside `if`, `for`, or a nested lambda — call them all unconditionally and use the result conditionally. `REACTOR_HOOKS_001` enforces this. - **Reference types need a new instance.** `UseState(new List<T>())` followed by `list.Add(item)` will not re-render: the reference is unchanged, so the setter never sees a difference. Reach for `UseReducer` instead. Pass `threadSafe: true` when the setter is called from a background thread. <!-- /index:use-state --> ### UseReducer
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen