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

推荐订阅源

博客园 - 【当耐特】
WordPress大学
WordPress大学
T
The Exploit Database - CXSecurity.com
博客园_首页
MyScale Blog
MyScale Blog
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
F
Full Disclosure
V
V2EX
博客园 - Franky
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
SecWiki News
SecWiki News
N
Netflix TechBlog - Medium
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
Hacker News: Ask HN
Hacker News: Ask HN
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Webroot Blog
Webroot Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Martin Fowler
Martin Fowler
PCI Perspectives
PCI Perspectives
S
Security @ Cisco Blogs
Recorded Future
Recorded Future
Help Net Security
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
AI
AI
Microsoft Azure Blog
Microsoft Azure Blog
K
Kaspersky official blog
G
GRAHAM CLULEY
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
CERT Recently Published Vulnerability Notes
U
Unit 42
T
Tor Project blog
Cloudbric
Cloudbric
Hacker News - Newest:
Hacker News - Newest: "LLM"
MongoDB | Blog
MongoDB | Blog
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
N
News and Events Feed by Topic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

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 A Chain Reaction — 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 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
How Does setState Know What to Do? — overreacted
2018-12-09 · via overreacted — A blog by Dan Abramov

When you call setState in a component, what do you think happens?

import React from 'react';
import ReactDOM from 'react-dom';
 
class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicked: false };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({ clicked: true });
  }
  render() {
    if (this.state.clicked) {
      return <h1>Thanks</h1>;
    }
    return (
      <button onClick={this.handleClick}>
        Click me!
      </button>
    );
  }
}
 
ReactDOM.render(<Button />, document.getElementById('container'));

Sure, React re-renders the component with the next { clicked: true } state and updates the DOM to match the returned <h1>Thanks</h1> element.

Seems straightforward. But wait, does React do it? Or React DOM?

Updating the DOM sounds like something React DOM would be responsible for. But we’re calling this.setState(), not something from React DOM. And our React.Component base class is defined inside React itself.

So how can setState() inside React.Component update the DOM?

Disclaimer: Just like most other posts on this blog, you don’t actually need to know any of that to be productive with React. This post is for those who like to see what’s behind the curtain. Completely optional!


We might think that the React.Component class contains DOM update logic.

But if that were the case, how can this.setState() work in other environments? For example, components in React Native apps also extend React.Component. They call this.setState() just like we did above, and yet React Native works with Android and iOS native views instead of the DOM.

You might also be familiar with React Test Renderer or Shallow Renderer. Both of these testing strategies let you render normal components and call this.setState() inside them. But neither of them works with the DOM.

If you used renderers like React ART, you might also know that it’s possible to use more than one renderer on the page. (For example, ART components work inside a React DOM tree.) This makes a global flag or variable untenable.

So somehow React.Component delegates handling state updates to the platform-specific code. Before we can understand how this happens, let’s dig deeper into how packages are separated and why.


There is a common misconception that the React “engine” lives inside the react package. This is not true.

In fact, ever since the package split in React 0.14, the react package intentionally only exposes APIs for defining components. Most of the implementation of React lives in the “renderers”.

react-dom, react-dom/server, react-native, react-test-renderer, react-art are some examples of renderers (and you can build your own).

This is why the react package is useful regardless of which platform you target. All its exports, such as React.Component, React.createElement, React.Children utilities and (eventually) Hooks, are independent of the target platform. Whether you run React DOM, React DOM Server, or React Native, your components would import and use them in the same way.

In contrast, the renderer packages expose platform-specific APIs like ReactDOM.render() that let you mount a React hierarchy into a DOM node. Each renderer provides an API like this. Ideally, most components shouldn’t need to import anything from a renderer. This keeps them more portable.

What most people imagine as the React “engine” is inside each individual renderer. Many renderers include a copy of the same code — we call it the “reconciler”. A build step smooshes the reconciler code together with the renderer code into a single highly optimized bundle for better performance. (Copying code is usually not great for bundle size but the vast majority of React users only needs one renderer at a time, such as react-dom.)

The takeaway here is that the react package only lets you use React features but doesn’t know anything about how they’re implemented. The renderer packages (react-dom, react-native, etc) provide the implementation of React features and platform-specific logic. Some of that code is shared (“reconciler”) but that’s an implementation detail of individual renderers.


Now we know why both react and react-dom packages need to be updated for new features. For example, when React 16.3 added the Context API, React.createContext() was exposed on the React package.

