reactjs / reactjs/react.dev

RFC: [Feature Request] Inject structural change metadata (`ChangePayload`) as an argument into the `useEffect` callback

未關閉
#8,467 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

主要語言
JavaScript
星號
11.8k
分支
7.9k
平均合併
1 天 11 小時
30 天內合併 PR
11

描述

[Feature Request] Inject structural change metadata (ChangePayload) as an argument into the useEffect callback
Summary

I propose extending the signature of the useEffect callback function to optionally accept a single argument: a ChangePayload object. This metadata object provides explicit access to current values, previous values, and an array of indices representing exactly which dependencies triggered the current effect execution pass.

Motivation

In legacy class components, developers had granular, imperative control over side-effect execution branching via the componentDidUpdate(prevProps, prevState) lifecycle method. This allowed developers to easily evaluate the precise delta between frames (e.g., if (this.props.id !== prevProps.id)).

With functional components and useEffect, this diagnostic visibility was fully abstracted away. If an effect relies on multiple cohesive dependencies, the callback executes blindly without knowing the specific trigger vector.

The Analogy to useReducer:
Much like a reducer function receives a discrete action object with a type/payload to determine how state should mutate, a multi-dependency useEffect callback should be able to receive a change payload to determine how side-effect execution should branch. Currently, developers are forced to choose between splitting code across multiple disconnected effects (fragmenting logic) or managing complex useRef snapshots to diff properties manually.

Detailed Design

We propose modifying the execution lifecycle so that the reconciler injects a structured payload directly into the effect callback:

interface ChangePayload<T extends ReadonlyArray<unknown>> {
  currentDepValues: T;
  previousDepValues: T;
  updatedDepIndex: number[]; // Array of dependency indices that failed equality checks
}

Code Implementation Example:

Instead of managing tracking refs, developers can treat dependency mutations as distinct event triggers within a single, cohesive side-effect block:

import { useEffect, useState } from 'react';

export function CoreDataEngine() {
  const [authHeader, setAuthHeader] = useState('Bearer xyz');
  const [streamPayload, setStreamPayload] = useState({ coordinates: [] });
  const [circuitBreaker, setCircuitBreaker] = useState(false);

  useEffect((changePayload) => {
    // If changePayload is undefined, it is the initial mount pass
    if (!changePayload) return;

    const { updatedDepIndex, currentDepValues, previousDepValues } = changePayload;

    // Direct branching reminiscent of action-handling in useReducer
    if (updatedDepIndex.includes(0)) {
      console.log(`Auth token updated from ${previousDepValues[0]} to ${currentDepValues[0]}. Re-signing sockets.`);
      // execTokenRotation();
    }

    if (updatedDepIndex.includes(1)) {
      console.log('Telemetry coordinate payload arrived. Re-rendering stream vectors.');
      // execVectorRender();
    }

    if (updatedDepIndex.includes(2)) {
      console.log('System critical circuit breaker tripped. Terminating pipelines.');
      // execEmergencyTeardown();
    }
  }, [authHeader, streamPayload, circuitBreaker]); // Index 0, 1, 2
}

Key Advantages
  1. Unifies Contextual Logic: Eliminates the anti-pattern of splitting highly cohesive dependencies into 3 or 4 separate useEffect declarations simply because their runtime execution logic differs slightly based on what changed.
  2. Deterministic Control Over Batching: When React batches multiple state setters together, updatedDepIndex provides an array of all indices that mutated in that specific batch, giving developers a clear execution map of the transaction.
  3. Ergonomic Parity with class lifecycles: Brings back the missing granular diffing power of componentDidUpdate without introducing stateful lifecycle clutter to functional components.
Alternatives Considered

The primary alternative is user-land implementation via custom hooks wrapping useRef. However, this requires continuous overhead, duplicate shallow/deep equality evaluation outside the Fiber loop, and forces every enterprise engineering team to repeatedly implement custom snapshot logic to solve a standard primitive constraint.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

該 issue 沒有列出任何 repository 檔案、測試或進入點。先檢視提議的 useEffect 簽章和 ChangePayload 設計,然後判定這項工作是屬於 react.dev 還是 React reconciler。一個可用的起點需要一份已達成共識的設計,以及已確定的實作和測試位置。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
javascript, react
領域
frontend
Issue 類型
功能
難度
5/5
預估耗時
一週以上
活躍度
冷清
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。