惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
博客园_首页
Vercel News
Vercel News
WordPress大学
WordPress大学
美团技术团队
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 【当耐特】
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
腾讯CDC
M
MIT News - Artificial intelligence
爱范儿
爱范儿
Recent Announcements
Recent Announcements
雷峰网
雷峰网
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
Jina AI
Jina AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
Help Net Security
Help Net Security
N
News and Events Feed by Topic
博客园 - Franky
P
Proofpoint News Feed
L
LINUX DO - 热门话题
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
D
Docker
Google DeepMind News
Google DeepMind News
有赞技术团队
有赞技术团队
IT之家
IT之家
Security Latest
Security Latest
L
LangChain Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks

overreacted — A blog by Dan Abramov

There Are No Instances in atproto — overreacted Algebraic Effects for the Rest of Us — overreacted A Social Filesystem Introducing RSC Explorer — overreacted Hire Me in Japan — overreacted How to Fix Any Bug — overreacted Where It's at:// — overreacted Open Social A Lean Syntax Primer — overreacted Beyond Booleans — overreacted The Math Is Haunted — overreacted Suppressions of Suppressions — overreacted I'm Doing a Little Consulting — overreacted How Imports Work in RSC — overreacted RSC for LISP Developers — overreacted Progressive JSON — overreacted Why Does RSC Integrate with a Bundler? — overreacted One Roundtrip Per Navigation — overreacted Static as a Server — overreacted RSC for Astro Developers — overreacted Functional HTML — overreacted What Does "use client" Do? — overreacted Impossible Components JSX Over The Wire React for Two Computers The Two Reacts — overreacted npm audit: Broken by Design — overreacted Before You memo() — overreacted The WET Codebase — overreacted Goodbye, Clean Code — overreacted My Decade in Review — overreacted What Are the React Team Principles? — overreacted On let vs const — overreacted What Is JavaScript Made Of? — overreacted How Does the Development Mode Work? — overreacted Preparing for a Tech Talk, Part 3: Content — overreacted Name It, and They Will Come — overreacted Writing Resilient Components — overreacted A Complete Guide to useEffect How Are Function Components Different from Classes? — overreacted Coping with Feedback — overreacted Fix Like No One’s Watching — overreacted Making setInterval Declarative with React Hooks — overreacted React as a UI Runtime Why Isn’t X a Hook? — overreacted The “Bug-O” Notation — overreacted Preparing for a Tech Talk, Part 2: What, Why, and How — overreacted The Elements of UI Engineering — overreacted Things I Don’t Know as of 2018 — overreacted Preparing for a Tech Talk, Part 1: Motivation — overreacted Why Do React Hooks Rely on Call Order? — overreacted Optimized for Change — overreacted How Does setState Know What to Do? — overreacted My Wishlist for Hot Reloading — overreacted Why Do React Elements Have a $$typeof Property? — overreacted How Does React Tell a Class from a Function? — overreacted Why Do We Write super(props)? — overreacted
A Chain Reaction — overreacted
2023-12-11 · via overreacted — A blog by Dan Abramov

I wrote a bit of JSX in my editor:

<p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
  Hello, <i>Alice</i>!
</p>

Right now, this information only exists on my device. But with a bit of luck, it will travel through time and space to your device, and appear on your screen.

Hello, Alice!

The fact that this works is a marvel of engineering.

Deep inside of your browser, there are pieces of code that know how to display a paragraph or draw text in italics. These pieces of code are different between different browsers, and even between different versions of the same browser. Drawing to the screen is also done differently on different operating systems.

However, because these concepts have been given agreed-upon names (<p> for a paragraph, <i> for italics), I can refer to them without worrying how they really work on your device. I can’t directly access their internal logic but I know which information I can pass to them (such as a CSS className). Thanks to the web standards, I can be reasonably sure my greeting will appear as I intended.

Tags like <p> and <i> let us refer to the built-in browser concepts. However, names don’t have to refer to something built-in. For example, I’m using CSS classes like text-2xl and font-sans to style my greeting. I didn’t come up with those names myself—they come from a CSS library called Tailwind. I’ve included it on this page which lets me use any of the CSS class names it defines.

So why do we like giving names to things?


I wrote <p> and <i>, and my editor recognized those names. So did your browser. If you’ve done some web development, you probably recognized them too, and maybe even guessed what would appear on the screen by reading the markup. In that sense, names help us start with a bit of a shared understanding.

