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

推荐订阅源

WordPress大学
WordPress大学
Security Latest
Security Latest
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
D
Docker
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
Schneier on Security
Schneier on Security
小众软件
小众软件
爱范儿
爱范儿
GbyAI
GbyAI
J
Java Code Geeks
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Securelist
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Y
Y Combinator Blog
S
Schneier on Security
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
F
Fortinet All Blogs
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
V
V2EX
V2EX - 技术
V2EX - 技术
O
OpenAI News
W
WeLiveSecurity

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 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
Why Isn’t X a Hook? — overreacted
2019-01-26 · via overreacted — A blog by Dan Abramov

Since the first alpha version of React Hooks was released, there is a question that keeps coming up in discussions: “Why isn’t <some other API> a Hook?”

To remind you, here’s a few things that are Hooks:

But there are some other APIs, like React.memo() and <Context.Provider>, that are not Hooks. Commonly proposed Hook versions of them would be noncompositional or antimodular. This article will help you understand why.

Note: this post is a deep dive for folks who are interested in API discussions. You don’t need to think about any of this to be productive with React!


There are two important properties that we want React APIs to preserve:

  1. Composition: Custom Hooks are largely the reason we’re excited about the Hooks API. We expect people to build their own Hooks very often, and we need to make sure Hooks written by different people don’t conflict. (Aren’t we all spoiled by how components compose cleanly and don’t break each other?)

  2. Debugging: We want the bugs to be easy to find as the application grows. One of React’s best features is that if you see something wrong rendered, you can walk up the tree until you find which component’s prop or state caused the mistake.

These two constraints put together can tell us what can or cannot be a Hook. Let’s try a few examples.


A Real Hook: useState()

Composition

Multiple custom Hooks each calling useState() don’t conflict:

function useMyCustomHook1() {
  const [value, setValue] = useState(0);
  // What happens here, stays here.
}
 
function useMyCustomHook2() {
  const [value, setValue] = useState(0);
  // What happens here, stays here.
}
 
function MyComponent() {
  useMyCustomHook1();
  useMyCustomHook2();
  // ...
}

Adding a new unconditional useState() call is always safe. You don’t need to know anything about other Hooks used by a component to declare a new state variable. You also can’t break other state variables by updating one of them.

Verdict:useState() doesn’t make custom Hooks fragile.

Debugging

Hooks are useful because you can pass values between Hooks:

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  // ...
  return width;
}
 
function useTheme(isMobile) {
  // ...
}
 
function Comment() {
  const width = useWindowWidth();
  const isMobile = width < MOBILE_VIEWPORT;
  const theme = useTheme(isMobile);
  return (
    <section className={theme.comment}>
      {/* ... */}
    </section>
  );
}

But what if we make a mistake? What’s the debugging story?

Let’s say the CSS class we get from theme.comment is wrong. How would we debug this? We can set a breakpoint or a few logs in the body of our component.

Maybe we’d see that theme is wrong but width and isMobile are correct. That would tell us the problem is inside useTheme(). Or perhaps we’d see that width itself is wrong. That would tell us to look into useWindowWidth().

A single look at the intermediate values tells us which of the Hooks at the top level contains the bug. We don’t need to look at all of their implementations.

Then we can “zoom in” on the one that has a bug, and repeat.

This becomes more important if the depth of custom Hook nesting increases. Imagine we have 3 levels of custom Hook nesting, each level using 3 different custom Hooks inside. The difference between looking for a bug in 3 places versus potentially checking 3 + 3×3 + 3×3×3 = 39 places is enormous. Luckily, useState() can’t magically “influence” other Hooks or components. A buggy value returned by it leaves a trail behind it, just like any variable. 🐛

Verdict:useState() doesn’t obscure the cause-effect relationship in our code. We can follow the breadcrumbs directly to the bug.


Not a Hook: useBailout()

As an optimization, components using Hooks can bail out of re-rendering.

One way to do it is to put a React.memo() wrapper around the whole component. It bails out of re-rendering if props are shallowly equal to what we had during the last render. This makes it similar to PureComponent in classes.

React.memo() takes a component and returns a component:

function Button(props) {
  // ...
}
export default React.memo(Button);

