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

推荐订阅源

Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
S
Security Affairs
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
小众软件
小众软件
Security Latest
Security Latest
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
F
Full Disclosure
S
Schneier on Security
L
LangChain Blog
MyScale Blog
MyScale Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
Google Online Security Blog
Google Online Security Blog
Scott Helme
Scott Helme
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
A
Arctic Wolf
Martin Fowler
Martin Fowler
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
Latest news
Latest news
F
Fortinet All Blogs
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites Why I Love Remix The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React How I structure Express apps What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode Profile a React App for Performance
When to useMemo and useCallback
Kent C. Dodds 🏹 @kentcdodds · 2019-06-04 · via Kent C. Dodds Blog

Here's a candy dispenser:

Candy Dispenser

Available candy

  • snickers
  • skittles
  • twix
  • milky way

Here's how it's implemented:

function CandyDispenser() {
	const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
	const [candies, setCandies] = React.useState(initialCandies)
	const dispense = (candy) => {
		setCandies((allCandies) => allCandies.filter((c) => c !== candy))
	}
	return (
		<div>
			<h1>Candy Dispenser</h1>
			<div>
				<div>Available Candy</div>
				{candies.length === 0 ? (
					<button onClick={() => setCandies(initialCandies)}>refill</button>
				) : (
					<ul>
						{candies.map((candy) => (
							<li key={candy}>
								<button onClick={() => dispense(candy)}>grab</button> {candy}
							</li>
						))}
					</ul>
				)}
			</div>
		</div>
	)
}

Now I want to ask you a question and I want you to think hard about it before moving forward. I'm going to make a change to this and I want you to tell me which will have the better performance characteristics.

The only thing I'm going to change is wrap the dispense function inside React.useCallback:

const dispense = React.useCallback((candy) => {
	setCandies((allCandies) => allCandies.filter((c) => c !== candy))
}, [])

Here's the original again:

const dispense = (candy) => {
	setCandies((allCandies) => allCandies.filter((c) => c !== candy))
}

So here's my question, in this specific case, which of these is better for performance? Go ahead and submit your guess (this is not recorded anywhere):

Let me give you some space to not spoil the answer for you...

Keep scrolling... You did answer, didn't you?

There, that should do it...

Why is useCallback worse?!

We hear a lot that you should use React.useCallback to improve performance and that "inline functions can be problematic for performance," so how could it ever be better to not useCallback?

Just take a step back from our specific example, and even from React and consider this: Every line of code which is executed comes with a cost. Let me refactor the useCallback example a bit (no actual changes, just moving things around) to illustrate things more clearly:

const dispense = (candy) => {
	setCandies((allCandies) => allCandies.filter((c) => c !== candy))
}
const dispenseCallback = React.useCallback(dispense, [])

And here's the original again:

const dispense = (candy) => {
	setCandies((allCandies) => allCandies.filter((c) => c !== candy))
}

Notice anything about these? Let's look at the diff:

const dispense = (candy) => {
	setCandies((allCandies) => allCandies.filter((c) => c !== candy))
}
const dispenseCallback = React.useCallback(dispense, [])

Yeah, they're exactly the same except the useCallback version is doing more work. Not only do we have to define the function, but we also have to define an array ([]) and call the React.useCallback which itself is setting properties/running through logical expressions etc.

So in both cases JavaScript must allocate memory for the function definition on every render and depending on how useCallback is implemented, you may get more allocation for function definitions (this is actually not the case, but the point still stands). This is what I was trying to get across with my twitter poll here:

Assuming this code appears in a React function component, how many function allocations are happening with this code on each render? const a = () => {} And how many are happening with this code? const a = useCallback(() => {}, [])

20 176

Granted, I had several people tell me that was worded poorly, so my apologies if you got the wrong answer but actually knew the correct answer.

I'd like to mention also that on the second render of the component, the original dispense function gets garbage collected (freeing up memory space) and then a new one is created. However with useCallback the original dispense function won't get garbage collected and a new one is created, so you're worse-off from a memory perspective as well.

As a related note, if you have dependencies then it's quite possible React is hanging on to a reference to previous functions because memoization typically means that we keep copies of old values to return in the event we get the same dependencies as given previously. The especially astute of you will notice that this means React also has to hang on to a reference to the dependencies for this equality check (which incidentally is probably happening anyway thanks to your closure, but it's something worth mentioning anyway).

How is useMemo different, but similar?

