How Fre's `memo()` HOC Optimizes Component Rendering: A Deep Dive into the Source Code
Fre's memo() higher-order component prevents unnecessary re-renders by marking functional components with a memo flag and using a shallow comparison or custom equality function to skip rendering when props remain unchanged.
Fre is a lightweight React alternative that implements a fiber-based reconciler. Understanding how its memo() higher-order component (HOC) optimizes rendering requires examining the interplay between the component factory in src/h.ts and the reconciliation logic in src/reconcile.ts.
What Is Fre's memo() HOC?
The memo() function is a tiny higher-order component factory located in src/h.ts (lines 51-55). It takes a functional component and an optional comparison function, then attaches metadata to the function object that the reconciler checks during the render phase.
Unlike React's implementation, Fre's version is intentionally minimal, adding only two properties to the component function:
fn.memo = true— signals that this component may be skippedfn.shouldUpdate— optionally stores a custom props comparison function
How the Reconciler Detects Memoized Components
The capture() Routine
During each reconciliation cycle, Fre's capture function in src/reconcile.ts traverses the fiber tree. When it encounters a fiber representing a functional component, it checks whether that component has been marked with memo.
The critical logic appears at lines 90-93:
if (isMemo(fiber)) {
fiber.memo = false
return sibling(fiber)
}
If isMemo(fiber) returns true, the reconciler immediately returns the next sibling fiber instead of recursing into the component's children. This means the component's render function is not invoked, its child fibers are not diffed, and no DOM work is scheduled for that subtree.
The isMemo() Logic
The isMemo function, defined at lines 104-115 in src/reconcile.ts, performs three validation steps before determining whether to skip rendering:
- Component type check — verifies the current and previous fibers refer to the same component type
- Props existence check — confirms a previous props object exists for comparison
- Equality comparison — executes either the user-provided
shouldUpdatefunction or Fre's default shallow comparison
The default shallow comparison lives at lines 41-47 in src/reconcile.ts:
const shouldUpdate = (a, b) => {
for (let i in a) if (a[i] !== b[i]) return true
for (let i in b) if (b[i] !== a[i]) return true
return false
}
If shouldUpdate returns false (indicating props are equal), isMemo returns true, triggering the skip logic in capture.
Practical Implementation Examples
Using memo() with Custom Comparison
The following example from the Fre demo repository demonstrates using a custom equality function to control when re-renders occur:
import { h, render, useState, memo } from '../../src'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<Counter count={count} />
<button onClick={() => setCount(count + 1)}>Inc</button>
</div>
)
}
// Re-render only when `count` becomes even
const Counter = memo(
({ count }: { count: number }) => <div>{count}</div>,
(prev, next) => next.count % 2 === 0
)
render(<App />, document.body)
In this implementation, the Counter component skips rendering on every odd update because the custom shouldUpdate returns false for those cases.
Verifying Optimization in Tests
The Fre test suite confirms that memoized components avoid unnecessary render calls:
// test/memo.tsx
import { h, memo } from '../src/index'
export const memor = async t => {
const Component = memo(() => {
console.log('rendered')
return <div />
})
// First render
t.is(await render(<Component />, document.body), undefined)
// Subsequent render with same props – should not log again
}
This test verifies that when props remain unchanged, the component function is not invoked again, confirming the optimization is working.
Summary
- Fre's
memo()marks functional components by settingfn.memo = trueand optionally attaching a customshouldUpdatecomparison function insrc/h.ts. - The reconciler checks for memoization during the
capturephase insrc/reconcile.ts, callingisMemo()to determine if props have changed. - Shallow comparison is the default equality check, though users can provide custom logic to control when updates occur.
- Subtree skipping happens immediately when
isMemoreturns true, preventing the render function from executing and avoiding all child reconciliation and DOM operations for that component.
Frequently Asked Questions
What is the difference between Fre memo() and React memo()?
Both implementations prevent re-renders when props remain unchanged, but Fre's version is significantly more lightweight. While React's implementation involves complex internal state management and additional development-mode checks, Fre simply attaches two properties (memo and shouldUpdate) to the function object and relies on the reconciler's isMemo check during the capture phase to skip rendering.
How does Fre's default shouldUpdate function work?
The default comparison function performs a shallow equality check on props objects. Located at lines 41-47 in src/reconcile.ts, it iterates through all keys in both the previous and current props objects, returning true (indicating an update is needed) if any values differ. If all values match, it returns false, triggering the memoization skip.
Can I use memo() with Fre hooks like useState or useEffect?
Yes, memo() works seamlessly with Fre's hook system. The HOC only controls whether the component function re-executes based on props changes; internal state updates from useState or effects from useEffect still function normally when the component does render. However, if a parent re-renders but the memoized component's props haven't changed, the component won't execute at all, meaning its internal hooks won't run during that specific cycle.
Where does Fre check if a component should update during reconciliation?
The check occurs in the capture function within src/reconcile.ts at lines 90-93. During the reconciliation traversal, when the reconciler encounters a functional component fiber, it calls isMemo(fiber) (defined at lines 104-115). This function verifies the component is marked with memo, compares props using either the custom or default shouldUpdate function, and returns a boolean that determines whether the reconciler should skip the entire subtree.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →