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.

소스 정보

저장소
microsoft/microsoft-ui-reactor
최근 소스 활동
2026년 9월 25일 18:07
감지된 SKILL.md 언어
영어
스타
663
포크
57

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기