But React.createContext() doesn’t actually implement the context feature. The implementation would need to be different between React DOM and React DOM Server, for example. So createContext() returns a few plain objects:

// A bit simplified
function createContext(defaultValue) {
  let context = {
    _currentValue: defaultValue,
    Provider: null,
    Consumer: null
  };
  context.Provider = {
    $$typeof: Symbol.for('react.provider'),
    _context: context
  };
  context.Consumer = {
    $$typeof: Symbol.for('react.context'),
    _context: context,
  };
  return context;
}

When you use <MyContext.Provider> or <MyContext.Consumer> in the code, it’s the renderer that decides how to handle them. React DOM might track context values in one way, but React DOM Server might do it differently.

So if you update react to 16.3+ but don’t update react-dom, you’d be using a renderer that isn’t yet aware of the special Provider and Consumer types. This is why an older react-dom would fail saying these types are invalid.

The same caveat applies to React Native. However, unlike React DOM, a React release doesn’t immediately “force” a React Native release. They have an independent release schedule. The updated renderer code is separately synced into the React Native repository once in a few weeks. This is why features become available in React Native on a different schedule than in React DOM.


Okay, so now we know that the react package doesn’t contain anything interesting, and the implementation lives in renderers like react-dom, react-native, and so on. But that doesn’t answer our question. How does setState() inside React.Component “talk” to the right renderer?

The answer is that every renderer sets a special field on the created class. This field is called updater. It’s not something you would set — rather, it’s something React DOM, React DOM Server or React Native set right after creating an instance of your class:

// Inside React DOM
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;
 
// Inside React DOM Server
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;
 
// Inside React Native
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;

Looking at the setState implementation in React.Component, all it does is delegate work to the renderer that created this component instance:

// A bit simplified
setState(partialState, callback) {
  // Use the `updater` field to talk back to the renderer!
  this.updater.enqueueSetState(this, partialState, callback);
}

React DOM Server might want to ignore a state update and warn you, whereas React DOM and React Native would let their copies of the reconciler handle it.

And this is how this.setState() can update the DOM even though it’s defined in the React package. It reads this.updater which was set by React DOM, and lets React DOM schedule and handle the update.


We know about classes now, but what about Hooks?

When people first look at the Hooks proposal API, they often wonder: how does useState “know what to do”? The assumption is that it’s more “magical” than a base React.Component class with this.setState().

But as we have seen today, the base class setState() implementation has been an illusion all along. It doesn’t do anything except forwarding the call to the current renderer. And useState Hook does exactly the same thing.

Instead of an updater field, Hooks use a “dispatcher” object. When you call React.useState(), React.useEffect(), or another built-in Hook, these calls are forwarded to the current dispatcher.

// In React (simplified a bit)
const React = {
  // Real property is hidden a bit deeper, see if you can find it!
  __currentDispatcher: null,
 
  useState(initialState) {
    return React.__currentDispatcher.useState(initialState);
  },
 
  useEffect(initialState) {
    return React.__currentDispatcher.useEffect(initialState);
  },
  // ...
};

And individual renderers set the dispatcher before rendering your component:

// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;
let result;
try {
  result = YourComponent(props);
} finally {
  // Restore it back
  React.__currentDispatcher = prevDispatcher;
}

For example, the React DOM Server implementation is here, and the reconciler implementation shared by React DOM and React Native is here.

This is why a renderer such as react-dom needs to access the same react package that you call Hooks from. Otherwise, your component won’t “see” the dispatcher! This may not work when you have multiple copies of React in the same component tree. However, this has always led to obscure bugs so Hooks force you to solve the package duplication before it costs you.

While we don’t encourage this, you can technically override the dispatcher yourself for advanced tooling use cases. (I lied about __currentDispatcher name but you can find the real one in the React repo.) For example, React DevTools will use a special purpose-built dispatcher to introspect the Hooks tree by capturing JavaScript stack traces. Don’t repeat this at home.

This also means Hooks aren’t inherently tied to React. If in the future more libraries want to reuse the same primitive Hooks, in theory the dispatcher could move to a separate package and be exposed as a first-class API with a less “scary” name. In practice, we’d prefer to avoid premature abstraction until there is a need for it.

Both the updater field and the __currentDispatcher object are forms of a generic programming principle called dependency injection. In both cases, the renderers “inject” implementations of features like setState into the generic React package to keep your components more declarative.

You don’t need to think about how this works when you use React. We’d like React users to spend more time thinking about their application code than abstract concepts like dependency injection. But if you’ve ever wondered how this.setState() or useState() know what to do, I hope this helps.