Fundamentally, computers execute relatively basic kinds of instructions—like adding or multiplying numbers, writing stuff to memory and reading from it, or communicating with external devices like a display. Merely showing a <p> on your screen could involve running hundreds of thousands of such instructions.

If you saw all the instructions your computer ran to display a <p> on the screen, you could hardly guess what they’re doing. It’s like trying to figure out which song is playing by analyzing all the atoms bouncing around the room. It would seem incomprehensible! You’d need to “zoom out” to see what’s going on.

To describe a complex system, or to instruct a complex system what to do, it helps to separate its behavior into layers that build on each other’s concepts.

This way, people working on screen drivers can focus on how to send the right colors to the right pixels. Then people working on text rendering can focus on how each character should turn into a bunch of pixels. And that lets people like me focus on picking just the right color for my “paragraphs” and “italics”.

We like names because they let us forget what’s behind them.


I’ve used many names that other people came up with. Some are built into the browsers, like <p> and <i>. Some are built into the tools I’m using, like text-2xl and font-sans. These may be my building blocks, but what am I building?

For example, what is this?

<p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
  Hello, <i>Alice</i>!
</p>

Hello, Alice!

From your browser’s perspective, this is a paragraph with certain CSS classes (which make it large and purple) and some text inside (part of it is in italics).

But from my perspective, it’s a greeting for Alice. Although my greeting happens to be a paragraph, most of the time I want to think about it this way instead:

<Greeting person={alice} />

Giving this concept a name provides me with some newfound flexibility. I can now display multiple Greetings without copying and pasting their markup. I can pass different data to them. If I wanted to change how all greetings look and behave, I could do it in a single place. Turning Greeting into its own concept lets me adjust “which greetings to display” separately from “what a greeting is”.

However, I have also introduced a problem.

Now that I’ve given this concept a name, the “language” in my mind is different from the “language” that your browser speaks. Your browser knows about <p> and <i>, but it has never heard of a <Greeting>—that’s my own concept. If I wanted your browser to understand what I mean, I’d have to “translate” this piece of markup to only use the concepts that your browser already knows.

I’d need to turn this:

<Greeting person={alice} />

into this:

<p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
  Hello, <i>Alice</i>!
</p>

How would I go about that?


To name something, I need to define it.

For example, alice does not mean anything until I define alice:

const alice = {
  firstName: 'Alice',
  birthYear: 1970
};

Now alice refers to that JavaScript object.

Similarly, I need to actually define what my concept of a Greeting means.

I will define a Greeting for any person as a paragraph showing “Hello, ” followed by that person’s first name in italics, plus an exclamation mark:

function Greeting({ person }) {
  return (
    <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
      Hello, <i>{person.firstName}</i>!
    </p>
  );
}

Unlike alice, I defined Greeting as a function. This is because a greeting would have to be different for every person. Greeting is a piece of code—it performs a transformation or a translation. It turns some data into some UI.

That gives me an idea for what to do with this:

<Greeting person={alice} />

Your browser wouldn’t know what a Greeting is—that’s my own concept. But now that I wrote a definition for that concept, I can apply this definition to “unpack” what I meant. You see, a greeting for a person is actually a paragraph:

function Greeting({ person }) {
  return (
    <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
      Hello, <i>{person.firstName}</i>!
    </p>
  );
}

Plugging the alice’s data into that definition, I end up with this final JSX:

<p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
  Hello, <i>Alice</i>!
</p>

At this point I only refer to the browser’s own concepts. By substituting the Greeting with what I defined it to be, I have “translated” it for your browser.

Hello, Alice!

Now let’s teach a computer to do the same thing.


Take a look at what JSX is made of.

const originalJSX = <Greeting person={alice} />;
console.log(originalJSX.type);  // Greeting
console.log(originalJSX.props); // { person: { firstName: 'Alice', birthYear: 1970 } }

Under the hood, JSX constructs an object with the type property corresponding to the tag, and the props property corresponding to the JSX attributes.

You can think of type as being the “code” and props as being the “data”. To get the result, you need to plug that data into that code like I’ve done earlier.

Here is a little function I wrote that does exactly that:

function translateForBrowser(originalJSX) {
  const { type, props } = originalJSX;
  return type(props);
}

In this case, type will be Greeting and props will be { person: alice }, so translateForBrowser(<Greeting person={alice} />) will return the result of calling Greeting with { person: alice } as the argument.

Which, as you might recall from the previous section, would give me this:

<p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
  Hello, <i>Alice</i>!
</p>

And that’s exactly what I wanted!

You can verify that feeding my original piece of JSX to translateForBrowser will produce the “browser JSX” that only refers to concepts like <p> and <i>.

const originalJSX = <Greeting person={alice} />;
console.log(originalJSX.type);  // Greeting
console.log(originalJSX.props); // { person: { firstName: 'Alice', birthYear: 1970 } }
 
const browserJSX = translateForBrowser(originalJSX);
console.log(browserJSX.type);  // 'p'
console.log(browserJSX.props); // { className: 'text-2xl font-sans text-purple-400 dark:text-purple-500', children: ['Hello', { type: 'i', props: { children: 'Alice' }, '!'] }

There are many things I could do with that “browser JSX”. For example, I could turn it into an HTML string to be sent to the browser. I could also convert it into a sequence of instructions that update an already existing DOM node. For now, I won’t be focusing on the different ways to use it. All that matters right now is that by the time I have the “browser JSX”, there is nothing left to “translate”.

It’s as if my <Greeting> has dissolved, and <p> and <i> are the residue.


Let’s try something a tiny bit more complex. Suppose I want to wrap my greeting inside a <details> tag so that it appears collapsed by default:

<details>
  <Greeting person={alice} />
</details>

The browser should display it like this (click “Details” to expand it!)

Hello, Alice!

So now my task is to figure out how to turn this:

<details>
  <Greeting person={alice} />
</details>

into this:

<details>
  <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
    Hello, <i>Alice</i>!
  </p>
</details>

Let’s see if translateForBrowser can already handle that.

const originalJSX = (
  <details>
    <Greeting person={alice} />
  </details>
);
console.log(originalJSX.type);  // 'details'
console.log(originalJSX.props); // { children: { type: Greeting, props: { person: alice } } }
 
const browserJSX = translateForBrowser(originalJSX);

You will get an error inside of the translateForBrowser call:

function translateForBrowser(originalJSX) {
  const { type, props } = originalJSX;
  return type(props); // 🔴 TypeError: type is not a function
}

What happened here? My translateForBrowser implementation assumed that type—that is, originalJSX.type—is always a function like Greeting.

However, notice that originalJSX.type is actually a string this time:

const originalJSX = (
  <details>
    <Greeting person={alice} />
  </details>
);
console.log(originalJSX.type);  // 'details'
console.log(originalJSX.props); // { children: { type: Greeting, props: { person: alice } } }

When you start a JSX tag with a lower case (like <details>), by convention it’s assumed that you want a built-in tag rather than some function you defined.

Since built-in tags don’t have any code associated with them (that code is somewhere inside your browser!), the type will be a string like 'details'. How <details> work is opaque to my code—all I really know is its name.

Let’s split the logic in two cases, and skip translating the built-ins for now:

function translateForBrowser(originalJSX) {
  const { type, props } = originalJSX;
  if (typeof type === 'function') {
    return type(props);
  } else if (typeof type === 'string') {
    return originalJSX;
  }
}

After this change, translateForBrowser will only attempt to call some function if the original JSX’s type actually is a function like Greeting.

So that’s the result I wanted, right?…

<details>
  <Greeting person={alice} />
</details>

Wait. What I wanted is this:

<details>
  <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
    Hello, <i>Alice</i>!
  </p>
</details>

In my translation process, I want to skip over the <details> tag because its implementation is opaque to me. I can’t do anything useful with it—it is fully up to the browser. However, anything inside of it may still need to be translated!

Let’s fix translateForBrowser to translate any built-in tag’s children:

function translateForBrowser(originalJSX) {
  const { type, props } = originalJSX;
  if (typeof type === 'function') {
    return type(props);
  } else if (typeof type === 'string') {
    return {
      type,
      props: {
        ...props,
        children: translateForBrowser(props.children)
      }
    };
  }
}

With this change, when it meets an element like <details>...</details>, it will return another <details>...</details> tag, but the stuff inside of it would be translated with my function again—so the Greeting will be gone:

<details>
  <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
    Hello, <i>Alice</i>!
  </p>
</details>

And now I am speaking the browser’s “language” again:

Hello, Alice!

