这是用户在 2024-12-17 14:32 为 https://saewitz.com/the-mental-model-of-server-components 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Daniel Saewitz 丹尼尔·塞维茨

The Mental Model of Server Components
服务器组件的心智模型

React Server Components are hard to grok and building a mental model of how they work has proven to be difficult to many of us. But, despite the general discourse, they are not confusing for the sake of being confusing, and they are not a conspiracy to sell server cycles. Beneath the abstraction is fundamentally strong engineering that has unlocked both UX and DX gains that were previously inaccessible. I think it's worthwhile to learn their design and what problems they truly solve.
React 服务器组件难以理解,构建它们的工作原理的心理模型对许多人来说都证明是困难的。但是,尽管有普遍的讨论,它们并不是为了混淆而混淆,也不是为了销售服务器周期而进行的阴谋。在抽象之下是根本强大的工程,它解锁了之前无法触及的 UX 和 DX 收益。我认为学习它们的设计和它们真正解决的问题是有价值的。

The advantages of client-side rendering and even the advantages of traditional server-side rendered apps (like PHP or Ruby) have been well discussed and both approaches have merit. I'd like to walk you through an approach to RSCs from a slightly different angle–one that looks at them through the lens of hydration and composition. To me, it's under this spotlight that RSCs shine.
客户端渲染的优势以及传统服务器端渲染应用(如 PHP 或 Ruby)的优势已经被广泛讨论,两种方法都有其优点。我想从略微不同的角度向您介绍 RSCs 的方法——一种通过加湿和组合的视角来看待它们的方法。对我来说,正是在这个聚光灯下,RSCs 才显得格外耀眼。

In this post, we'll explore:
在这个帖子中,我们将探讨:

  1. Why hydration is so costly and over-utilized
    为什么水合作用如此昂贵且过度使用
  2. Why server components exist
    为什么服务器组件存在
  3. How through composition, they are unlike anything that we've seen before
    如何通过组合,它们与我们之前所见的一切都不同
  4. Why RSCs are so confusing, and how we can simplify the mental model
    为什么 RSCs 如此令人困惑,以及我们如何简化思维模型

This won't cover everything, but hopefully it will clear up some of the aspects that are tough to wrap your head around. And once they do begin to click, I think you'll find the rest of it slowly falls in place.
这不会涵盖所有内容,但希望它能澄清一些难以理解的部分。一旦它们开始变得清晰,我认为你会发现其余的部分会逐渐变得容易理解。

What is Hydration? 什么是水合作用?

Hydration is taking some html and enriching it with client-side javascript. It doesn't matter if you are writing a simple php or rails app and sprinkling in some javascript by hand, or an isomorphic SSR app that handles it for you. Everyone hydrates at some point. And we're all desiccated, because hydration is costly.
水分化是将一些 HTML 通过客户端 JavaScript 进行丰富。无论是你正在编写一个简单的 PHP 或 Rails 应用程序并手动添加一些 JavaScript,还是一个同构 SSR 应用程序为你处理它,每个人在某个时刻都会进行水分化。我们都变得干燥,因为水分化是昂贵的。

Hand-hydrating a PHP or rails app works until you need to re-use elements, or you need to share state across components, or you need cross-view client side state. There are of course ways to solve these problems, but it's really, really hard to do so scalably. As your website grows, it only becomes more and more tangled. You'll notice that most of the people who espouse the benefits of this pattern often exclaim the lack of build-steps and compilation as its true benefit, not its technological superiority.
手动水合 PHP 或 rails 应用直到你需要重用元素,或者需要在组件间共享状态,或者需要在跨视图客户端状态。当然有解决这些问题的方法,但真正实现起来非常非常困难。随着你的网站增长,它只会变得越来越复杂。你会发现,大多数宣扬这种模式好处的人,常常惊叹于缺乏构建步骤和编译是其真正的好处,而不是其技术优势。

reddit-post.html
1<div>
2 A Reddit Post
3 <div>
4 <button class="preview">Show</button>
5 <img src="https://placecats.com/neo/300/200" style="display:none;" />
6 </div>
7</div>
8
9<script>
10 // this is hydration
11 document.querySelector('.preview').addEventListener('click', (e) => {
12 const $img = e.target.parentElement.querySelector('img');
13 // toggle visibility
14 $img.style.display = $img.style.display === 'none' ? 'block' : 'none';
15 });
16</script>
A Reddit Post Reddit 帖子

Though this is a rather naive example, we can see how this is fickle - our javascript is not contained nor componentized – it's globalized and not composable. It works fine in isolation, but it will never actually exist in isolation.
尽管这是一个相当简单的例子,但我们可以看出这是多么不稳定——我们的 javascript 既没有被封装也没有组件化——它是全局化的且不可组合的。在独立情况下它运行良好,但它实际上永远不会孤立存在。

Isomorphic Rendering 同构渲染

The javascript world solved this hand-hydration spaghetti problem by introducing isomorphic rendering1
JavaScript 世界通过引入同构渲染解决了这个手动水合意大利面问题 1
. This allowed people to write apps entirely in javascript that render on both the server and the client. It was a natural evolution. As with many software solutions, this was wonderful in theory, but less so in practice. Instead of chonky client-only JS bundles downloading, then parsing, then executing, then rendering before anything was visible, we could render the site on the server first. But with that came a fatal flaw: we hydrated everything. The entire website, all of it.
这允许人们完全使用 javascript 编写在服务器和客户端上渲染的应用程序。这是一个自然的演变。就像许多软件解决方案一样,在理论上这很美妙,但在实践中则不然。我们不再需要下载、解析、执行然后渲染才能看到任何内容的大型客户端 JS 包。我们可以在服务器上首先渲染网站。但随之而来的是一个致命的缺陷:我们使一切变得活跃。整个网站,全部都是。

Contrary to general consensus, the cost of hydrating everything is not only slower download speeds for those javascript bundles, but the time it takes to reach interactivity. Downloading, parsing, and executing all of that javascript is incredibly slow and blocks the main thread. Websites would freeze for a few seconds before you could enter text into an input, click a button, or even scroll the page.
与普遍共识相反,为所有内容加水印不仅会导致那些 javascript 包的下载速度变慢,还会影响达到交互状态的时间。下载、解析和执行所有这些 javascript 非常缓慢,并阻塞主线程。在你能够输入文本到输入框、点击按钮或甚至滚动页面之前,网站可能会冻结几秒钟。

React's hydration is even more powerful than just attaching event handlers. It takes over your components' rendering on the client so it can re-render updates for future cycles. This is incredibly useful, but again, costly and unnecessary for every single div.
React 的 Hydration 甚至比仅仅附加事件处理器更强大。它接管了组件在客户端的渲染,以便它可以重新渲染未来的周期更新。这非常实用,但同样,对于每个 div 来说都是昂贵且不必要的。

This process of rendering your components and attaching event handlers is known as “hydration”. It’s like watering the “dry” HTML with the “water” of interactivity and event handlers. (Or at least, that’s how I explain this term to myself.)
这个过程将您的组件渲染并附加事件处理器称为“水合”。这就像给“干燥”的 HTML 浇上“水”般的交互性和事件处理器。(或者至少,这是我向自己解释这个术语的方式。)


-Dan Abramov2 - 丹·阿布拉莫夫 2

Writing an entire website in one composable paradigm improved our code quality – and the user (or Googlebot) saw something immediately. But all of your rendering code had to be pure on both the server and the client, leading to frustration and hamstringing the server.
编写整个网站在一个组合范式下提高了代码质量 - 用户(或 Googlebot)立即看到了这一点。但所有渲染代码都必须在服务器和客户端上都是纯的,这导致了挫败感和限制了服务器。

timestamp-ssr.js timestamp-ssr.js 时间戳-ssr.js
1export function Timestamp() {
2 return new Date().toLocaleString();
3}
Error: Hydration failed because the initial UI does not match what was rendered on the server
错误:由于初始 UI 与服务器上渲染的内容不匹配, hydration 失败
With React SSR, code runs in both environments
使用 React SSR,代码在两个环境中运行

This turned the trusted server into what is effectively a dumbed down client. We lost all of the access control and environmental benefits of the server itself. And, when writing something like PHP, you never had to consider what timestamp was going to render, because it was only being rendered in one place.
这使受信任的服务器变成了一个实际上被简化的客户端。我们失去了服务器本身的所有访问控制和环境优势。而且,在编写像 PHP 这样的东西时,你永远不需要考虑哪个时间戳将被渲染,因为它只在一个地方被渲染。

Reinventing PHP 重新发明 PHP

Dismissals of RSCs often come with a lackadaisical insult to say the React developers have just reinvented PHP. But that's a cheap knock.
RSCs 的解雇通常伴随着对 React 开发者的轻蔑侮辱,说他们只是重新发明了 PHP。但这只是个廉价的批评。

The reason those PHP apps sprinkled with JS kinda worked is the same reason React Server Components exist – it turns out we don't need to hydrate most of our application. Most of our components are not interactive and they don't need client-side updates frequently. They are merely simple divs and text and depend on data that doesn't change often (like a Reddit post title). All we need most of the time is a little interactivity sprinkled throughout. The dissidents were right.
那些带有 JS 的 PHP 应用程序之所以能勉强工作,原因与 React 服务器组件存在的原因相同——结果是,我们大多数应用程序不需要进行初始化。我们的大部分组件都不是交互式的,也不需要频繁地进行客户端更新。它们只是简单的 div 和文本,依赖于不经常变化的数据(如 Reddit 帖子标题)。我们大多数时候需要的只是一个微小的交互性点缀。持不同意见者是正确的。

Let's take our Reddit example and show how we might render it with PHP.
让我们以 Reddit 为例,展示如何使用 PHP 进行渲染。

reddit-post.php reddit-post.php reddit-post.php
1<?php
2$stmt = $pdo->prepare("SELECT title, image FROM posts WHERE id = ?");
3$stmt->execute([$id]);
4$post = $stmt->fetchAll(PDO::FETCH_ASSOC);
5?>
6<div>
7 <?= $post['title'] ?>
8 <div>
9 <button class="preview">Show</button>
10 <img src="<?= $post['image'] ?>" style="display:none;" />
11 </div>
12</div>
13
14<script>
15 document.querySelector('.preview').addEventListener('click', (e) => {
16 const $img = e.target.parentElement.querySelector('img');
17 // toggle visibility
18 $img.style.display = $img.style.display === 'none' ? 'block' : 'none';
19 });
20</script>

When we don't need to automatically hydrate all of our html, we can take full advantage of the server. In a PHP route you can query a database, or write an API call to a third party without exposing it to the client, or just render some complex html. It's fast, easy to write, and intuitive. It's one of those things frontend engineers forgot they missed, and once you start playing with it, the client-side complexity around data fetching just melts away.
当我们不需要自动填充所有 HTML 时,我们可以充分利用服务器。在 PHP 路由中,你可以查询数据库,或者向第三方编写 API 调用而不暴露给客户端,或者只是渲染一些复杂的 HTML。这既快又容易编写,且直观。这是前端工程师忘记他们错过了的事情之一,一旦你开始玩弄它,数据获取的客户端复杂性就会消失。

This is not "reinventing PHP", this is understanding that PHP's advantages weren't previously accessible to our modern frontend stack due to universal hydration. Be critical of where we were before, not where we are now!
这不是“重新发明 PHP”,这是理解 PHP 的优势之前由于通用解冻而无法被我们的现代前端堆栈所利用。对我们之前的位置持批判态度,而不是我们现在所在的位置!

This gives power back to the server, instead of having to render our UI on both the server and the client simultaneously, we can render just on the server and take with it the wonderful advantages the server gives us.
这使服务器重新获得权力,而不是同时需要在服务器和客户端上渲染我们的 UI,我们只需在服务器上渲染,并享受服务器为我们带来的美妙优势。

What is a Server Component?
什么是服务器组件?

A server component is a React component that renders ONLY on the server3, meaning it doesn't hydrate and it doesn't get sent to the client bundle. Again, this is often most of the UI on our websites, and shedding all of that hydration leads to significantly smaller bundle sizes and much faster time-to-interactivity (TTI).
服务器组件是一个仅在服务器上渲染的 React 组件 3 ,这意味着它不会进行水合,也不会发送到客户端包。再次强调,这通常是我们在网站上大部分 UI,去除所有这些水合可以显著减小包的大小,并大大加快交互时间(TTI)。

React 18 emphasized the commitment to reducing TTI by introducing selective hydration4, which prioritizes interactive hydration as early as possible - pushing time to interactivity towards its lower limit without you having to do anything.
React 18 强调了通过引入选择性 hydration 4 来减少 TTI 的承诺,这优先考虑尽可能早地进行交互式 hydration - 将交互时间推向其下限,而无需您做任何事情。

Since our code has the ability to run only on the server, the server now becomes a first-class citizen (instead of the dumb client it was relegated to previously with isomorphic rendering).
由于我们的代码只能在服务器上运行,现在服务器成为了第一等公民(而不是之前被降级为的愚蠢客户端,通过同构渲染)。

Let's look at the PHP example as a server component. And yes, the SQL parameter is properly sanitized.
让我们看看 PHP 示例作为服务器组件。是的,SQL 参数已正确清理。

page.js page.js 页面.js
1export default async function Page({ params }) {
2 const post = await sql`SELECT title, image FROM posts WHERE id = ${params.id}`;
3
4 if (!post) return notFound(); // 404
5
6 return (
7 <div>
8 {post.title}
9 <div>
10 <button className="preview">Show</button>
11 <img src={post.image} style={{ display: 'none' }} />
12 </div>
13 </div>
14 );
15}

Nothing here is hydrating yet, we're just rendering out html like we were with PHP. But the big differentiator is: we can compose in hydration with little effort.
此处尚无任何数据加载,我们只是在像使用 PHP 一样渲染 HTML。但最大的区别是:我们可以轻松地在数据加载时进行组合。

Let's take the interactive part of our UI and write it as a client component.
让我们将我们的 UI 的交互部分编写为一个客户端组件。

Preview.js
1'use client';
2
3import { useState } from 'react';
4
5export function Preview({ image }) {
6 const [isOpen, setIsOpen] = useState(false);
7
8 const toggleIsOpen = () => setIsOpen(!isOpen);
9
10 return (
11 <div>
12 <button onClick={toggleIsOpen}>{isOpen ? 'Hide' : 'Show'}</button>
13 {isOpen && <img src={image} />}
14 </div>
15 );
16}

And let's return to page.js and compose that into our server component:
然后让我们回到 page.js 并将其组合到我们的服务器组件中:

page.js page.js 页面.js
1export default async function Page({ params }) {
2 const post = await sql`SELECT title, image FROM posts WHERE id = ${params.id}`;
3
4 if (!post) return notFound(); // 404
5
6 return (
7 <div>
8 {post.title}
9+ <Preview image={post.image} />
10 </div>
11 );
12}
A Reddit Post Reddit 帖子

That's it – that's all you have to do to "sprinkle" in interactivity. We're not managing spaghetti, we're composing an architecture that reaches across the hydration aisle, and we still were able to take full advantage of the server.
这就是了——这就是您需要做的来“添加”交互性。我们不是在管理意大利面,我们正在构建一个跨越水分化通道的架构,而且我们仍然能够充分利用服务器。

When this mental model clicks, when you start to mold it for yourself, you'll be shocked at how natural it feels. You can render incredibly complex components, svgs, charts, and other things on the server, and just compose in the small interactions when you need them. It's a more mature, more flexible programming model than anything we've had prior.
当这个思维模型发挥作用时,当你开始为自己塑造它时,你会对它感觉多么自然而感到震惊。你可以在服务器上渲染极其复杂的组件、SVG、图表和其他事物,只需在你需要时进行小交互的编写。这是一个比我们之前所拥有的任何东西都更成熟、更灵活的编程模型。

That all being said, it is hard to build that mental model. You're right to find it confusing. I think there are ways to simplify the mental model and over time, I expect these things will come to feel more natural, or other frameworks will adopt these patterns and make them more accessible.
所有这些话都说了,建立这样的心理模型很难。你发现它令人困惑是正确的。我认为有方法可以简化心理模型,随着时间的推移,我预计这些事情会感觉更加自然,或者其他框架会采用这些模式并使它们更容易获取。

Where Is My Code Running?
我的代码在哪里运行?

A friend phoned me for some help with RSCs and he pointedly asked "where the fuck is this running?" - where is this code executing? It's a frustrating and unsettling experience, it's not something any of us are used to. Running code in two environments is inherently complicated, and abstracting it away blurs that clarity in the pursuit of composition. It takes a second to understand why it's designed this way, but it does make sense.
一个朋友给我打电话寻求 RSCs 的帮助,他尖锐地问道:“这玩意儿到底在哪里运行?”——这段代码在哪里执行?这真是一种令人沮丧和不安的经历,这不是我们任何人习惯的事情。在两个环境中运行代码本质上很复杂,将其抽象化模糊了追求组合的清晰度。理解为什么它被设计成这样需要一点时间,但确实是有道理的。

