当前位置:首页 > 科技  > 软件

我们聊聊从头学服务器组件:在导航间保留状态

来源: 责编: 时间:2023-11-30 09:25:48 170观看
导读存在的问题到目前为止,访问每个页面,服务器返回的都是一个 HTML 字符串:async function sendHTML(res, jsx) { const html = await renderJSXToHTML(jsx); res.setHeader("Content-Type", "text/html"); res.end(html

存在的问题

到目前为止,访问每个页面,服务器返回的都是一个 HTML 字符串:Maq28资讯网——每日最新资讯28at.com

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

这对浏览器首次加载是非常友好的——浏览器有针对 HTML 渲染做针对性优化,会尽可能快地展示——但对于页面导航就不太理想了。我们希望页面支持布局刷新,即只更新 "发生变化的部分",页面其他部分不受影响,也让交互变得流畅了。Maq28资讯网——每日最新资讯28at.com

为了说明这个问题,我们向 BlogLayout 组件的 <nav> 中添加一个 <input />:Maq28资讯网——每日最新资讯28at.com

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

请注意,每次浏览博客时,输入框里的内容(或叫“状态”)都会消失:Maq28资讯网——每日最新资讯28at.com

图片图片Maq28资讯网——每日最新资讯28at.com

这对一个简单的博客来说可能没有问题,但如果要构建具有复杂交互的应用程序,这种行为在某些时候就会让人头疼。因此,你需要让用户在应用程序中随意浏览时,不会丢失页面状态。Maq28资讯网——每日最新资讯28at.com

接下来,我们将分 3 步骤来解决这个问题:Maq28资讯网——每日最新资讯28at.com

  1. 添加一些客户端 JS 逻辑拦截导航,修改默认行为(这样我们就可以手动重新获取内容,而无需重新加载整个页面)
  2. 修改服务端代码,支持在随后的导航中需要提供返回 JSX 响应(而不是 HTML)的支持
  3. 修改客户端代码,在不破坏 DOM 的情况下执行 JSX 更新(提示:这部分将使用 React)

局部更新支持

步骤 1:拦截导航

我们需要一些客户端逻辑,因此增加一个 client.js 文件。先修改服务端代码(即 server.js),增加静态资源的路由请求支持(即"/client.js")。Maq28资讯网——每日最新资讯28at.com

createServer(async (req, res) => {  try {    const url = new URL(req.url, `http://${req.headers.host}`);    // 增加静态资源 "/client.js" 的路由支持    if (url.pathname === "/client.js") {      await sendScript(res, "./client.js"); // 返回服务端本地的 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() 中为响应的 HTML  文本增加  <script> 标签,引用 client.js 文件。Maq28资讯网——每日最新资讯28at.com

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);}

现在说回 client.js。在这个文件中,我们会覆盖网站内导航的默认行为(译注:此处指<a> 标签链接能力),改调我们自己的 navigate() 函数:Maq28资讯网——每日最新资讯28at.com

async function navigate(pathname) {  // TODO}window.addEventListener("click", (e) => {  // 只处理 <a> 标签的点击事件  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> 标签默认的跳转行为  e.preventDefault();  // 然后,将跳转地址推入浏览历史,浏览器地址栏的地址也同步更新  window.history.pushState(null, null, href);  // 最后,执行自定义的导航行为  navigate(href);}, true);window.addEventListener("popstate", () => {  // When the user presses Back/Forward, call our custom logic too.  navigate(window.location.pathname);});

navigate() 函数就是编写自定义导航行为的地方。在 navigate() 函数中,我们使用 fetch API 请求下一个路由的 HTML 响应,并使用这个响应更新页面:Maq28资讯网——每日最新资讯28at.com

let currentPathname = window.location.pathname;async function navigate(pathname) {  currentPathname = pathname;  // 获取当前导航路由下的 HTML 数据  const response = await fetch(pathname);  const html = await response.text();  if (pathname === currentPathname) {    // 获取 HTML 响应数据 <body> 标签里的内容    const bodyStartIndex = html.indexOf("<body>") + "<body>".length;    const bodyEndIndex = html.lastIndexOf("</body>");    const bodyHTML = html.slice(bodyStartIndex, bodyEndIndex);    // 将拿到的 HTML 数据替换掉当前 <body> 里的内容    document.body.innerHTML = bodyHTML;  }}

点击这里的 线上 demo[7] 查看效果。Maq28资讯网——每日最新资讯28at.com

可以看到,在页面不刷新的情况下,页面内容被替换了,表示我们已经成功覆盖了浏览器默认导航行为。不过,由于目前是针对 <body> 元素的整体覆盖,所以<input> 里的数据仍然会丢失。接下来,我们会修改服务器部分,增加返回 JSX 响应的支持。Maq28资讯网——每日最新资讯28at.com

步骤 2:增加返回 JSX 响应的支持

还记得我们之前使用 JSX 组成的对象树结构吧?Maq28资讯网——每日最新资讯28at.com

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

接下来,我们将为服务器增加以 ?jsx 结尾的请求支持。?jsx 请求返回的是一个 JSX 树结构,而不是 HTML,这更容易让客户端确定哪些部分发生了变化,并只在必要时更新 DOM,这将直接解决 <input> 状态在每次导航时丢失的问题,但这并不是我们这样做的唯一原因。接下来,你将看到我们如何将新信息(不仅仅是 HTML)从服务器传递到客户端。Maq28资讯网——每日最新资讯28at.com

首先修改服务器代码,支持请求中携带 ?jsx 参数时调用一个新的 sendJSX() 函数:Maq28资讯网——每日最新资讯28at.com

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 数据    } else {      await sendHTML(res, <Router url={url} />);    }    // ...

在 sendJSX() 中,我们使用 JSON.stringify(jsx) 把的 JSX 树转换成一个 JSON 字符串,并通过网络返回:Maq28资讯网——每日最新资讯28at.com

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);}

要注意的是,我们并不是发送 JSX 语法本身(如 "<Foo />" ),只是将 JSX 生成的树结构转换成 JSON 字符串。当然,真正的 RSC 实现使用的是并不是 JSON 格式,这里只是为了方便理解。真正的实现方式我们将在下一个系列进行探讨。Maq28资讯网——每日最新资讯28at.com

修改客户端代码,看看目前返回的数据:Maq28资讯网——每日最新资讯28at.com

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

点击这里[8],加载首页,然后点击一个链接会看到一个提示,类似下面这样:Maq28资讯网——每日最新资讯28at.com

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

这并不是我们想要的结构,我们希望看到类似 <html>...</html> 的 JSX 树。那是哪里出问题了呢?Maq28资讯网——每日最新资讯28at.com

我们分析一下,现在的 JSX 看起来是这样的:Maq28资讯网——每日最新资讯28at.com

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

这种结构发送给客户端还“为时过早”,我们不知道 Router 具体的 JSX 内容,因为 Router 只存在服务器上。我们需要在服务端调用 Router 组件,才能得到需要发送给客户端的 JSX 内容。Maq28资讯网——每日最新资讯28at.com

如果,我们使用 { url: "http://localhost:3000/hello-world" } } 作为 prop 调用 Router 函数,会得到这样一段 JSX:Maq28资讯网——每日最新资讯28at.com

<BlogLayout>  <BlogIndexPage /></BlogLayout>

一样的,把这个结果发送回客户端还为时过早,因为我们不知道 BlogLayout 的内容,而且它只存在于服务器上,因此我们还必须调用 BlogLayout,得到最终传递给客户端的 JSX。Maq28资讯网——每日最新资讯28at.com

先想一下我们的结果——调用结束后,我们希望得到一个不引用任何服务器代码的 JSX 树。类似:Maq28资讯网——每日最新资讯28at.com

<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>

这个结果可以直接传递给 JSON.stringify() 并发送给客户端。Maq28资讯网——每日最新资讯28at.com

首先,编写一个 renderJSXToClientJSX() 函数。接收一段 JSX 作为参数,"解析 "服务器专有部分(通过调用相应组件获得),直到只剩下客户端可以理解的 JSX。Maq28资讯网——每日最新资讯28at.com

结构上看,这个函数类似于 renderJSXToHTML(),但它递归遍历返回的是对象,而不是 HTML:Maq28资讯网——每日最新资讯28at.com

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 转换为 "客户端 JSX",然后再对其字符串化:Maq28资讯网——每日最新资讯28at.com

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);}

点击这里的 线上 demo[9] 查看效果。Maq28资讯网——每日最新资讯28at.com

现在点击链接就会显示一个与 HTML 很类似的树状结构提示,这表示我们可以对它进行差异化处理了。Maq28资讯网——每日最新资讯28at.com

注意:目前我们的目标是能最低要求的工作起来,但在实现过程中还有很多不尽如人意的地方。例如,格式本身非常冗长和重复,真正的 RSC 使用的是更简洁的格式。就像前面生成 HTML 一样,整个响应被 await 是很糟糕的体验。理想情况下,我们希望将 JSX 分块进行流式传输,并在客户端进行拼接。同样,共享布局的部分内容(如 <html> 和 <nav> )没有发生变化,现在也要重新发送这些内容。生产就绪的 RSC 实现并不存在这些缺陷,不过我们暂时接受这些缺陷,让代码看起来更容易理解。Maq28资讯网——每日最新资讯28at.com

步骤 3:在客户端执行 JSX 更新

严格来说,我们不必使用 React 来扩展 JSX。Maq28资讯网——每日最新资讯28at.com

到目前为止,我们的 JSX 节点只包含浏览器内置组件(例如:<nav>、<footer>)。为了实现对服务端返回的 JSX 数据,执行差异化处理和更新,我们会直接使用 React。Maq28资讯网——每日最新资讯28at.com

