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.

Informações da origem

Repositório
microsoft/microsoft-ui-reactor
Última atividade na origem
25 de setembro de 2026 às 18:07
Idioma detectado do SKILL.md
inglês
Estrelas
663
Forks
57

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub