// value is the react model we pass // this is in server side so we have request context exportfunctionresolveModelToJSON(request, parent, key, value) {
while ( typeof value === 'object' && value !== null && value.$$typeof === REACT_ELEMENT_TYPE ) { constelement: React$Element<any> = value; // server component or plain html element try { value = attemptResolveElement( element.type, element.key, element.ref, element.props, ); } } // client side component if (isModuleReference(value)) { // get file path like "./src/ClientComponent.client.js" // name is the file export, e.g. name: "default" -> ClientComponent is the default export... constmoduleMetaData: ModuleMetaData = resolveModuleMetaData( request.bundlerConfig, moduleReference, ); // placeholder++ request.pendingChunks++; // assign an ID for this module const moduleId = request.nextChunkId++; // add module meta data to chunk emitModuleChunk(request, moduleId, moduleMetaData); // cache writtenModules.set(moduleKey, moduleId); // return id as a reference returnserializeByValueID(moduleId); } }
functionattemptResolveElement( type: any, key: null | React$Key, ref: mixed, props: any, ): ReactModel { // ... if (typeoftype === 'function') { // This is a server-side component. // render it directly using props and return the result returntype(props); } elseif (typeoftype === 'string') { // This is a host element. E.g. HTML. // It is already serializable, return it directly return [REACT_ELEMENT_TYPE, type, key, props]; } // client component, leave it here // This might be a built-in React component. We'll let the client decide. // Any built-in works as long as its props are serializable. return [REACT_ELEMENT_TYPE, type, key, props]; // ... }
{ $$typeof: Symbol(react.element), // The type field now has a reference object, // instead of the actual component function type: { $$typeof: Symbol(react.module.reference), // ClientComponent is the default export... name: "default", // from this file! filename: "./src/ClientComponent.client.js" }, props: { children: "oh my" }, }
// The Suspense component allows us to wait for the DataFetchingComponent to load // while showing some fallback content. // When the DataFetchingComponent is loaded, it will replace the fallback content. functionDataFetchingComponent() { const [data, setData] = useState(null);
// Fetch data on component mount useEffect(() => { fetchData().then(data =>setData(data)); }, []);
// If data is not yet fetched, return null if (!data) returnnull;
return<div>{data}</div>; }
functionApp() { return ( // Wrap the data fetching component inside the Suspense component <Suspensefallback={<div>Loading...</div>}> <DataFetchingComponent /> </Suspense> ); }
M1:{"id":"./src/ClientComponent.client.js","chunks":["client1"],"name":""} J0:["$","@1",null,{"children":["$","span",null,{"children":"Hello from server land"}]}]
以 M 开头的行定义了一个客户端组件的模块引用,包含在客户端 bundle 中查找组件函数所需的信息。
M1:{"id":"./src/ClientComponent.client.js","chunks":["client1"],"name":""} S2:"react.suspense" J0:["$","@1",null,{"children":[["$","span",null,{"children":"Hello from server land"}],["$","$2",null,{"fallback":"Loading tweets...","children":"@3"}]]}] M4:{"id":"./src/Tweet.client.js","chunks":["client8"],"name":""} J3:["$","ul",null,{"children":[["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}],["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}]]}]
import { createFromFetch } from'react-server-dom-webpack' functionClientRootComponent() { // fetch() from our RSC API endpoint. react-server-dom-webpack // can then take the fetch result and reconstruct the React // element tree const response = createFromFetch(fetch('/rsc?...')) return<Suspensefallback={null}>{response.readRoot() /* Returns a React element! */}</Suspense> }