useMemo is similar to useCallback except it allows you to apply memoization to any value type (not just functions). It does this by accepting a function which returns the value and then that function is only called when the value needs to be retrieved (which typically will only happen once each time an element in the dependencies array changes between renders).

So, if I didn't want to initialize that array of initialCandies every render, I could make this change:

const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
const initialCandies = React.useMemo(
	() => ['snickers', 'skittles', 'twix', 'milky way'],
	[],
)

And I would avoid that problem, but the savings would be so minimal that the cost of making the code more complex just isn't worth it. In fact, it's probably worse to use useMemo for this as well because again we're making a function call and that code is doing property assignments etc.

In this particular scenario, what would be even better is to make this change:

const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
function CandyDispenser() {
  const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
  const [candies, setCandies] = React.useState(initialCandies)

But sometimes you don't have that luxury because the value is either derived from props or other variables initialized within the body of the function.

The point is that it doesn't matter either way. The benefits of optimizing that code is so minuscule that your time would be WAY better spent worrying about making your product better.

What's the point?

The point is this:

Performance optimizations are not free. They ALWAYS come with a cost but do NOT always come with a benefit to offset that cost.

Therefore, optimize responsibly.

So when should I useMemo and useCallback?

There are specific reasons both of these hooks are built-into React:

  1. Referential equality
  2. Computationally expensive calculations

Referential equality

If you're new to JavaScript/programming, it wont take long before you learn why this is the case:

true === true // true
false === false // true
1 === 1 // true
'a' === 'a' // true

{} === {} // false
[] === [] // false
(() => {}) === (() => {}) // false

const z = {}
z === z // true

// NOTE: React actually uses Object.is, but it's very similar to ===

I'm not going to go too deep into this, but suffice it to say when you define an object inside your React function component, it is not going to be referentially equal to the last time that same object was defined (even if it has all the same properties with all the same values).

There are two situations where referential equality matters in React, let's go through them one at a time.

Dependencies lists

Let's review an example.

Warning, you're about to see some seriously contrived code. Please don't nit-pick that and just focus on the concepts please, thank you.

function Foo({ bar, baz }) {
	const options = { bar, baz }
	React.useEffect(() => {
		buzz(options)
	}, [options]) // we want this to re-run if bar or baz change
	return <div>foobar</div>
}

function Blub() {
	return <Foo bar="bar value" baz={3} />
}

The reason this is problematic is useEffect is going to do a referential equality check on options between every render, and thanks to the way JavaScript works, options will be new every time so when React tests whether options changed between renders it'll always evaluate to true, meaning the useEffect callback will be called after every render rather than only when bar and baz change.

There are two things we can do to fix this:

// option 1
function Foo({ bar, baz }) {
	React.useEffect(() => {
		const options = { bar, baz }
		buzz(options)
	}, [bar, baz]) // we want this to re-run if bar or baz change
	return <div>foobar</div>
}

That's a great option and if this were a real thing that's how I'd fix this.

But there's one situation when this isn't a practical solution: If bar or baz are (non-primitive) objects/arrays/functions/etc:

function Blub() {
	const bar = () => {}
	const baz = [1, 2, 3]
	return <Foo bar={bar} baz={baz} />
}

This is precisely the reason why useCallback and useMemo exist. So here's how you'd fix that (all together now):

function Foo({ bar, baz }) {
	React.useEffect(() => {
		const options = { bar, baz }
		buzz(options)
	}, [bar, baz])
	return <div>foobar</div>
}

function Blub() {
	const bar = React.useCallback(() => {}, [])
	const baz = React.useMemo(() => [1, 2, 3], [])
	return <Foo bar={bar} baz={baz} />
}

Note that this same thing applies for the dependencies array passed to useEffect, useLayoutEffect, useCallback, and useMemo.

React.memo (and friends)

Warning, you're about to see some more contrived code. Please be advised to not nit-pick this either but focus on the concepts, thanks.

Check this out:

function CountButton({ onClick, count }) {
	return <button onClick={onClick}>{count}</button>
}

function DualCounter() {
	const [count1, setCount1] = React.useState(0)
	const increment1 = () => setCount1((c) => c + 1)

	const [count2, setCount2] = React.useState(0)
	const increment2 = () => setCount2((c) => c + 1)

	return (
		<>
			<CountButton count={count1} onClick={increment1} />
			<CountButton count={count2} onClick={increment2} />
		</>
	)
}

Every time you click on either of those buttons, the DualCounter's state changes and therefore re-renders which in turn will re-render both of the CountButtons. However, the only one that actually needs to re-render is the one that was clicked right? So if you click the first one, the second one gets re-rendered, but nothing changes. We call this an "unnecessary re-render."

MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this. In fact, the need to optimize stuff with what I'm about to show you is so rare that I've literally never needed to do it in the 3 years I worked on PayPal products and the even longer time that I've been working with React.

However, there are situations when rendering can take a substantial amount of time (think highly interactive Graphs/Charts/Animations/etc.). Thanks to the pragmatistic nature of React, there's an escape hatch:

const CountButton = React.memo(function CountButton({ onClick, count }) {
	return <button onClick={onClick}>{count}</button>
})

Now React will only re-render CountButton when its props change! Woo! But we're not done yet. Remember that whole referential equality thing? In the DualCounter component, we're defining the increment1 and increment2 functions within the component functions which means every time DualCounter is re-rendered, those functions will be new and therefore React will re-render both of the CountButtons anyway.

So this is the other situation where useCallback and useMemo can be of help:

const CountButton = React.memo(function CountButton({ onClick, count }) {
	return <button onClick={onClick}>{count}</button>
})

function DualCounter() {
	const [count1, setCount1] = React.useState(0)
	const increment1 = React.useCallback(() => setCount1((c) => c + 1), [])

	const [count2, setCount2] = React.useState(0)
	const increment2 = React.useCallback(() => setCount2((c) => c + 1), [])

	return (
		<>
			<CountButton count={count1} onClick={increment1} />
			<CountButton count={count2} onClick={increment2} />
		</>
	)
}

Now we can avoid the so-called "unnecessary re-renders" of CountButton.

I would like to re-iterate that I strongly advise against using React.memo (or it's friends PureComponent and shouldComponentUpdate) without measuring because those optimizations come with a cost and you need to make sure you know what that cost will be as well as the associated benefit so you can determine whether it will actually be helpful (and not harmful) in your case, and as we observe above it can be tricky to get right all the time so you may not be reaping any benefits at all anyway.

Computationally expensive calculations

This is the other reason that useMemo is a built-in hook for React (note that this one does not apply to useCallback). The benefit to useMemo is that you can take a value like:

const a = { b: props.b }

And get it lazily:

const a = React.useMemo(() => ({ b: props.b }), [props.b])

This isn't really useful for that case above, but imagine that you've got a function that synchronously calculates a value which is computationally expensive to calculate (I mean how many apps actually need to calculate prime numbers like this ever, but that's an example):

function RenderPrimes({ iterations, multiplier }) {
	const primes = calculatePrimes(iterations, multiplier)
	return <div>Primes! {primes}</div>
}

That could be pretty slow given the right iterations or multiplier and there's not too much you can do about that specifically. You can't automagically make your user's hardware faster. But you can make it so you never have to calculate the same value twice in a row, which is what useMemo will do for you:

function RenderPrimes({ iterations, multiplier }) {
	const primes = React.useMemo(
		() => calculatePrimes(iterations, multiplier),
		[iterations, multiplier],
	)
	return <div>Primes! {primes}</div>
}

The reason this works is even though you're defining the function to calculate the primes on every render (which is VERY fast), React is only calling that function when the value is needed. On top of that React also stores previous values given the inputs and will return the previous value given the same previous inputs. That's memoization at work.

Conclusion

I'd just like to wrap this up by saying that every abstraction (and performance optimization) comes at a cost. Apply the AHA Programming principle and wait until the abstraction/optimization is screaming at you before applying it and you'll save yourself from incurring the costs without reaping the benefit.

Specifically the cost for useCallback and useMemo are that you make the code more complex for your co-workers, you could make a mistake in the dependencies array, and you're potentially making performance worse by invoking the built-in hooks and preventing dependencies and memoized values from being garbage collected. Those are all fine costs to incur if you get the performance benefits necessary, but it's best to measure first.

Related reading:

P.S. If you're among the few who worry about the move to hooks and that it forces us to define functions within our function components where we used to define functions as methods on our classes, I would invite you to consider the fact that we've been defining methods in the render phase of our components since day one... For example:

class FavoriteNumbers extends React.Component {
	render() {
		return (
			<ul>
				{this.props.favoriteNumbers.map((number) => (
					// TADA! This is a function defined in the render method!
					// Hooks did not introduce this concept.
					// We've been doing this all along.
					<li key={number}>{number}</li>
				))}
			</ul>
		)
	}
}