成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我們聊聊從頭學(xué)服務(wù)器組件:在導(dǎo)航間保留狀態(tài)

開發(fā) 前端
本文,我們?yōu)榉?wù)端增加了返回 JSX 數(shù)據(jù)的支持,并使用 React 在客戶端進(jìn)行消費(fèi),實(shí)現(xiàn)基于 JSX 結(jié)構(gòu)的頁面初始化和頁面局部更新。出于安全考慮,React 要求 JSX 節(jié)點(diǎn)中需要包含一個(gè) $$typeof: Symbol.for("react.element")? 屬性。為此,我們?cè)谛蛄谢徒馕龅臅r(shí)候,對(duì) $$typeof 做了特殊的轉(zhuǎn)換處理。

存在的問題

到目前為止,訪問每個(gè)頁面,服務(wù)器返回的都是一個(gè) HTML 字符串:

async function sendHTML(res, jsx) {
  const html = await renderJSXToHTML(jsx);
  res.setHeader("Content-Type", "text/html");
  res.end(html);
}

這對(duì)瀏覽器首次加載是非常友好的——瀏覽器有針對(duì) HTML 渲染做針對(duì)性優(yōu)化,會(huì)盡可能快地展示——但對(duì)于頁面導(dǎo)航就不太理想了。我們希望頁面支持布局刷新,即只更新 "發(fā)生變化的部分",頁面其他部分不受影響,也讓交互變得流暢了。

為了說明這個(gè)問題,我們向 BlogLayout 組件的 <nav> 中添加一個(gè) <input />:

<nav>
  <a href="/">Home</a>
  <hr />
  <input />
  <hr />
</nav>

請(qǐng)注意,每次瀏覽博客時(shí),輸入框里的內(nèi)容(或叫“狀態(tài)”)都會(huì)消失:

圖片圖片

這對(duì)一個(gè)簡(jiǎn)單的博客來說可能沒有問題,但如果要構(gòu)建具有復(fù)雜交互的應(yīng)用程序,這種行為在某些時(shí)候就會(huì)讓人頭疼。因此,你需要讓用戶在應(yīng)用程序中隨意瀏覽時(shí),不會(huì)丟失頁面狀態(tài)。

接下來,我們將分 3 步驟來解決這個(gè)問題:

  1. 添加一些客戶端 JS 邏輯攔截導(dǎo)航,修改默認(rèn)行為(這樣我們就可以手動(dòng)重新獲取內(nèi)容,而無需重新加載整個(gè)頁面)
  2. 修改服務(wù)端代碼,支持在隨后的導(dǎo)航中需要提供返回 JSX 響應(yīng)(而不是 HTML)的支持
  3. 修改客戶端代碼,在不破壞 DOM 的情況下執(zhí)行 JSX 更新(提示:這部分將使用 React)

局部更新支持

步驟 1:攔截導(dǎo)航

我們需要一些客戶端邏輯,因此增加一個(gè) client.js 文件。先修改服務(wù)端代碼(即 server.js),增加靜態(tài)資源的路由請(qǐng)求支持(即"/client.js")。

createServer(async (req, res) => {
  try {
    const url = new URL(req.url, `http://${req.headers.host}`);
    // 增加靜態(tài)資源 "/client.js" 的路由支持
    if (url.pathname === "/client.js") {
      await sendScript(res, "./client.js"); // 返回服務(wù)端本地的 client.js 文件
    } else {
      await sendHTML(res, <Router url={url} />);
    }
  } catch (err) {
    // ...
  }
}).listen(8080)

async function sendScript(res, filename) {
  const content = await readFile(filename, "utf8");
  res.setHeader("Content-Type", "text/javascript");
  res.end(content);
}

另外,在 sendHTML() 中為響應(yīng)的 HTML  文本增加  <script> 標(biāo)簽,引用 client.js 文件。

async function sendHTML(res, jsx) {
  let html = await renderJSXToHTML(jsx);
  html += `<script type="module" src="/client.js"></script>`;
  res.setHeader("Content-Type", "text/html");
  res.end(html);
}

現(xiàn)在說回 client.js。在這個(gè)文件中,我們會(huì)覆蓋網(wǎng)站內(nèi)導(dǎo)航的默認(rèn)行為(譯注:此處指<a> 標(biāo)簽鏈接能力),改調(diào)我們自己的 navigate() 函數(shù):