The Greeting has been dissolved.


Now suppose that I try to define an ExpandableGreeting:

function ExpandableGreeting({ person }) {
  return (
    <details>
      <Greeting person={person} />
    </details>
  );
}

Here is my new original JSX:

<ExpandableGreeting person={alice} />

If I run it through translateForBrowser, I’ll get this JSX in return:

<details>
  <Greeting person={alice} />
</details>

But that’s not what I wanted! It still has a Greeting in it, and we don’t consider a piece of JSX “browser-ready” until all of my own concepts are gone.

This is a bug in my translateForBrowser function. When it calls a function like ExpandableGreeting, it will return its output, and not do anything else. But we need to keep on going! That returned JSX also needs to be translated.

Luckily, there is an easy way I can solve this. When I call a function like ExpandableGreeting, I can take the JSX it returned and translate that next:

function translateForBrowser(originalJSX) {
  const { type, props } = originalJSX;
  if (typeof type === 'function') {
    const returnedJSX = type(props);
    return translateForBrowser(returnedJSX);
  } else if (typeof type === 'string') {
    return {
      type,
      props: {
        ...props,
        children: translateForBrowser(props.children)
      }
    };
  }
}

I also need to stop the process when there’s nothing left to translate, like if it receives null or a string. If it receives an array of things, I need to translate each of them. With these two fixes, translateForBrowser is complete:

function translateForBrowser(originalJSX) {
  if (originalJSX == null || typeof originalJSX !== 'object') {
    return originalJSX;
  }
  if (Array.isArray(originalJSX)) {
    return originalJSX.map(translateForBrowser);
  }
  const { type, props } = originalJSX;
  if (typeof type === 'function') {
    const returnedJSX = type(props);
    return translateForBrowser(returnedJSX);
  } else if (typeof type === 'string') {
    return {
      type,
      props: {
        ...props,
        children: translateForBrowser(props.children)
      }
    };
  }
}

Now, suppose that I start with this:

<ExpandableGreeting person={alice} />

It will turn into this:

<details>
  <Greeting person={alice} />
</details>

Which will turn into this:

<details>
  <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
    Hello, <i>Alice</i>!
  </p>
</details>

And at that point, the process will stop.


Let’s see how this works one more time, with a bit of extra depth.

I’ll define WelcomePage like this:

function WelcomePage() {
  return (
    <section>
      <h1 className="text-3xl font-sans pb-2">Welcome</h1>
      <ExpandableGreeting person={alice} />
      <ExpandableGreeting person={bob} />
      <ExpandableGreeting person={crystal} />
    </section>
  );
}

Now let’s say I start the process with this original JSX:

<WelcomePage />

Can you retrace the sequence of transformations in your head?

Let’s do it step by step together.

First, imagine WelcomePage dissolving, leaving behind its output:

<section>
  <h1 className="text-3xl font-sans pb-2">Welcome</h1>
  <ExpandableGreeting person={alice} />
  <ExpandableGreeting person={bob} />
  <ExpandableGreeting person={crystal} />
</section>

Then imagine each ExpandableGreeting dissolving, leaving behind its output:

<section>
  <h1 className="text-3xl font-sans pb-2">Welcome</h1>
  <details>
    <Greeting person={alice} />
  </details>
  <details>
    <Greeting person={bob} />
  </details>
  <details>
    <Greeting person={crystal} />
  </details>
</section>

Then imagine each Greeting dissolving, leaving behind its output:

<section>
  <h1 className="text-3xl font-sans pb-2">Welcome</h1>
  <details>
    <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
      Hello, <i>Alice</i>!
    </p>
  </details>
  <details>
    <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
      Hello, <i>Bob</i>!
    </p>
  </details>
  <details>
    <p className="text-2xl font-sans text-purple-400 dark:text-purple-500">
      Hello, <i>Crystal</i>!
    </p>
  </details>
</section>

And now there is nothing left to “translate”. All my concepts have dissolved.

Welcome

Hello, Alice!

Hello, Bob!

Hello, Crystal!

This feels like a chain reaction. You mix a bit of data and code, and it keeps transforming until there is no more code to run, and only the residue is left.

It would be nice if there was a library that could do this for us.

But wait, here’s a question. These transformations have to happen somewhere on the way between your computer and mine. So where do they happen?

Do they happen on your computer?

Or do they happen on mine?