新しい React コンセプト: エフェクト イベント

実装原則の違いにより、各フレームワークにはいくつかの固有の概念があります。例えば:

  • Vue3応答性の高い実装原則により、refreactiveなどの概念が派生します。

  • Svelte独自のコンパイラに大きく依存しているため、コンパイル関連の概念 (タグの革新的な使用などlabel)を派生します。

には、誤用されやすい「非常に簡単」Reactがあります -  、今日紹介したいのは、から派生した概念に属します。APIuseEffectEffect EventuseEffect

悪用効果

この記事では、次の 3 つの概念について説明します。

  • Event(イベント)

  • Effect(副作用)

  • Effect Event(副作用事象)

EventまずはとチャットしましょうEffectuseEffectまた、2 つの概念は混同されやすいため、誤用されやすくなります。

イベントのコンセプト

以下のコードでは、クリックによってdivクリック イベント (onClickクリック コールバック) がトリガーされます。その中には以下のものonClickに属しますEvent:

function App() {
  const [num , update] = useState(0);
  
  function onClick() {
    update(num + 1);
  }
  
  return (
    <div onClick={onClick}>{num}</div>
  )
}

Eventの特徴は、「状態変化ではなく、何らかの動作によってトリガーされるロジック」です。

たとえば、上記のコードでは、ロジックは「クリック イベント」の動作によってトリガーされonClick状態の変更はトリガーされませんnumonClick

エフェクトの概念

EffectそれどころかEvent、彼は「何らかの行動によって引き起こされる論理ではなく、何らかの状態変化によって引き起こされる」のです。

たとえば、次のコードでは、title変更されると、次の値document.titleに更新されます。title

function Title({title}) {
  useEffect(() => {
    document.title = title;
  }, [title])
  
  // ...
}

上記のコードのロジックuseEffectは に属しておりEffecttitle変更によってトリガーされます。さらにuseEffect、次の 2 つもHook属しますEffect

  • useLayoutEffect(あまり使われない)

  • useInsertionEffect(非常にまれに使用されます)

なぜ悪用されやすいのでしょうか?

ここで問題は、EventEffectの概念がまったく異なるのに、なぜ誤用されるのかということです。

たとえば、プロジェクトの最初のバージョンでは、useEffectデータを初期化するロジックが次のとおりでした。

function App() {
  const [data, updateData] = useState(null);

  useEffect(() => {
    fetchData().then(data => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    })
  }, []);

  // ...
}

プロジェクトが進行するにつれて、フォームの送信後にデータを更新するという別の要件が発生します。

前のロジックを再利用するには、新しいoptions状態を追加し (フォーム データを保存)、それをuseEffect依存関係として使用します。

function App() {
  const [data, updateData] = useState(null);
  const [options, updateOptions] = useState(null);

  useEffect(() => {
    fetchData(options).then(data => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    })
  }, [options]);
  
  
  function onSubmit(opt) {
    updateOptions(opt);
  }

  // ...
}

これで、フォームを送信した後 (onSubmitコールバックをトリガーした後)、以前のデータ初期化ロジックを再利用できるようになります。

とても便利なので、これがuseEffectベストな使い方だと考える学生も多いです。しかし実際には、これは典型的な「useEffect の誤用」です。

注意深く分析した結果、 「フォームの送信」は明らかにEvent(送信された動作によってトリガーされる) ものであり、Eventロジックはイベント コールバックではなくイベント コールバックに記述する必要があることがわかりますuseEffect正しい書き方は次のようになります。

function App() {
  const [data, updateData] = useState(null);

  useEffect(() => {
    fetchData().then(data => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    })
  }, []);
  
  
  function onSubmit(opt) {
    fetchData(opt).then(data => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    })
  }

  // ...
}

上記の例のロジックは比較的単純であり、2 つの記述方法にほとんど違いはありません。ただし、実際のプロジェクトでは、プロジェクトが反復を続けると、次のコードが表示される場合があります。

useEffect(() => {
  fetchData(options).then(data => {
    // ...一些业务逻辑
    // 更新data
    updateData(data);
  })
}, [options, xxx, yyy, zzz]);

fetchDataその時点では、次の理由から、メソッドがどのような状況で実行されるかを知るのは困難です。

  1. useEffect依存関係が多すぎる

  2. 依存関係の変更のタイミングを完全に把握するのが難しい

したがって、 では、と をReact明確に区別する必要があります。つまり、「ロジックの一部が動作によってトリガーされるのか、状態変化によってトリガーされるのか」を明確に区別する必要があります。EventEffect

useEffect の依存関係の問題

Eventと を明確に区別できるようになったのでEffect、プロジェクトの作成に問題がないことは当然です。しかし、 「効果の仕組みの問題」により、新たな問題にも直面しています。

roomId変更されたときに新しいチャット ルームに再接続する必要があるチャット ルーム コードがあるとします。このシナリオでは、チャット ルームの切断/再接続はroomId状態の変化に依存します。これは明らかにEffect次のコードに属します。

function ChatRoom({roomId}) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    return () => {
      connection.disconnect()
    };
  }, [roomId]);
  
  // ...
}

次に、新しい要件を受け取ります。接続が成功すると、「グローバル リマインダー」がポップアップ表示されます。

 「グローバルリマインダー」がダークモードであるかどうかがtheme props影響します。useEffect変更されたコードは次のとおりです。

useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();
  
  connection.on('connected', () => {
    showNotification('连接成功!', theme);
  });
  
  return () => connection.disconnect();
}, [roomId, theme]);

しかし、このコードには重大な問題があります。変更を引き起こすものはすべて、themeチャット ルームの切断/再接続を引き起こします。結局のところ、依存関係themeも同様ですuseEffect