async function navigate(pathname) {
  // TODO
}

window.addEventListener("click", (e) => {
  // 只處理 <a> 標(biāo)簽的點(diǎn)擊事件
  if (e.target.tagName !== "A") {
    return;
  }
  // Ignore "open in a new tab".
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
    return;
  }
  // Ignore external URLs.
  const href = e.target.getAttribute("href");
  if (!href.startsWith("/")) {
    return;
  }
  // 這里是核心代碼。首先,阻止 <a> 標(biāo)簽?zāi)J(rèn)的跳轉(zhuǎn)行為
  e.preventDefault();
  // 然后,將跳轉(zhuǎn)地址推入瀏覽歷史,瀏覽器地址欄的地址也同步更新
  window.history.pushState(null, null, href);
  // 最后,執(zhí)行自定義的導(dǎo)航行為
  navigate(href);
}, true);

window.addEventListener("popstate", () => {
  // When the user presses Back/Forward, call our custom logic too.
  navigate(window.location.pathname);
});

navigate() 函數(shù)就是編寫自定義導(dǎo)航行為的地方。在 navigate() 函數(shù)中,我們使用 fetch API 請(qǐng)求下一個(gè)路由的 HTML 響應(yīng),并使用這個(gè)響應(yīng)更新頁面:

let currentPathname = window.location.pathname;

async function navigate(pathname) {
  currentPathname = pathname;
  // 獲取當(dāng)前導(dǎo)航路由下的 HTML 數(shù)據(jù)
  const response = await fetch(pathname);
  const html = await response.text();

  if (pathname === currentPathname) {
    // 獲取 HTML 響應(yīng)數(shù)據(jù) <body> 標(biāo)簽里的內(nèi)容
    const bodyStartIndex = html.indexOf("<body>") + "<body>".length;
    const bodyEndIndex = html.lastIndexOf("</body>");
    const bodyHTML = html.slice(bodyStartIndex, bodyEndIndex);

    // 將拿到的 HTML 數(shù)據(jù)替換掉當(dāng)前 <body> 里的內(nèi)容
    document.body.innerHTML = bodyHTML;
  }
}

點(diǎn)擊這里的 線上 demo[7] 查看效果。

可以看到,在頁面不刷新的情況下,頁面內(nèi)容被替換了,表示我們已經(jīng)成功覆蓋了瀏覽器默認(rèn)導(dǎo)航行為。不過,由于目前是針對(duì) <body> 元素的整體覆蓋,所以<input> 里的數(shù)據(jù)仍然會(huì)丟失。接下來,我們會(huì)修改服務(wù)器部分,增加返回 JSX 響應(yīng)的支持。

步驟 2:增加返回 JSX 響應(yīng)的支持

還記得我們之前使用 JSX 組成的對(duì)象樹結(jié)構(gòu)吧?