But why isn’t it just a Hook?

Whether you call it useShouldComponentUpdate(), usePure(), useSkipRender(), or useBailout(), the proposal tends to look something like this:

function Button({ color }) {
  // ⚠️ Not a real API
  useBailout(prevColor => prevColor !== color, color);
 
  return (
    <button className={'button-' + color}>  
      OK
    </button>
  )
}

There are a few more variations (e.g. a simple usePure() marker) but in broad strokes they have the same flaws.

Composition

Let’s say we try to put useBailout() in two custom Hooks:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
 
  // ⚠️ Not a real API
  useBailout(prevIsOnline => prevIsOnline !== isOnline, isOnline);
 
  useEffect(() => {
    const handleStatusChange = status => setIsOnline(status.isOnline);
    ChatAPI.subscribe(friendID, handleStatusChange);
    return () => ChatAPI.unsubscribe(friendID, handleStatusChange);
  });
 
  return isOnline;
}
 
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  
  // ⚠️ Not a real API
  useBailout(prevWidth => prevWidth !== width, width);
 
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  });
 
  return width;
}

Now what happens if you use them both in the same component?

function ChatThread({ friendID, isTyping }) {
  const width = useWindowWidth();
  const isOnline = useFriendStatus(friendID);
  return (
    <ChatLayout width={width}>
      <FriendStatus isOnline={isOnline} />
      {isTyping && 'Typing...'}
    </ChatLayout>
  );
}

When does it re-render?

If every useBailout() call has the power to skip an update, then updates from useWindowWidth() would be blocked by useFriendStatus(), and vice versa. These Hooks would break each other.

However, if useBailout() was only respected when all calls to it inside a single component “agree” to block an update, our ChatThread would fail to update on changes to the isTyping prop.

Even worse, with these semantics any newly added Hooks to ChatThread would break if they don’t also call useBailout(). Otherwise, they can’t “vote against” the bailout inside useWindowWidth() and useFriendStatus().

Verdict: 🔴 useBailout() breaks composition. Adding it to a Hook breaks state updates in other Hooks. We want the APIs to be antifragile, and this behavior is pretty much the opposite.

Debugging

How does a Hook like useBailout() affect debugging?

We’ll use the same example:

function ChatThread({ friendID, isTyping }) {
  const width = useWindowWidth();
  const isOnline = useFriendStatus(friendID);
  return (
    <ChatLayout width={width}>
      <FriendStatus isOnline={isOnline} />
      {isTyping && 'Typing...'}
    </ChatLayout>
  );
}

Let’s say the Typing... label doesn’t appear when we expect, even though somewhere many layers above the prop is changing. How do we debug it?

Normally, in React you can confidently answer this question by looking up. If ChatThread doesn’t get a new isTyping value, we can open the component that renders <ChatThread isTyping={myVar} /> and check myVar, and so on. At one of these levels, we’ll either find a buggy shouldComponentUpdate() bailout, or an incorrect isTyping value being passed down. One look at each component in the chain is usually enough to locate the source of the problem.

However, if this useBailout() Hook was real, you would never know the reason an update was skipped until you checked every single custom Hook (deeply) used by our ChatThread and components in its owner chain. Since every parent component can also use custom Hooks, this scales terribly.

It’s like if you were looking for a screwdriver in a chest of drawers, and each drawer contained a bunch of smaller chests of drawers, and you don’t know how deep the rabbit hole goes.

Verdict: 🔴 Not only useBailout() Hook breaks composition, but it also vastly increases the number of debugging steps and cognitive load for finding a buggy bailout — in some cases, exponentially.


We just looked at one real Hook, useState(), and a common suggestion that is intentionally not a Hook — useBailout(). We compared them through the prism of Composition and Debugging, and discussed why one of them works and the other one doesn’t.

While there is no “Hook version” of memo() or shouldComponentUpdate(), React does provide a Hook called useMemo(). It serves a similar purpose, but its semantics are different enough to not run into the pitfalls described above.

useBailout() is just one example of something that doesn’t work well as a Hook. But there are a few others — for example, useProvider(), useCatch(), or useSuspense().

Can you see why?

(Whispers: Composition… Debugging…)