我们为 React 提供的 JSX 数据,会帮助它知道要创建的 DOM 节点信息,也方便将来安全地进行更改。同样,React 处理过程·中,也会遍历 DOM,查看每个 DOM 节点对应的 JSX 信息。这样,React 就能将事件处理程序附加到 DOM 节点,让节点具备交互性,我们把这个过程叫水合(hydration)。Maq28资讯网——每日最新资讯28at.com

传统上,要将服务器渲染的标记水合,需要调用 hydrateRoot(),需要为 React 提供一个挂载 DOM 节点,还有服务器上创建的初始 JSX。类似这样:Maq28资讯网——每日最新资讯28at.com

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

问题是我们在客户端根本没有像 <App /> 这样的根组件!从客户端的角度来看,目前我们的整个应用程序就是一大块 JSX,没有任何 React 组件。然而,React 真正需要的只是与初始 HTML 相对应的 JSX 树。这个树类似 <html>...</html> 这样的:Maq28资讯网——每日最新资讯28at.com

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}

这个过程会非常快,因为客户端返回的 JSX 树中没有任何自定义组件。React 将会以近乎瞬时的速度遍历 DOM 树和 JSX 树,并构建稍后更新树时所需要的一些内部数据结构。Maq28资讯网——每日最新资讯28at.com

然后,在每次用户导航时,我们获取下一页的 JSX,并通过 root.render[10] 更新 DOM:Maq28资讯网——每日最新资讯28at.com

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}

补充完 getInitialClientJSX() 和 fetchClientJS() 的内容后。React 就会按照我们预期的方式,创建并托管我们的项目,同时也不会丢失状态。Maq28资讯网——每日最新资讯28at.com

现在,就让我们来看看如何实现这两个功能。Maq28资讯网——每日最新资讯28at.com

从服务器获取 JSX

我们先从实现 fetchClientJSX() 开始,因为它更容易实现。Maq28资讯网——每日最新资讯28at.com

首先,让我们回顾一下 ?jsx 服务器端的实现。Maq28资讯网——每日最新资讯28at.com

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

我们在客户端向服务端发送 JSX 请求,然后将响应代入 JSON.parse(),再将其转换为 JSX:Maq28资讯网——每日最新资讯28at.com

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

这样实现之后[11],点击链接渲染 JSX 时就会报错:Maq28资讯网——每日最新资讯28at.com

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

原因是我们传递给 JSON.stringify() 的对象是这样的:Maq28资讯网——每日最新资讯28at.com

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