{
  $$typeof: Symbol.for("react.element"),
  type: 'html',
  props: {
    children: [
      {
        $$typeof: Symbol.for("react.element"),
        type: 'head',
        props: {
          // ... And so on ...

接下來,我們將為服務(wù)器增加以 ?jsx 結(jié)尾的請(qǐng)求支持。?jsx 請(qǐng)求返回的是一個(gè) JSX 樹結(jié)構(gòu),而不是 HTML,這更容易讓客戶端確定哪些部分發(fā)生了變化,并只在必要時(shí)更新 DOM,這將直接解決 <input> 狀態(tài)在每次導(dǎo)航時(shí)丟失的問題,但這并不是我們這樣做的唯一原因。接下來,你將看到我們?nèi)绾螌⑿滦畔ⅲú粌H僅是 HTML)從服務(wù)器傳遞到客戶端。

首先修改服務(wù)器代碼,支持請(qǐng)求中攜帶 ?jsx 參數(shù)時(shí)調(diào)用一個(gè)新的 sendJSX() 函數(shù):

createServer(async (req, res) => {
  try {
    const url = new URL(req.url, `http://${req.headers.host}`);
    if (url.pathname === "/client.js") {
      // ...
    } else if (url.searchParams.has("jsx")) {
      url.searchParams.delete("jsx"); // 確保傳遞給 <Router> 的 url 是干凈的
      await sendJSX(res, <Router url={url} />); // 返回 JSX 數(shù)據(jù)
    } else {
      await sendHTML(res, <Router url={url} />);
    }
    // ...

在 sendJSX() 中,我們使用 JSON.stringify(jsx) 把的 JSX 樹轉(zhuǎn)換成一個(gè) JSON 字符串,并通過網(wǎng)絡(luò)返回:

async function sendJSX(res, jsx) {
  const jsxString = JSON.stringify(jsx, null, 2); // Indent with two spaces.
  res.setHeader("Content-Type", "application/json");
  res.end(jsxString);
}

要注意的是,我們并不是發(fā)送 JSX 語法本身(如 "<Foo />" ),只是將 JSX 生成的樹結(jié)構(gòu)轉(zhuǎn)換成 JSON 字符串。當(dāng)然,真正的 RSC 實(shí)現(xiàn)使用的是并不是 JSON 格式,這里只是為了方便理解。真正的實(shí)現(xiàn)方式我們將在下一個(gè)系列進(jìn)行探討。

修改客戶端代碼,看看目前返回的數(shù)據(jù):

async function navigate(pathname) {
  currentPathname = pathname;
  const response = await fetch(pathname + "?jsx");
  const jsonString = await response.text();
  if (pathname === currentPathname) {
    alert(jsonString);
  }
}

點(diǎn)擊這里[8],加載首頁,然后點(diǎn)擊一個(gè)鏈接會(huì)看到一個(gè)提示,類似下面這樣:

{
  "key": null,
  "ref": null,
  "props": {
    "url": "http://localhost:3000/hello-world"
  },
  // ...
}

這并不是我們想要的結(jié)構(gòu),我們希望看到類似 <html>...</html> 的 JSX 樹。那是哪里出問題了呢?

我們分析一下,現(xiàn)在的 JSX 看起來是這樣的:

<Router url="http://localhost:3000/hello-world" />
// {
//   $$typeof: Symbol.for('react.element'),
//   type: Router,
//   props: { url: "http://localhost:3000/hello-world" } },
//    ...
// }

這種結(jié)構(gòu)發(fā)送給客戶端還“為時(shí)過早”,我們不知道 Router 具體的 JSX 內(nèi)容,因?yàn)?nbsp;Router 只存在服務(wù)器上。我們需要在服務(wù)端調(diào)用 Router 組件,才能得到需要發(fā)送給客戶端的 JSX 內(nèi)容。

如果,我們使用 { url: "http://localhost:3000/hello-world" } } 作為 prop 調(diào)用 Router 函數(shù),會(huì)得到這樣一段 JSX:

<BlogLayout>
  <BlogIndexPage />
</BlogLayout>

一樣的,把這個(gè)結(jié)果發(fā)送回客戶端還為時(shí)過早,因?yàn)槲覀儾恢?nbsp;BlogLayout 的內(nèi)容,而且它只存在于服務(wù)器上,因此我們還必須調(diào)用 BlogLayout,得到最終傳遞給客戶端的 JSX。

先想一下我們的結(jié)果——調(diào)用結(jié)束后,我們希望得到一個(gè)不引用任何服務(wù)器代碼的 JSX 樹。類似:

<html>
  <head>...</head>
  <body>
    <nav>
      <a href="/">Home</a>
      <hr />
    </nav>
    <main>
    <section>
      <h1>Welcome to my blog</h1>
      <div>
        ...
      </div>
    </main>
    <footer>
      <hr />
      <p>
        <i>
          (c) Jae Doe 2003
        </i>
      </p>
    </footer>
  </body>
</html>

這個(gè)結(jié)果可以直接傳遞給 JSON.stringify() 并發(fā)送給客戶端。

首先,編寫一個(gè) renderJSXToClientJSX() 函數(shù)。接收一段 JSX 作為參數(shù),"解析 "服務(wù)器專有部分(通過調(diào)用相應(yīng)組件獲得),直到只剩下客戶端可以理解的 JSX。

結(jié)構(gòu)上看,這個(gè)函數(shù)類似于 renderJSXToHTML(),但它遞歸遍歷返回的是對(duì)象,而不是 HTML:

async function renderJSXToClientJSX(jsx) {
  if (
    typeof jsx === "string" ||
    typeof jsx === "number" ||
    typeof jsx === "boolean" ||
    jsx == null
  ) {
    // Don't need to do anything special with these types.
    return jsx;
  } else if (Array.isArray(jsx)) {
    // Process each item in an array.
    return Promise.all(jsx.map((child) => renderJSXToClientJSX(child)));
  } else if (jsx != null && typeof jsx === "object") {
    if (jsx.$$typeof === Symbol.for("react.element")) {
      if (typeof jsx.type === "string") {
        // This is a component like <div />.
        // Go over its props to make sure they can be turned into JSON.
        return {
          ...jsx,
          props: await renderJSXToClientJSX(jsx.props),
        };
      } else if (typeof jsx.type === "function") {
        // This is a custom React component (like <Footer />).
        // Call its function, and repeat the procedure for the JSX it returns.
        const Component = jsx.type;
        const props = jsx.props;
        const returnedJsx = await Component(props);
        return renderJSXToClientJSX(returnedJsx);
      } else throw new Error("Not implemented.");
    } else {
      // This is an arbitrary object (for example, props, or something inside of them).
      // Go over every value inside, and process it too in case there's some JSX in it.
      return Object.fromEntries(
        await Promise.all(
          Object.entries(jsx).map(async ([propName, value]) => [
            propName,
            await renderJSXToClientJSX(value),
          ])
        )
      );
    }
  } else throw new Error("Not implemented");
}

接下來編輯 sendJSX(),先將類似 <Router /> 的 JSX 轉(zhuǎn)換為 "客戶端 JSX",然后再對(duì)其字符串化:

async function sendJSX(res, jsx) {
  const clientJSX = await renderJSXToClientJSX(jsx);
  const clientJSXString = JSON.stringify(clientJSX, null, 2); // Indent with two spaces
  res.setHeader("Content-Type", "application/json");
  res.end(clientJSXString);
}

點(diǎn)擊這里的 線上 demo[9] 查看效果。

現(xiàn)在點(diǎn)擊鏈接就會(huì)顯示一個(gè)與 HTML 很類似的樹狀結(jié)構(gòu)提示,這表示我們可以對(duì)它進(jìn)行差異化處理了。

注意:目前我們的目標(biāo)是能最低要求的工作起來,但在實(shí)現(xiàn)過程中還有很多不盡如人意的地方。例如,格式本身非常冗長(zhǎng)和重復(fù),真正的 RSC 使用的是更簡(jiǎn)潔的格式。就像前面生成 HTML 一樣,整個(gè)響應(yīng)被 await 是很糟糕的體驗(yàn)。理想情況下,我們希望將 JSX 分塊進(jìn)行流式傳輸,并在客戶端進(jìn)行拼接。同樣,共享布局的部分內(nèi)容(如 <html> 和 <nav> )沒有發(fā)生變化,現(xiàn)在也要重新發(fā)送這些內(nèi)容。生產(chǎn)就緒的 RSC 實(shí)現(xiàn)并不存在這些缺陷,不過我們暫時(shí)接受這些缺陷,讓代碼看起來更容易理解。

步驟 3:在客戶端執(zhí)行 JSX 更新

嚴(yán)格來說,我們不必使用 React 來擴(kuò)展 JSX。

到目前為止,我們的 JSX 節(jié)點(diǎn)只包含瀏覽器內(nèi)置組件(例如:<nav>、<footer>)。為了實(shí)現(xiàn)對(duì)服務(wù)端返回的 JSX 數(shù)據(jù),執(zhí)行差異化處理和更新,我們會(huì)直接使用 React。

我們?yōu)?React 提供的 JSX 數(shù)據(jù),會(huì)幫助它知道要?jiǎng)?chuàng)建的 DOM 節(jié)點(diǎn)信息,也方便將來安全地進(jìn)行更改。同樣,React 處理過程·中,也會(huì)遍歷 DOM,查看每個(gè) DOM 節(jié)點(diǎn)對(duì)應(yīng)的 JSX 信息。這樣,React 就能將事件處理程序附加到 DOM 節(jié)點(diǎn),讓節(jié)點(diǎn)具備交互性,我們把這個(gè)過程叫水合(hydration)。

傳統(tǒng)上,要將服務(wù)器渲染的標(biāo)記水合,需要調(diào)用 hydrateRoot(),需要為 React 提供一個(gè)掛載 DOM 節(jié)點(diǎn),還有服務(wù)器上創(chuàng)建的初始 JSX。類似這樣:

// Traditionally, you would hydrate like this
hydrateRoot(document, <App />);

問題是我們?cè)诳蛻舳烁緵]有像 <App /> 這樣的根組件!從客戶端的角度來看,目前我們的整個(gè)應(yīng)用程序就是一大塊 JSX,沒有任何 React 組件。然而,React 真正需要的只是與初始 HTML 相對(duì)應(yīng)的 JSX 樹。這個(gè)樹類似 <html>...</html> 這樣的:

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(document, getInitialClientJSX());

function getInitialClientJSX() {
  // TODO: return the <html>...</html> client JSX tree mathching the initial HTML
}

這個(gè)過程會(huì)非常快,因?yàn)榭蛻舳朔祷氐?JSX 樹中沒有任何自定義組件。React 將會(huì)以近乎瞬時(shí)的速度遍歷 DOM 樹和 JSX 樹,并構(gòu)建稍后更新樹時(shí)所需要的一些內(nèi)部數(shù)據(jù)結(jié)構(gòu)。

