Table of Contents
1. Code Splitting
Don't download code until that user needs it.
1. Dynamic Module Import import
For example, in the registerModule
file, the module file is only downloaded when the user needs to register.
registerModule.js
const register = (formData) => {
console.log(formData)
}
export { register }
App.js
import('./modules/registerModule')
.then((module) => module.register(formData))
.catch((err) => console.log(err))
2. React.Lazy
Server-side rendering is not supported
Components that require lazy loading must be wrapped with <Suspense>
. The fallback
prop, which defines the element to render while the component is loading, is required (it can be null
, but must be explicitly specified).
In the demo below, if the user has not visited the "This Season's Ranking" and "Now Streaming" pages, these files will not be downloaded. The corresponding chunk is only downloaded when the user navigates to these pages.
const Home = lazy(() => import('./pages/Home')) // import Home from './pages/Home'
const Air = lazy(() => import('./pages/Air'))
const Rank = lazy(() => import('./pages/Rank'))
const App = () => {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/air">On Air</Link>
<Link to="/rank">Rank</Link>
</nav>
<Suspense fallback={<Loading />}>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/air" component={Air} />
<Route exact path="/rank" component={Rank} />
</Switch>
</Suspense>
</Router>
)
}
3. Memorization & Rendering
1. React.memo()
Component Memorization: A component re-renders if its props change; otherwise, the previous render is used.
The following demo uses the <SnowBackground />
component to render 1,000 snowflake particles as a background.
Each time the <input />
field updates, the <App />
component re-renders. Since <SnowBackground />
is a child component, it also re-renders.
With 1,000 particles, rendering can cause slight lag. More importantly, if <SnowBackground />
props haven't changed, there is no need to re-render the component. As the number of particles increases, frequent re-renders can negatively impact user experience.
const SnowBackground = React.memo(() => {
console.log('Render Snow Particles')
return <Snow backgroundColor="#000" particles={1000} />
})
const App = () => {
console.log('Render App')
const handleChange = (e) => setSearch(e.target.value)
return (
<>
<input type="text" value={search} onChange={handleChange} />
<SnowBackground />
</>
)
}
export default App
2. useMemo()
Value Memorization: A function is re-executed only if its dependencies change.
1. Recomputing Expensive Functions Based on Dependency Changes
calculate()
is a computationally expensive function, but toggling the <button>
to switch themes also causes the <App />
component to re-render, triggering calculate()
again—even though the toggle is unrelated to this function.
To optimize this, useMemo
stores the return value of calculate()
. If number
remains unchanged, the memoized value is used; otherwise, calculate()
is re-executed.
2. Referential Equality for Storing Reference Values
Similarly, themeStyle
is an object. Every <App />
re-render creates a new themeStyle
object. Since objects in JavaScript are reference values, React's shallow comparison detects it as a new object, causing unnecessary re-renders whenever the input changes.
const calculate = (n) => {
for (let i = 0; i < 1000000000; i++) {}
console.log('计算')
return n + 2
}
const App = () => {
const [number, setNumber] = useState(0)
const [dark, setDark] = useState(false)
const result = useMemo(() => {
return calculate(number)
}, [number])
const themeStyle = useMemo(() => {
return {
backgroundColor: dark ? 'black' : 'white',
color: dark ? 'white' : 'black'
}
}, [dark])
return (
<>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
/>
<button onClick={() => setDark((prevState) => !prevState)}>
Change Theme
</button>
<div style={themeStyle}>{result}</div>
</>
)
}
export default App
3. useCallback()
Function Memorization: Typically used with
React.memo()
to prevent unnecessary function re-renders.
1. Scenario: useCallback()
+ React.memo()
to Prevent Component Re-renders
In the following code, the child component <Child />
uses memo
to prevent unnecessary re-renders. It also receives an increment
function from the parent component to update the counter.
However, memo
does not work as expected here. Ideally, clicking the button should only re-render the parent, while <Child />
—wrapped in memo
—should compare props and decide whether to update. Since only count
changes, and increment()
remains the same, the child component should not re-render.
The issue arises because, in JavaScript, functions are objects, and due to Referential Equality, two identical objects are still considered different references. This causes memo
to detect a new function reference and re-render <Child />
unnecessarily.
Previously, we used useMemo
to resolve Referential Equality issues, but:
useMemo()
returns a value, useful for caching expensive computations.useCallback()
returns a function, which can accept parameters and be used for event handling.
Thus, by combining useCallback()
with memo
, we can prevent both redundant function re-creations and unnecessary child component re-renders.
import React, { useCallback, useState } from 'react'
const Child = React.memo((props) => {
const { increment } = props
return (
<>
<button onClick={() => increment(5)}>Increment</button>
</>
)
})
const App = () => {
const [count, setCount] = useState(0)
const increment = useCallback((step) => {
setCount((prevState) => prevState + step)
}, [])
return (
<>
<h1>Count: {count}</h1>
<Child increment={increment} />
</>
)
}
export default App