# React 接入

使用 React 适配器挂载编辑器与 Portal，保持同一应用的 Context。

## 最小接入

`MsfeEditorContent` 负责挂载和卸载，`onReady` 提供当前实例。options 是初始配置，更新已经挂载的编辑器时调用实例方法。

```tsx
import { MsfeEditorContent } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'
import type { ReactEditorOptions } from '@quirekit/msfe-react'

const options: ReactEditorOptions = {
  markdown: '# Hello, Markdown',
  mode: 'live',
  extensions: [StarterKit],
}

/** @returns 当前应用中的编辑器组件。 */
export function EditorExample() {
  return <MsfeEditorContent options={options} />
}
```

## 自定义宿主布局

需要自行放置挂载元素时，使用 `useMsfeEditor(parentRef, optionsFactory)`。它返回当前 session 或 `null`；session 包含 `editor` 和 `host`。将 `ReactNodeViewHost` 放在原有 Provider 树内。

```tsx
import { useRef } from 'react'
import { useMsfeEditor, ReactNodeViewHost } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'

/** @returns 保留 Context 的编辑器与组件视图宿主。 */
export function EditorWithHost() {
  const parent = useRef<HTMLDivElement>(null)
  const session = useMsfeEditor(parent, () => ({
    markdown: '开始编辑。',
    extensions: [StarterKit],
  }))

  return <>
    <div ref={parent} />
    {session && <ReactNodeViewHost host={session.host} />}
  </>
}
```

## Context 与 StrictMode

`ReactNodeViewRenderer()` 和 `ReactComponentViewRenderer()` 使用同一宿主内的 Portal。开发 StrictMode 会重新执行 effect 生命周期，`onReady` 也可能再次调用；宿主不要把它当作只会执行一次的业务动作。

QuireKit 当前的公共组件库提供 Vue 组件。React 适配器允许使用应用自己的 React UI，字段和事务协议保持一致。

## React NodeView 的实现形状

组件接收 `nodeView`，字段 codec 与 Vue 共用。不要用 createRoot 给每个节点挂一棵独立 React 树；使用适配器的 Portal Host 保留 Context。

~~~tsx
import type { MsfeNodeViewProps } from '@quirekit/msfe-core'
import { MsfeEditorContent, ReactNodeViewRenderer } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'
import { Status } from './status'

interface StatusAttributes { readonly value: 'todo' | 'done' }
function StatusView({ nodeView }: { nodeView: MsfeNodeViewProps<StatusAttributes> }) {
  return <button disabled={nodeView.readOnly} onClick={() => nodeView.updateAttributes({
    value: nodeView.attrs.value === 'done' ? 'todo' : 'done',
  })}>{nodeView.attrs.value}</button>
}
// Status is the framework-independent extension from the custom-node tutorial.
const StatusWithUI = Status.extend({
  addNodeView() { return ReactNodeViewRenderer(StatusView) },
})

/** @returns A mounted editor using the custom React node view. */
export function StatusEditor() {
  return <MsfeEditorContent options={{
    markdown: '# Status\n\n[[todo]]', mode: 'live',
    extensions: [StarterKit, StatusWithUI],
  }} />
}
~~~

先从 [节点教程](/extensions/custom-node) 的接入源码复制与框架无关的 `status.ts` 和 `types.ts` 到此组件旁。生产界面将示意原生 button 换成你的 React 组件库控件。通用控件则使用 `ReactComponentViewRenderer`，组件接收 `view`，不混用 `nodeView` 协议。

## 更新和清理

保存、模式切换、外部增量与 Vue 使用相同实例 API。不要把 onChange 得到的全文回写 options 造成 remount；同时避免在 effect 中捕获过期 editor。每次 session 改变时重新建立宿主订阅并返回清理。

React 示例代码展示的是公共接入形状；本站的交互示例运行于 Vue 文档应用。消费项目仍应覆盖自己的 StrictMode、SSR 和路由生命周期。