There are two directives in next.js for RSCs: 'use client' and 'use server'. Let's start with the first one – you might guess that components marked with 'use client' are rendered on the client. But, you'd only be half correct: they are actually rendered on both the server and the client.
next.js 中有两个用于 RSC 的指令: 'use client''use server' 。让我们从第一个开始——你可能猜到标记为 'use client' 的组件是在客户端渲染的。但是,你只猜对了一半:它们实际上在服务器和客户端都会被渲染。

These are named "Client Components" because they are included in the client bundle, not because of where they are executed. Their initial state is rendered on the server and future updates are managed on the client.
这些被称为“客户端组件”,因为它们包含在客户端包中,而不是因为它们在哪里执行。它们的初始状态在服务器上渲染,未来的更新由客户端管理。

With this simple table, we can visualize where our code is being executed:
使用这个简单的表格,我们可以可视化代码的执行位置:

Server 服务器Client 客户端
Server Components less 服务器组件🚫
Client Components 客户端组件

I think a better way to think about client components is to consider them "Hydrated Components". Because their initial state renders on the server, and then hydrates. If they didn't render on the server initially, the "Show" button would magically appear well after the user loaded the page.
我认为思考客户端组件的更好方式是将它们视为“水合组件”。因为它们的初始状态在服务器上渲染,然后进行水合。如果它们最初不在服务器上渲染,那么“显示”按钮就会在用户加载页面后神奇地出现。

a chart explaining execution
execution environments 执行环境

Some of these "Client Components" may not be hydrated in the traditional sense (if they are hidden behind state, like our <img> tag in the Reddit example), but most of them will be and more importantly, they can be. Personally, I find it to be an easier mental framing because it more accurately explains in which environments your components could execute.
一些这些“客户端组件”可能不是在传统意义上被激活(如果它们被状态隐藏,就像 Reddit 示例中的 <img> 标签),但大多数都会,更重要的是,它们可以。我个人认为这是一种更简单的心理框架,因为它更准确地解释了组件可以在哪些环境中执行。

This brings us to the 'use server' directive, which is reserved for server actions. This is another network boundary pattern that is rather lovely, but has less to do with rendering components. It is an abstraction for communicating back to the server from the client (like an API request), using the server function as a reference to a POST request. It's not explicitly necessary to use in order to take advantage of RSCs, and is only additive. A better name, in my opinion, would be to give actions the directive of 'use action' or even 'use server action' to avoid the confusion with server components.
这使我们来到了 'use server' 指令,它是为服务器操作保留的。这是另一种相当可爱的网络边界模式,但与渲染组件的关系不大。它是对客户端(如 API 请求)从服务器返回通信的抽象,使用服务器函数作为 POST 请求的参考。为了利用 RSCs,并不需要明确使用它,它只是附加的。在我看来,更好的命名方法是将动作指令命名为 'use action' 甚至 'use server action' ,以避免与服务器组件混淆。

Server Components have no directive because they are the default. This is intentional because the html tree starts rendering from the top down, from the server. Hydration is sprinkled in further down the tree.
服务器组件没有指令,因为它们是默认的。这是故意的,因为 HTML 树从上到下、从服务器开始渲染。在树的更下方进行数据绑定。

The confusion around these names can be no further verified than by looking at React's own docs where they readily share that this is a common misconception. If your docs must point out such a profound misunderstanding, perhaps it would be best to improve the naming of these patterns.
关于这些名称的混淆无法比查看 React 自己的文档更进一步的验证,其中他们乐意分享这是一个常见的误解。如果你的文档必须指出这样的深刻误解,可能最好是改进这些模式的命名。

screenshot of the react docs
screenshot of the react docs
截图 React 文档