然后,在每次用戶導(dǎo)航時(shí),我們獲取下一頁的 JSX,并通過 root.render[10] 更新 DOM:

async function navigate(pathname) {
  currentPathname = pathname;
  const clientJSX = await fetchClientJSX(pathname);
  if (pathname === currentPathname) {
    root.render(clientJSX);
  }
}

async function fetchClientJSX(pathname) {
  // TODO: fetch and return the <html>...</html> client JSX tree for the next route
}

補(bǔ)充完 getInitialClientJSX() 和 fetchClientJS() 的內(nèi)容后。React 就會(huì)按照我們預(yù)期的方式,創(chuàng)建并托管我們的項(xiàng)目,同時(shí)也不會(huì)丟失狀態(tài)。

現(xiàn)在,就讓我們來看看如何實(shí)現(xiàn)這兩個(gè)功能。

從服務(wù)器獲取 JSX

我們先從實(shí)現(xiàn) fetchClientJSX() 開始,因?yàn)樗菀讓?shí)現(xiàn)。

首先,讓我們回顧一下 ?jsx 服務(wù)器端的實(shí)現(xiàn)。

async function sendJSX(res, jsx) {
  const clientJSX = await renderJSXToClientJSX(jsx);
  const clientJSXString = JSON.stringify(clientJSX);
  res.setHeader("Content-Type", "application/json");
  res.end(clientJSXString);
}

我們?cè)诳蛻舳讼蚍?wù)端發(fā)送 JSX 請(qǐng)求,然后將響應(yīng)代入 JSON.parse(),再將其轉(zhuǎn)換為 JSX:

async function fetchClientJSX(pathname) {
  const response = await fetch(pathname + "?jsx");
  const clientJSXString = await response.text();
  const clientJSX = JSON.parse(clientJSXString);
  return clientJSX;
}

這樣實(shí)現(xiàn)之后[11],點(diǎn)擊鏈接渲染 JSX 時(shí)就會(huì)報(bào)錯(cuò):

Objects are not valid as a React child (found: object with keys {type, key, ref, props, _owner, _store}).

原因是我們傳遞給 JSON.stringify() 的對(duì)象是這樣的:

{
  $$typeof: Symbol.for("react.element"),
    type: 'html',
    props: {
    // ...

但是查看客戶端 JSON.parse() 結(jié)果,$$typeof 屬性在傳輸過程中丟失了:

{
  type: 'html',
  props: {
    // ...

對(duì) React 來說,如果 JSX 節(jié)點(diǎn)中沒有 $$typeof: Symbol.for("react.element")  屬性,就不會(huì)被看作是有效節(jié)點(diǎn)。這是一種有意為之的安全機(jī)制——默認(rèn)情況下,React 不會(huì)隨意將一個(gè)任意 JSON 對(duì)象視為 JSX 標(biāo)記。因此,就利用了 Symbol 值無法在 JSON 序列化過程中存在的特性,為 JSX 增加了一個(gè)Symbol.for('react.element') 屬性。

不過,我們確實(shí)在服務(wù)器上創(chuàng)建了這些 JSX 節(jié)點(diǎn),而且確實(shí)希望在客戶端上呈現(xiàn)它們。因此,我們需要調(diào)整邏輯,以 "保留" $$typeof: Symbol.for("react.element") 屬性。

幸運(yùn)的是,這個(gè)問題并不難解決。JSON.stringify() 接受一個(gè)替換函數(shù)[12],可以讓我們自定義 JSON 的生成行為。在服務(wù)器上,我們將用 "$RE" 這樣的特殊字符串替換 Symbol.for('react.element'):

async function sendJSX(res, jsx) {
  // ...
  const clientJSXString = JSON.stringify(clientJSX, stringifyJSX); // Notice the second argument
  // ...
}

function stringifyJSX(key, value) {
  if (value === Symbol.for("react.element")) {
    // We can't pass a symbol, so pass our magic string instead.
    return "$RE"; // Could be arbitrary. I picked RE for React Element.
  } else if (typeof value === "string" && value.startsWith("$")) {
    // To avoid clashes, prepend an extra $ to any string already starting with $.
    return "$" + value;
  } else {
    return value;
  }
}

在客戶端,我們將向 JSON.parse() 傳遞一個(gè) reviver 函數(shù),將 "$RE" 替換為 Symbol.for('react.element') :

async function fetchClientJSX(pathname) {
  // ...
  const clientJSX = JSON.parse(clientJSXString, parseJSX); // Notice the second argument
  // ...
}

function parseJSX(key, value) {
  if (value === "$RE") {
    // This is our special marker we added on the server.
    // Restore the Symbol to tell React that this is valid JSX.
    return Symbol.for("react.element");
  } else if (typeof value === "string" && value.startsWith("$$")) {
    // This is a string starting with $. Remove the extra $ added by the server.
    return value.slice(1);
  } else {
    return value;
  }
}

點(diǎn)擊這里的 線上 demo[13] 查看效果。

現(xiàn)在,在頁面間進(jìn)行導(dǎo)航,不過更新全部是以 JSX 形式獲取并在客戶端應(yīng)用的!

如果你在輸入框中輸入內(nèi)容,然后點(diǎn)擊鏈接,會(huì)發(fā)現(xiàn) <input> 狀態(tài)在所有導(dǎo)航中都得到了保留,除第一個(gè)導(dǎo)航除外。這是因?yàn)槲覀冞€沒有告訴 React 頁面的初始 JSX 是什么,所以它無法正確關(guān)聯(lián)服務(wù)器返回的 HTML。

將初始 JSX 內(nèi)聯(lián)到 HTML 中

還有這段代碼:

const root = hydrateRoot(document, getInitialClientJSX());

function getInitialClientJSX() {
  return null; // TODO
}

我們需要將根節(jié)點(diǎn)與初始客戶端 JSX 相結(jié)合,但客戶端上的 JSX 怎么來?

為了解決這個(gè)問題,我們可以把初始 JSX 字符串作為客戶端的全局變量:

const root = hydrateRoot(document, getInitialClientJSX());

function getInitialClientJSX() {
  const clientJSX = JSON.parse(window.__INITIAL_CLIENT_JSX_STRING__, reviveJSX);
  return clientJSX;
}

在服務(wù)器上,我們修改 sendHTML() 函數(shù),以便將應(yīng)用程序渲染為客戶端 JSX,并將其內(nèi)聯(lián)到 HTML 末尾:

async function sendHTML(res, jsx) {
  let html = await renderJSXToHTML(jsx);

  // Serialize the JSX payload after the HTML to avoid blocking paint:
  const clientJSX = await renderJSXToClientJSX(jsx);
  const clientJSXString = JSON.stringify(clientJSX, stringifyJSX);
  html += `<script>window.__INITIAL_CLIENT_JSX_STRING__ = `;
  html += JSON.stringify(clientJSXString).replace(/</g, "\\u003c");
  html += `</script>`;
  // ...

最后,我們需要對(duì)文本節(jié)點(diǎn)生成 HTML 的方式進(jìn)行一些小調(diào)整[14],以便 React 可以將它們水合。

點(diǎn)擊這里的 線上 demo[15] 查看效果。

現(xiàn)在你可以輸入一些內(nèi)容,其數(shù)據(jù)不會(huì)在導(dǎo)航間丟失:

圖片圖片

這就是我們最初設(shè)定的目標(biāo)!當(dāng)然,保存這個(gè)輸入框數(shù)據(jù)并不是重點(diǎn),重要的是,我們的應(yīng)用程序?qū)崿F(xiàn)了“就地 ”刷新和導(dǎo)航,而不必丟失任何狀態(tài)。

注意:雖然真正的 RSC 實(shí)現(xiàn)確實(shí)會(huì)在 HTML payload 對(duì) JSX 進(jìn)行編碼,但仍存在一些重要的差異。生產(chǎn)就緒的 RSC 設(shè)置會(huì)在生成 JSX 塊時(shí)發(fā)送它們,而不是在最后發(fā)送單個(gè)大 Blob 數(shù)據(jù)。當(dāng) React 加載時(shí),水合作用可以立即開始——React 使用已經(jīng)可用的 JSX 塊遍歷樹,而不是非要等到它們?nèi)康竭_(dá)。 RSC 還允許將某些組件標(biāo)記為客戶端組件,這時(shí)候它們?nèi)匀粫?huì) SSR 到 HTML 中,但它們的代碼會(huì)包含在捆綁包中。對(duì)于客戶端組件,只有其 props 的 JSON 被序列化。將來,React 可能會(huì)添加額外的機(jī)制來刪除 HTML 和嵌入的 payload之間的重復(fù)內(nèi)容。

總結(jié)

本文,我們?yōu)榉?wù)端增加了返回 JSX 數(shù)據(jù)的支持,并使用 React 在客戶端進(jìn)行消費(fèi),實(shí)現(xiàn)基于 JSX 結(jié)構(gòu)的頁面初始化和頁面局部更新。出于安全考慮,React 要求 JSX 節(jié)點(diǎn)中需要包含一個(gè) $$typeof: Symbol.for("react.element") 屬性。為此,我們?cè)谛蛄谢徒馕龅臅r(shí)候,對(duì) $$typeof 做了特殊的轉(zhuǎn)換處理。

至此,現(xiàn)在我們的代碼實(shí)際上可以工作了,而且架構(gòu)已經(jīng)很接近真正的 RSC 了(雖然沒有引入流式傳輸這樣的復(fù)雜機(jī)制)。在下一篇,也就是本系列的終篇,我們將對(duì)現(xiàn)在的代碼做一些清理工作,修復(fù)一些缺陷,并為下一波功能做好準(zhǔn)備。

責(zé)任編輯:武曉燕 來源: 寫代碼的寶哥
相關(guān)推薦

2018-02-07 10:24:01

Nginx服務(wù)器架構(gòu)

2021-06-02 07:43:42

服務(wù)器阿里云配置

2025-06-11 02:10:00

2018-08-31 10:10:06

2019-09-10 09:40:11

游戲服務(wù)器框架

2019-02-20 13:57:48

游戲服務(wù)器框架

2018-10-15 09:39:12

服務(wù)器開發(fā)語言

2011-01-12 11:54:46

ARM

2022-02-22 11:57:32

BOAWeb服務(wù)器

2010-04-28 11:22:46

2012-02-13 23:46:22

keepalived高可用

2020-08-07 14:28:04

裸金屬服務(wù)器云服務(wù)

2018-01-19 10:30:48

HTTP服務(wù)器代碼

2020-06-07 11:54:34

Linux服務(wù)器命令

2019-06-13 17:15:30

監(jiān)控Linux服務(wù)器

2010-05-18 15:25:07

IIS服務(wù)器

2021-05-26 11:30:34

戴爾

2009-08-12 13:18:46

IBMX86服務(wù)器

2010-05-19 10:31:07

IIS服務(wù)器

2019-01-15 10:54:03

高性能ServerReactor
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 黄a在线播放 | 亚洲欧美激情网 | 久久视频精品 | 久久久久久成人 | 亚洲精品电影 | 久久久久久久久国产成人免费 | 国产精品国产三级国产aⅴ浪潮 | 干干干操操操 | 国产一级一级 | 国产精品久久久久久久久久久久冷 | 视频一区在线观看 | 久久毛片| 一区二区三区精品 | 亚洲视频一区在线观看 | 国产精品日韩高清伦字幕搜索 | 日本福利视频 | 国产清纯白嫩初高生在线播放视频 | 日韩视频一区二区 | www.99re| 91影院| 精品免费视频 | 日韩av一区二区在线观看 | 天天操人人干 | 亚洲免费观看视频网站 | 国产精品区二区三区日本 | 鲁大师一区影视 | 国产精品美女 | 欧洲国产精品视频 | 精品真实国产乱文在线 | 国产一区二区在线91 | 激情五月婷婷在线 | 精品国产乱码久久久久久蜜臀 | 一区二区三区国产 | 色婷婷av久久久久久久 | 国产成人午夜精品影院游乐网 | 人人cao | com.国产| 国产良家自拍 | 国产精品日本一区二区在线播放 | 日韩精品一区二区三区视频播放 | 国产成人精品在线播放 |