[{"data":1,"prerenderedAt":828},["ShallowReactive",2],{"msfe-doc:\u002Fguide\u002Freact":3},{"id":4,"title":5,"body":6,"description":815,"extension":816,"keywords":817,"locale":821,"meta":822,"navigation":87,"order":38,"path":823,"section":824,"seo":825,"stem":826,"__hash__":827},"docs\u002Fguide\u002Freact.zh-CN.md","React 接入",{"type":7,"value":8,"toc":808},"minimark",[9,13,25,204,207,230,420,424,436,439,443,450,768,795,798,801,804],[10,11,12],"h2",{"id":12},"最小接入",[14,15,16,20,21,24],"p",{},[17,18,19],"code",{},"MsfeEditorContent"," 负责挂载和卸载，",[17,22,23],{},"onReady"," 提供当前实例。options 是初始配置，更新已经挂载的编辑器时调用实例方法。",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-tsx shiki shiki-themes github-light github-light github-dark","import { MsfeEditorContent } from '@quirekit\u002Fmsfe-react'\nimport { StarterKit } from '@quirekit\u002Fmsfe-starter-kit'\nimport type { ReactEditorOptions } from '@quirekit\u002Fmsfe-react'\n\nconst options: ReactEditorOptions = {\n  markdown: '# Hello, Markdown',\n  mode: 'live',\n  extensions: [StarterKit],\n}\n\n\u002F** @returns 当前应用中的编辑器组件。 *\u002F\nexport function EditorExample() {\n  return \u003CMsfeEditorContent options={options} \u002F>\n}\n","tsx","",[17,33,34,54,67,82,89,112,124,135,141,147,152,165,180,199],{"__ignoreMap":31},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sDD9d","import",[35,44,46],{"class":45},"sVPnS"," { MsfeEditorContent } ",[35,48,49],{"class":41},"from",[35,51,53],{"class":52},"s-HEn"," '@quirekit\u002Fmsfe-react'\n",[35,55,57,59,62,64],{"class":37,"line":56},2,[35,58,42],{"class":41},[35,60,61],{"class":45}," { StarterKit } ",[35,63,49],{"class":41},[35,65,66],{"class":52}," '@quirekit\u002Fmsfe-starter-kit'\n",[35,68,70,72,75,78,80],{"class":37,"line":69},3,[35,71,42],{"class":41},[35,73,74],{"class":41}," type",[35,76,77],{"class":45}," { ReactEditorOptions } ",[35,79,49],{"class":41},[35,81,53],{"class":52},[35,83,85],{"class":37,"line":84},4,[35,86,88],{"emptyLinePlaceholder":87},true,"\n",[35,90,92,95,99,102,106,109],{"class":37,"line":91},5,[35,93,94],{"class":41},"const",[35,96,98],{"class":97},"sUNQB"," options",[35,100,101],{"class":41},":",[35,103,105],{"class":104},"sgQzM"," ReactEditorOptions",[35,107,108],{"class":41}," =",[35,110,111],{"class":45}," {\n",[35,113,115,118,121],{"class":37,"line":114},6,[35,116,117],{"class":45},"  markdown: ",[35,119,120],{"class":52},"'# Hello, Markdown'",[35,122,123],{"class":45},",\n",[35,125,127,130,133],{"class":37,"line":126},7,[35,128,129],{"class":45},"  mode: ",[35,131,132],{"class":52},"'live'",[35,134,123],{"class":45},[35,136,138],{"class":37,"line":137},8,[35,139,140],{"class":45},"  extensions: [StarterKit],\n",[35,142,144],{"class":37,"line":143},9,[35,145,146],{"class":45},"}\n",[35,148,150],{"class":37,"line":149},10,[35,151,88],{"emptyLinePlaceholder":87},[35,153,155,159,162],{"class":37,"line":154},11,[35,156,158],{"class":157},"sqmzS","\u002F** ",[35,160,161],{"class":41},"@returns",[35,163,164],{"class":157}," 当前应用中的编辑器组件。 *\u002F\n",[35,166,168,171,174,177],{"class":37,"line":167},12,[35,169,170],{"class":41},"export",[35,172,173],{"class":41}," function",[35,175,176],{"class":104}," EditorExample",[35,178,179],{"class":45},"() {\n",[35,181,183,186,189,191,193,196],{"class":37,"line":182},13,[35,184,185],{"class":41},"  return",[35,187,188],{"class":45}," \u003C",[35,190,19],{"class":97},[35,192,98],{"class":104},[35,194,195],{"class":41},"=",[35,197,198],{"class":45},"{options} \u002F>\n",[35,200,202],{"class":37,"line":201},14,[35,203,146],{"class":45},[10,205,206],{"id":206},"自定义宿主布局",[14,208,209,210,213,214,217,218,221,222,225,226,229],{},"需要自行放置挂载元素时，使用 ",[17,211,212],{},"useMsfeEditor(parentRef, optionsFactory)","。它返回当前 session 或 ",[17,215,216],{},"null","；session 包含 ",[17,219,220],{},"editor"," 和 ",[17,223,224],{},"host","。将 ",[17,227,228],{},"ReactNodeViewHost"," 放在原有 Provider 树内。",[26,231,233],{"className":28,"code":232,"language":30,"meta":31,"style":31},"import { useRef } from 'react'\nimport { useMsfeEditor, ReactNodeViewHost } from '@quirekit\u002Fmsfe-react'\nimport { StarterKit } from '@quirekit\u002Fmsfe-starter-kit'\n\n\u002F** @returns 保留 Context 的编辑器与组件视图宿主。 *\u002F\nexport function EditorWithHost() {\n  const parent = useRef\u003CHTMLDivElement>(null)\n  const session = useMsfeEditor(parent, () => ({\n    markdown: '开始编辑。',\n    extensions: [StarterKit],\n  }))\n\n  return \u003C>\n    \u003Cdiv ref={parent} \u002F>\n    {session && \u003CReactNodeViewHost host={session.host} \u002F>}\n  \u003C\u002F>\n}\n",[17,234,235,247,258,268,272,281,292,319,340,350,355,360,364,371,388,409,415],{"__ignoreMap":31},[35,236,237,239,242,244],{"class":37,"line":38},[35,238,42],{"class":41},[35,240,241],{"class":45}," { useRef } ",[35,243,49],{"class":41},[35,245,246],{"class":52}," 'react'\n",[35,248,249,251,254,256],{"class":37,"line":56},[35,250,42],{"class":41},[35,252,253],{"class":45}," { useMsfeEditor, ReactNodeViewHost } ",[35,255,49],{"class":41},[35,257,53],{"class":52},[35,259,260,262,264,266],{"class":37,"line":69},[35,261,42],{"class":41},[35,263,61],{"class":45},[35,265,49],{"class":41},[35,267,66],{"class":52},[35,269,270],{"class":37,"line":84},[35,271,88],{"emptyLinePlaceholder":87},[35,273,274,276,278],{"class":37,"line":91},[35,275,158],{"class":157},[35,277,161],{"class":41},[35,279,280],{"class":157}," 保留 Context 的编辑器与组件视图宿主。 *\u002F\n",[35,282,283,285,287,290],{"class":37,"line":114},[35,284,170],{"class":41},[35,286,173],{"class":41},[35,288,289],{"class":104}," EditorWithHost",[35,291,179],{"class":45},[35,293,294,297,300,302,305,308,311,314,316],{"class":37,"line":126},[35,295,296],{"class":41},"  const",[35,298,299],{"class":97}," parent",[35,301,108],{"class":41},[35,303,304],{"class":104}," useRef",[35,306,307],{"class":45},"\u003C",[35,309,310],{"class":104},"HTMLDivElement",[35,312,313],{"class":45},">(",[35,315,216],{"class":97},[35,317,318],{"class":45},")\n",[35,320,321,323,326,328,331,334,337],{"class":37,"line":137},[35,322,296],{"class":41},[35,324,325],{"class":97}," session",[35,327,108],{"class":41},[35,329,330],{"class":104}," useMsfeEditor",[35,332,333],{"class":45},"(parent, () ",[35,335,336],{"class":41},"=>",[35,338,339],{"class":45}," ({\n",[35,341,342,345,348],{"class":37,"line":143},[35,343,344],{"class":45},"    markdown: ",[35,346,347],{"class":52},"'开始编辑。'",[35,349,123],{"class":45},[35,351,352],{"class":37,"line":149},[35,353,354],{"class":45},"    extensions: [StarterKit],\n",[35,356,357],{"class":37,"line":154},[35,358,359],{"class":45},"  }))\n",[35,361,362],{"class":37,"line":167},[35,363,88],{"emptyLinePlaceholder":87},[35,365,366,368],{"class":37,"line":182},[35,367,185],{"class":41},[35,369,370],{"class":45}," \u003C>\n",[35,372,373,376,380,383,385],{"class":37,"line":201},[35,374,375],{"class":45},"    \u003C",[35,377,379],{"class":378},"su0ou","div",[35,381,382],{"class":104}," ref",[35,384,195],{"class":41},[35,386,387],{"class":45},"{parent} \u002F>\n",[35,389,391,394,397,399,401,404,406],{"class":37,"line":390},15,[35,392,393],{"class":45},"    {session ",[35,395,396],{"class":41},"&&",[35,398,188],{"class":45},[35,400,228],{"class":97},[35,402,403],{"class":104}," host",[35,405,195],{"class":41},[35,407,408],{"class":45},"{session.host} \u002F>}\n",[35,410,412],{"class":37,"line":411},16,[35,413,414],{"class":45},"  \u003C\u002F>\n",[35,416,418],{"class":37,"line":417},17,[35,419,146],{"class":45},[10,421,423],{"id":422},"context-与-strictmode","Context 与 StrictMode",[14,425,426,221,429,432,433,435],{},[17,427,428],{},"ReactNodeViewRenderer()",[17,430,431],{},"ReactComponentViewRenderer()"," 使用同一宿主内的 Portal。开发 StrictMode 会重新执行 effect 生命周期，",[17,434,23],{}," 也可能再次调用；宿主不要把它当作只会执行一次的业务动作。",[14,437,438],{},"QuireKit 当前的公共组件库提供 Vue 组件。React 适配器允许使用应用自己的 React UI，字段和事务协议保持一致。",[10,440,442],{"id":441},"react-nodeview-的实现形状","React NodeView 的实现形状",[14,444,445,446,449],{},"组件接收 ",[17,447,448],{},"nodeView","，字段 codec 与 Vue 共用。不要用 createRoot 给每个节点挂一棵独立 React 树；使用适配器的 Portal Host 保留 Context。",[26,451,453],{"className":28,"code":452,"language":30,"meta":31,"style":31},"import type { MsfeNodeViewProps } from '@quirekit\u002Fmsfe-core'\nimport { MsfeEditorContent, ReactNodeViewRenderer } from '@quirekit\u002Fmsfe-react'\nimport { StarterKit } from '@quirekit\u002Fmsfe-starter-kit'\nimport { Status } from '.\u002Fstatus'\n\ninterface StatusAttributes { readonly value: 'todo' | 'done' }\nfunction StatusView({ nodeView }: { nodeView: MsfeNodeViewProps\u003CStatusAttributes> }) {\n  return \u003Cbutton disabled={nodeView.readOnly} onClick={() => nodeView.updateAttributes({\n    value: nodeView.attrs.value === 'done' ? 'todo' : 'done',\n  })}>{nodeView.attrs.value}\u003C\u002Fbutton>\n}\n\u002F\u002F Status is the framework-independent extension from the custom-node tutorial.\nconst StatusWithUI = Status.extend({\n  addNodeView() { return ReactNodeViewRenderer(StatusView) },\n})\n\n\u002F** @returns A mounted editor using the custom React node view. *\u002F\nexport function StatusEditor() {\n  return \u003CMsfeEditorContent options={{\n    markdown: '# Status\\n\\n[[todo]]', mode: 'live',\n    extensions: [StarterKit, StatusWithUI],\n  }} \u002F>\n}\n",[17,454,455,469,480,490,502,506,538,573,609,631,641,645,650,667,684,689,693,702,714,730,751,757,763],{"__ignoreMap":31},[35,456,457,459,461,464,466],{"class":37,"line":38},[35,458,42],{"class":41},[35,460,74],{"class":41},[35,462,463],{"class":45}," { MsfeNodeViewProps } ",[35,465,49],{"class":41},[35,467,468],{"class":52}," '@quirekit\u002Fmsfe-core'\n",[35,470,471,473,476,478],{"class":37,"line":56},[35,472,42],{"class":41},[35,474,475],{"class":45}," { MsfeEditorContent, ReactNodeViewRenderer } ",[35,477,49],{"class":41},[35,479,53],{"class":52},[35,481,482,484,486,488],{"class":37,"line":69},[35,483,42],{"class":41},[35,485,61],{"class":45},[35,487,49],{"class":41},[35,489,66],{"class":52},[35,491,492,494,497,499],{"class":37,"line":84},[35,493,42],{"class":41},[35,495,496],{"class":45}," { Status } ",[35,498,49],{"class":41},[35,500,501],{"class":52}," '.\u002Fstatus'\n",[35,503,504],{"class":37,"line":91},[35,505,88],{"emptyLinePlaceholder":87},[35,507,508,511,514,517,520,524,526,529,532,535],{"class":37,"line":114},[35,509,510],{"class":41},"interface",[35,512,513],{"class":104}," StatusAttributes",[35,515,516],{"class":45}," { ",[35,518,519],{"class":41},"readonly",[35,521,523],{"class":522},"sde8i"," value",[35,525,101],{"class":41},[35,527,528],{"class":52}," 'todo'",[35,530,531],{"class":41}," |",[35,533,534],{"class":52}," 'done'",[35,536,537],{"class":45}," }\n",[35,539,540,543,546,549,551,554,556,558,560,562,565,567,570],{"class":37,"line":126},[35,541,542],{"class":41},"function",[35,544,545],{"class":104}," StatusView",[35,547,548],{"class":45},"({ ",[35,550,448],{"class":522},[35,552,553],{"class":45}," }",[35,555,101],{"class":41},[35,557,516],{"class":45},[35,559,448],{"class":522},[35,561,101],{"class":41},[35,563,564],{"class":104}," MsfeNodeViewProps",[35,566,307],{"class":45},[35,568,569],{"class":104},"StatusAttributes",[35,571,572],{"class":45},"> }) {\n",[35,574,575,577,579,582,585,587,590,593,595,598,600,603,606],{"class":37,"line":137},[35,576,185],{"class":41},[35,578,188],{"class":45},[35,580,581],{"class":378},"button",[35,583,584],{"class":104}," disabled",[35,586,195],{"class":41},[35,588,589],{"class":45},"{nodeView.readOnly} ",[35,591,592],{"class":104},"onClick",[35,594,195],{"class":41},[35,596,597],{"class":45},"{() ",[35,599,336],{"class":41},[35,601,602],{"class":45}," nodeView.",[35,604,605],{"class":104},"updateAttributes",[35,607,608],{"class":45},"({\n",[35,610,611,614,617,619,622,624,627,629],{"class":37,"line":143},[35,612,613],{"class":45},"    value: nodeView.attrs.value ",[35,615,616],{"class":41},"===",[35,618,534],{"class":52},[35,620,621],{"class":41}," ?",[35,623,528],{"class":52},[35,625,626],{"class":41}," :",[35,628,534],{"class":52},[35,630,123],{"class":45},[35,632,633,636,638],{"class":37,"line":149},[35,634,635],{"class":45},"  })}>{nodeView.attrs.value}\u003C\u002F",[35,637,581],{"class":378},[35,639,640],{"class":45},">\n",[35,642,643],{"class":37,"line":154},[35,644,146],{"class":45},[35,646,647],{"class":37,"line":167},[35,648,649],{"class":157},"\u002F\u002F Status is the framework-independent extension from the custom-node tutorial.\n",[35,651,652,654,657,659,662,665],{"class":37,"line":182},[35,653,94],{"class":41},[35,655,656],{"class":97}," StatusWithUI",[35,658,108],{"class":41},[35,660,661],{"class":45}," Status.",[35,663,664],{"class":104},"extend",[35,666,608],{"class":45},[35,668,669,672,675,678,681],{"class":37,"line":201},[35,670,671],{"class":104},"  addNodeView",[35,673,674],{"class":45},"() { ",[35,676,677],{"class":41},"return",[35,679,680],{"class":104}," ReactNodeViewRenderer",[35,682,683],{"class":45},"(StatusView) },\n",[35,685,686],{"class":37,"line":390},[35,687,688],{"class":45},"})\n",[35,690,691],{"class":37,"line":411},[35,692,88],{"emptyLinePlaceholder":87},[35,694,695,697,699],{"class":37,"line":417},[35,696,158],{"class":157},[35,698,161],{"class":41},[35,700,701],{"class":157}," A mounted editor using the custom React node view. *\u002F\n",[35,703,705,707,709,712],{"class":37,"line":704},18,[35,706,170],{"class":41},[35,708,173],{"class":41},[35,710,711],{"class":104}," StatusEditor",[35,713,179],{"class":45},[35,715,717,719,721,723,725,727],{"class":37,"line":716},19,[35,718,185],{"class":41},[35,720,188],{"class":45},[35,722,19],{"class":97},[35,724,98],{"class":104},[35,726,195],{"class":41},[35,728,729],{"class":45},"{{\n",[35,731,733,735,738,741,744,747,749],{"class":37,"line":732},20,[35,734,344],{"class":45},[35,736,737],{"class":52},"'# Status",[35,739,740],{"class":97},"\\n\\n",[35,742,743],{"class":52},"[[todo]]'",[35,745,746],{"class":45},", mode: ",[35,748,132],{"class":52},[35,750,123],{"class":45},[35,752,754],{"class":37,"line":753},21,[35,755,756],{"class":45},"    extensions: [StarterKit, StatusWithUI],\n",[35,758,760],{"class":37,"line":759},22,[35,761,762],{"class":45},"  }} \u002F>\n",[35,764,766],{"class":37,"line":765},23,[35,767,146],{"class":45},[14,769,770,771,776,777,221,780,783,784,787,788,791,792,794],{},"先从 ",[772,773,775],"a",{"href":774},"\u002Fextensions\u002Fcustom-node","节点教程"," 的接入源码复制与框架无关的 ",[17,778,779],{},"status.ts",[17,781,782],{},"types.ts"," 到此组件旁。生产界面将示意原生 button 换成你的 React 组件库控件。通用控件则使用 ",[17,785,786],{},"ReactComponentViewRenderer","，组件接收 ",[17,789,790],{},"view","，不混用 ",[17,793,448],{}," 协议。",[10,796,797],{"id":797},"更新和清理",[14,799,800],{},"保存、模式切换、外部增量与 Vue 使用相同实例 API。不要把 onChange 得到的全文回写 options 造成 remount；同时避免在 effect 中捕获过期 editor。每次 session 改变时重新建立宿主订阅并返回清理。",[14,802,803],{},"React 示例代码展示的是公共接入形状；本站的交互示例运行于 Vue 文档应用。消费项目仍应覆盖自己的 StrictMode、SSR 和路由生命周期。",[805,806,807],"style",{},"html pre.shiki code .sDD9d, html code.shiki .sDD9d{--shiki-default:#D73A49;--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVPnS, html code.shiki .sVPnS{--shiki-default:#24292E;--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s-HEn, html code.shiki .s-HEn{--shiki-default:#032F62;--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sUNQB, html code.shiki .sUNQB{--shiki-default:#005CC5;--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sgQzM, html code.shiki .sgQzM{--shiki-default:#6F42C1;--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sqmzS, html code.shiki .sqmzS{--shiki-default:#6A737D;--shiki-light:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .su0ou, html code.shiki .su0ou{--shiki-default:#22863A;--shiki-light:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sde8i, html code.shiki .sde8i{--shiki-default:#E36209;--shiki-light:#E36209;--shiki-dark:#FFAB70}",{"title":31,"searchDepth":56,"depth":56,"links":809},[810,811,812,813,814],{"id":12,"depth":56,"text":12},{"id":206,"depth":56,"text":206},{"id":422,"depth":56,"text":423},{"id":441,"depth":56,"text":442},{"id":797,"depth":56,"text":797},"使用 React 适配器挂载编辑器与 Portal，保持同一应用的 Context。","md",[818,819,820],"React","StrictMode","Portal","zh-CN",{},"\u002Fguide\u002Freact","接入指南",{"title":5,"description":815},"guide\u002Freact.zh-CN","Y4khlE8R4VzvHgXFufiv9Saq6Ij3jYbrI3PcDMqCVeo",1793046311417]