但是查看客户端 JSON.parse() 结果,$$typeof 属性在传输过程中丢失了:Maq28资讯网——每日最新资讯28at.com

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

对 React 来说,如果 JSX 节点中没有 $$typeof: Symbol.for("react.element")  属性,就不会被看作是有效节点。这是一种有意为之的安全机制——默认情况下,React 不会随意将一个任意 JSON 对象视为 JSX 标记。因此,就利用了 Symbol 值无法在 JSON 序列化过程中存在的特性,为 JSX 增加了一个Symbol.for('react.element') 属性。Maq28资讯网——每日最新资讯28at.com

不过,我们确实在服务器上创建了这些 JSX 节点,而且确实希望在客户端上呈现它们。因此,我们需要调整逻辑,以 "保留" $$typeof: Symbol.for("react.element") 属性。Maq28资讯网——每日最新资讯28at.com

幸运的是,这个问题并不难解决。JSON.stringify() 接受一个替换函数[12],可以让我们自定义 JSON 的生成行为。在服务器上,我们将用 "$RE" 这样的特殊字符串替换 Symbol.for('react.element'):Maq28资讯网——每日最新资讯28at.com

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() 传递一个 reviver 函数,将 "$RE" 替换为 Symbol.for('react.element') :Maq28资讯网——每日最新资讯28at.com

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;  }}

点击这里的 线上 demo[13] 查看效果。Maq28资讯网——每日最新资讯28at.com

现在,在页面间进行导航,不过更新全部是以 JSX 形式获取并在客户端应用的!Maq28资讯网——每日最新资讯28at.com

如果你在输入框中输入内容,然后点击链接,会发现 <input> 状态在所有导航中都得到了保留,除第一个导航除外。这是因为我们还没有告诉 React 页面的初始 JSX 是什么,所以它无法正确关联服务器返回的 HTML。Maq28资讯网——每日最新资讯28at.com

将初始 JSX 内联到 HTML 中

还有这段代码:Maq28资讯网——每日最新资讯28at.com

const root = hydrateRoot(document, getInitialClientJSX());function getInitialClientJSX() {  return null; // TODO}

我们需要将根节点与初始客户端 JSX 相结合,但客户端上的 JSX 怎么来?Maq28资讯网——每日最新资讯28at.com

为了解决这个问题,我们可以把初始 JSX 字符串作为客户端的全局变量:Maq28资讯网——每日最新资讯28at.com

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

在服务器上,我们修改 sendHTML() 函数,以便将应用程序渲染为客户端 JSX,并将其内联到 HTML 末尾:Maq28资讯网——每日最新资讯28at.com

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>`;  // ...

最后,我们需要对文本节点生成 HTML 的方式进行一些小调整[14],以便 React 可以将它们水合。Maq28资讯网——每日最新资讯28at.com

点击这里的 线上 demo[15] 查看效果。Maq28资讯网——每日最新资讯28at.com

现在你可以输入一些内容,其数据不会在导航间丢失:Maq28资讯网——每日最新资讯28at.com

图片图片Maq28资讯网——每日最新资讯28at.com

这就是我们最初设定的目标!当然,保存这个输入框数据并不是重点,重要的是,我们的应用程序实现了“就地 ”刷新和导航,而不必丢失任何状态。Maq28资讯网——每日最新资讯28at.com

注意:虽然真正的 RSC 实现确实会在 HTML payload 对 JSX 进行编码,但仍存在一些重要的差异。生产就绪的 RSC 设置会在生成 JSX 块时发送它们,而不是在最后发送单个大 Blob 数据。当 React 加载时,水合作用可以立即开始——React 使用已经可用的 JSX 块遍历树,而不是非要等到它们全部到达。 RSC 还允许将某些组件标记为客户端组件,这时候它们仍然会 SSR 到 HTML 中,但它们的代码会包含在捆绑包中。对于客户端组件,只有其 props 的 JSON 被序列化。将来,React 可能会添加额外的机制来删除 HTML 和嵌入的 payload之间的重复内容。Maq28资讯网——每日最新资讯28at.com

总结

本文,我们为服务端增加了返回 JSX 数据的支持,并使用 React 在客户端进行消费,实现基于 JSX 结构的页面初始化和页面局部更新。出于安全考虑,React 要求 JSX 节点中需要包含一个 $$typeof: Symbol.for("react.element") 属性。为此,我们在序列化和解析的时候,对 $$typeof 做了特殊的转换处理。Maq28资讯网——每日最新资讯28at.com

至此,现在我们的代码实际上可以工作了,而且架构已经很接近真正的 RSC 了(虽然没有引入流式传输这样的复杂机制)。在下一篇,也就是本系列的终篇,我们将对现在的代码做一些清理工作,修复一些缺陷,并为下一波功能做好准备。Maq28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-35252-0.html我们聊聊从头学服务器组件:在导航间保留状态

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 为什么要在项目中使用TypeScript?

下一篇: 利用属性选择器对外部链接进行样式设计

标签:
  • 热门焦点
  • MIX Fold3包装盒泄露 新机本月登场

    MIX Fold3包装盒泄露 新机本月登场

    小米的全新折叠屏旗舰MIX Fold3将于本月发布,近日该机的真机包装盒在网上泄露。从图上来看,新的MIX Fold3包装盒在外观设计方面延续了之前的方案,变化不大,这也是目前小米旗舰
  • 俄罗斯:将审查iPhone等外国公司设备 保数据安全

    俄罗斯:将审查iPhone等外国公司设备 保数据安全

    iPhone和特斯拉都属于在各自领域领头羊的品牌,推出的产品也也都是数一数二的,但对于一些国家而言,它们的产品可靠性和安全性还是在限制范围内。近日,俄罗斯联邦通信、信息技术
  • 2023 年的 Node.js 生态系统

    2023 年的 Node.js 生态系统

    随着技术的不断演进和创新,Node.js 在 2023 年达到了一个新的高度。Node.js 拥有一个庞大的生态系统,可以帮助开发人员更快地实现复杂的应用。本文就来看看 Node.js 最新的生
  • Flowable工作流引擎的科普与实践

    Flowable工作流引擎的科普与实践

    一.引言当我们在日常工作和业务中需要进行各种审批流程时,可能会面临一系列技术和业务上的挑战。手动处理这些审批流程可能会导致开发成本的增加以及业务复杂度的上升。在这
  • 每天一道面试题-CPU伪共享

    每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 冯提莫签约抖音公会 前“斗鱼一姐”消失在直播间

    冯提莫签约抖音公会 前“斗鱼一姐”消失在直播间

    来源:直播观察提起&ldquo;冯提莫&rdquo;这个名字,很多网友或许听过,但应该不记得她是哪位主播了。其实,作为曾经的&ldquo;斗鱼一姐&rdquo;,冯提莫在游戏直播的年代影响力不输于现
  • 超闭合精工铰链 彻底消灭缝隙 三星Galaxy Z Flip5与Galaxy Z Fold5发布

    超闭合精工铰链 彻底消灭缝隙 三星Galaxy Z Flip5与Galaxy Z Fold5发布

    2023年7月26日,三星电子正式发布了Galaxy Z Flip5与Galaxy Z Fold5。三星新一代折叠屏手机采用超闭合精工铰链,让折叠后的缝隙不再可见。同时,配合处
  • 超级标准版旗舰!iQOO 11S全球首发iQOO超算独显芯片

    超级标准版旗舰!iQOO 11S全球首发iQOO超算独显芯片

    上半年已接近尾声,截至目前各大品牌旗下的顶级旗舰都已悉数亮相,而下半年即将推出的顶级旗舰已经成为了数码圈爆料的主流,其中就包括全新的iQOO 11S系
  • 3699元!iQOO Neo8 Pro顶配版今日首销:1TB UFS 4.0同价位唯一

    3699元!iQOO Neo8 Pro顶配版今日首销:1TB UFS 4.0同价位唯一

    5月23日,iQOO推出了全新的iQOO Neo8系列,包含iQOO Neo8和iQOO Neo8 Pro两个版本,其中标准版搭载高通骁龙8+,而Pro版更是首发搭载了联发科天玑9200+旗舰
Top