The reasoning behind these names is you have to begin your mental model with this idea: that everything starts in the server, the shell of your application begins at the server. From there, when you need to, the 'use client' directive moves you across the network boundary into the client bundle. And from the client, the 'use server' directive moves you back across the network boundary to the server to POST actions. Once this clicks, it becomes very natural and the abstraction is remarkably smooth, but it's clearly difficult for people to understand.
这些名称背后的推理是,你必须从以下想法开始构建你的心智模型:一切始于服务器,你的应用程序的壳层从服务器开始。从那里,当你需要时, 'use client' 指令将你移动到网络边界之外的客户端包。从客户端, 'use server' 指令将你移动回网络边界,以便在服务器上执行 POST 操作。一旦理解了这个,它就会变得非常自然,抽象也非常流畅,但显然对人们来说很难理解。

a chart explaining network boundaries
crossing the network boundary
跨越网络边界

Naming these concepts is incredibly hard - we are abstracting away the network in the pursuit of composition, it's not simple stuff. While the mental model is difficult to build, and the concepts can be tough to grasp, the engineering behind it is in fact solid and empowering. It is worth taking the time to play with these ideas.
命名这些概念极其困难 - 我们在追求组合的过程中抽象化网络,这不是简单的事情。虽然构建心理模型很困难,这些概念也难以理解,但背后的工程技术实际上是坚实且赋予力量的。花时间玩转这些想法是值得的。

Wrapping It Up 总结

Despite the broad confusion, RSCs give us newfound optionality as to how we architect websites. No longer do we have to pick between composition and hydration – we have access to the best of both worlds. If you take the time to learn them, I think you'll find the experience rather delightful, and unlike anything that has ever existed before. But there's plenty of room to make them more accessible, and to soften the cognitive load.
尽管存在广泛的困惑,RSCs 为我们提供了新的选择,关于如何构建网站。我们不再需要在组合和激活之间做出选择——我们可以同时获得两者的最佳之处。如果你花时间学习它们,我认为你会发现这种体验相当愉快,并且与之前存在的任何事物都不同。但还有很大的空间让它们更加易于访问,并减轻认知负担。

There are other solutions out there to the hydration problem, like Qwik's resumability paradigm or Astro's (and others) Islands Architecture. It's worth keeping an eye on these to see if we can solve this hydration composition problem in simpler ways.
其他地方还有解决水合问题的方案,比如 Qwik 的恢复性范式或 Astro(及其他)的岛屿架构。值得关注这些,看看我们是否可以用更简单的方式解决这个水合组合问题。

If you are happy with your PHP app or your vanilla react, if you truly have no need for RSCs, please keep using the technology that serves you. RSCs are not a one-size-fits-all solution, rather they are a great balance that scales remarkably well to many needs. I submit that most "Top 1,000+"5 public-facing websites would net-benefit from the technological advantages that RSCs provide, and even many that aren't public-facing. Please don't confuse this with me suggesting you drop everything and rewrite your applications, but for those of us who have experienced the hydration and composition problems, RSCs are a natural conclusion.
如果您对您的 PHP 应用程序或 vanilla react 感到满意,如果您确实不需要 RSCs,请继续使用为您服务的这项技术。RSCs 并非万能解决方案,而是非常适合多种需求的优秀平衡。我认为,大多数“Top 1,000+” 5 公开网站以及许多非公开网站都将从 RSCs 提供的科技优势中获得净收益。请不要将此误解为我建议您放弃一切并重写您的应用程序,但对于那些经历过水合和组合问题的人来说,RSCs 是一个自然的结论。

For future posts, I'd like to write more about how RSCs are a return to web standards, and how they encourage us to move client side state to the URL thereby giving our UIs more object permanence. I'd also like to explain how RSCs allow us to drop page-level loading states, an entire class of UI that is largely unnecessary, over-complex, and generally hinders user experience6. And I'd like to show you how server components ultimately will lead to better UX – which is really what matters most. If you are interested hearing more about these ideas, please send me a note below.
关于未来的帖子,我想更多地写关于 RSCs 如何回归网络标准,以及它们如何鼓励我们将客户端状态移动到 URL,从而赋予我们的 UIs 更多的物体恒常性。我还想解释 RSCs 如何让我们摆脱页面级别的加载状态,这是一个在很大程度上不必要、过于复杂且通常阻碍用户体验的 UI 类别。我还想展示服务器组件最终将如何导致更好的 UX——这实际上是最重要的。如果您对了解更多关于这些想法感兴趣,请在下方发给我一条消息。

that's the end. if you have a thought, please: email me.
这是结束。如果您有想法,请:给我发电子邮件。