この例では、Effect依存していますがtheme、変更Effectによってthemeトリガーされません (roomId変更によってトリガーされます)。

このシナリオに対処するために、React新しいコンセプト - 彼は、上記の例のように、「Effect で実行されるが、Effect は状態のロジックに依存しない」ものを指します。 Effect Event

() => {
  showNotification('连接成功!', theme);
}

useEffectEvent(これは実験的なHook) 定義を使用できますEffect Event

function ChatRoom({roomId, theme}) {
  const onConnected = useEffectEvent(() => {
    showNotification('连接成功!', theme);
  });

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    connection.on('connected', () => {
      onConnected();
    });
    
    return () => {
      connection.disconnect()
    };
  }, [roomId]);
  
  // ...
}

上記のコードでは(he is a )themeに移動していますが、最新値の最新値が使用されていますが、依存関係として使用する必要はありません。onConnectedEffect EventuseEffecttheme

useEffectEvent ソースコード分析

useEffectEvent実装は複雑ではなく、コアコードは次のとおりです。

function updateEvent(callback) {
  const hook = updateWorkInProgressHook();
  // 保存callback的引用
  const ref = hook.memoizedState;
  // 在useEffect执行前更新callback的引用
  useEffectEventImpl({ref, nextImpl: callback});
  
  return function eventFn() {
    if (isInvalidExecutionContextForEventFunction()) {
      throw new Error(
        "A function wrapped in useEffectEvent can't be called during rendering.",
      );
    }
    return ref.impl.apply(undefined, arguments);
  };
}

このうち、ref変数には「コールバックの参照」が保持されます。上の例の場合:

const onConnected = useEffectEvent(() => {
  showNotification('连接成功!', theme);
});

ref次のような関数への参照を保存します。

() => {
  showNotification('连接成功!', theme);
}

useEffectEventImplこのメソッドはパラメータとしてrefと を受け入れcallback的最新值useEffect実行前にrefに保存されたcallback引用値を更新しますcallback的最新值

したがって、 でuseEffect実行すると、に保存されている次のクロージャの最新の値がonConnected取得されます。ref

() => {
  showNotification('连接成功!', theme);
}

当然、クロージャの最新値も表示されますtheme

useEffectEvent と useEvent

useEffectEvent戻り値を注意深く観察してください。戻り値には 2 つの制限があります。

return function eventFn() {
    if (isInvalidExecutionContextForEventFunction()) {
      throw new Error(
        "A function wrapped in useEffectEvent can't be called during rendering.",
      );
    }
    return ref.impl.apply(undefined, arguments);
};

最初の制限はより明らかです。次のコード行のuseEffectEvent戻り値の制限はuseEffect、コールバック内でのみ実行できます (そうでない場合はエラーが報告されます)。

if (isInvalidExecutionContextForEventFunction()) {
  // ...    
}

もう 1 つの制限はさらに微妙です。戻り値はまったく新しい参照です。

return function eventFn() {
  // ...
};

「新鮮な参照」が制限である理由がよくわからない場合は、useCallback戻り値を返すことを検討してください。

return useCallback((...args) => {
    const fn = ref.impl;
    return fn(...args);
}, []);

これにより、useEffectEvent関数の戻り値は不変の参照となり、「useEffect コールバック内でのみ実行可能」という制約がなくなるuseEffectEventと拡張版となりますuseCallback

たとえば、上記の制限に違反している場合は、次のコードが該当します。

function App({a, b}) {
  const [c, updateC] = useState(0);
  const fn = useCallback(() => a + b + c, [a, b, c])
  
  // ...
}

代わりに、コードはuseEffectEvent次のようになりますuseCallback

const fn = useEffectEvent(() => a + b + c)

それに比べてuseCallback、彼には 2 つの利点があります。

  1. 依存関係を明示的に宣言せずに

  2. 依存関係が変化してもfn参照は変化しません。これは単にパフォーマンスの最適化にとって最良の選択です。

では、ReactなぜuseEffectEvent制限するのでしょうか?

実際、useEffectEventの前身はuseEvent上記の実装に従っていますが、次の理由によります。

  1. useEventポジショニングは のはずですEffect Eventが、実際の用途はもっと広く(置き換え可能useCallback)、彼のポジショニングと一致しません

  2. 現時点では(と同等のコードを生成できる公式コンパイラ)は考慮されていませんReact ForgetuseMemoこれを追加すると実装の難易度が上がりますuseCallbackuseEventhookReact Forget

したがって、useEvent正式には規格には入りませんでした。代わりに、より制限的なuseEffectEventものが React ドキュメント [1] に組み込まれました。

要約する

今日は次の 3 つの概念を学びました。

  • Event: 状態の変化ではなく、何らかの動作によってトリガーされるロジック

  • Effect: 何らかの動作ではなく、何らかの状態変化によってトリガーされるロジック

  • Effect Event:Effect内部でが、Effect状態のロジックには依存しません

その具体的な実装はEffect Eventにあります前任者と比較して、彼には 2 つの追加の制限があります。ReactuseEffectEventuseEvent

  1. Effect内でのみ実行可能

  2. 常に異なる参照を返します

私の考えでは、Effect Eventの出現はひとえにHooks実装機構の複雑さ(依存関係を明示的に指定する必要がある)による精神的負担によるものだと考えています。

結局のところ、同じHooksコンセプトに従っている人にはVue Composition APIこの問題はありません。

参考文献

[1]

React ドキュメント: https://react.dev/learn/separating-events-from-Effects

おすすめ

転載: blog.csdn.net/huihui_999/article/details/131720253