How React Functional Components Trigger Updates

  • 895Words
  • 4Minutes
  • 21 Sep, 2024

Since the introduction of Hooks, React functional components have become central to modern React application development. Compared to class components, functional components are not only more concise but also more powerful. However, understanding the update mechanism behind them, especially from the perspective of source code, can help us optimize performance and avoid unnecessary re-renders. This article will dive deep into the update mechanism of functional components, starting from React’s underlying source code.

1. Trigger Conditions for Functional Component Updates

Functional component updates are primarily triggered by the following:

  • State Changes: Updating the state via the useState setter method.
  • Prop Changes: React re-renders a component when the props passed from the parent component change.
  • Context Changes: React re-renders components when the context data obtained via useContext changes.

From a source code perspective, React stores the component’s state, props, and other data in an internal Fiber tree. When these data change, React enters the reconciliation phase to determine if a re-render is necessary.

2. The Core Role of the Fiber Tree

After the introduction of the Fiber architecture in React 16, updates to all components (including both class and functional components) are represented as Fiber nodes. The core role of the Fiber tree is to break the update process into smaller tasks, allowing React to remain responsive to the user interface even during large rendering tasks.

Each functional component has a corresponding Fiber object, which stores the component’s state, props, and update queue. When calling setState or the useState setter, React adds this update to the Fiber node’s update queue, waiting for the scheduler to execute.

3. The React Update Scheduling Process

When a functional component’s state or props change, React enters the update scheduling process. The core flow includes:

3.1 The Update Mechanism of setState and useState

When the setter method of useState (e.g., setState) is called, it actually creates an update object, which contains the new state value and a reference to the component that needs updating. This update object is added to the current Fiber node’s update queue, awaiting React’s scheduling.

1
// Simplified implementation of useState
2
function useState(initialState) {
3
const hook = getHook(); // Retrieve the hook state from the current Fiber node
4
if (!hook) {
5
// Initialize hook state
6
return mountState(initialState);
7
}
8
return updateState(hook);
9
}

With each update, React processes the update logic for each Fiber node in the Fiber tree. It checks the update queue via the beginWork function, recalculates state values, and triggers component re-renders.

3.2 Storage and Reuse of Hooks

During each execution of a functional component, React uses an internal linked list to store and reuse Hooks. Each call to useState or useEffect creates or reuses a hook node on this list to store state values or side effects.

1
function renderWithHooks(currentFiber, nextChildren) {
2
currentlyRenderingFiber = currentFiber;
3
currentHook = currentFiber.memoizedState; // Retrieve the previously stored hook list
4
nextChildren = Component(props); // Re-execute the functional component
5
return nextChildren;
6
}

Each time a functional component re-renders, React re-executes the function body, but because Hooks are stored in order, it retrieves the corresponding states and effects to maintain consistency.

4. The Reconciliation Algorithm and Virtual DOM Mechanism

4.1 The Diff Algorithm of the Virtual DOM

React’s update mechanism relies on the Reconciliation algorithm to decide which parts need to be updated. The core steps of reconciliation include:

  1. Creating a new Virtual DOM: With each component update, React generates a new virtual DOM tree based on the new state and props.
  2. Diffing Phase: React compares the new and old virtual DOM trees, using the Diff algorithm to identify the parts that need modification.
  3. Updating the Real DOM: React minimizes differences and batch updates the actual DOM.

The Fiber tree allows React to pause and resume between work units (update steps), optimizing the execution of long tasks and improving application responsiveness.

4.2 Update Priorities and Scheduling

React uses a priority queue to control the scheduling order of updates. Each time state or props change, React assigns a priority to the update task, determining when to execute it based on the urgency.

  • High-priority updates (like user input events) are executed immediately.
  • Low-priority updates (such as animations or background data loading) are deferred to maintain UI smoothness.

5. Performance Optimization for Functional Components: useMemo and useCallback

Since functional components re-execute the entire function with each update, using useMemo and useCallback to cache expensive computations or function references becomes crucial.

  • useMemo: Caches computation results to avoid re-computation on every render.
  • useCallback: Caches functions to prevent creating new function references on each render.
1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
2
3
const memoizedCallback = useCallback(() => {
4
handleClick(a);
5
}, [a]);

These Hooks help by caching unchanged values or functions, reducing unnecessary recalculations and enhancing application performance.

6. Simulating Lifecycle in Functional Components

While functional components don’t have lifecycle methods like class components, useEffect can simulate similar lifecycle behavior:

  • componentDidMount and componentDidUpdate: Simulated using useEffect, which runs on component mount or update.
  • componentWillUnmount: Simulated by returning a cleanup function within useEffect.
1
useEffect(() => {
2
// Runs on mount or update
3
return () => {
4
// Runs on unmount
5
};
6
}, [dependencies]); // The dependency array controls when it runs

Conclusion

The update process of React functional components starts with state changes, and through the Fiber tree and the Reconciliation algorithm, the virtual DOM is gradually updated and reflected in the actual DOM. The introduction of Hooks makes functional components more concise, but also requires developers to focus more on performance optimization, such as wisely using useMemo, useCallback, and React.memo.

Understanding these underlying mechanisms helps us write more efficient React applications and avoid unnecessary performance bottlenecks.