質問するログイン新規登録

Q&A

1回答

2075閲覧

[React] useEffectとイベントハンドラの実行順序について

momo3159

総合スコア1

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2022/06/18 04:18

0

0

質問事項

以下の2つの関数の間に,特定の実行順序(例: useEffectに渡した関数が先)はありますでしょうか?
もし存在しない場合,順序を制御する方法はありますでしょうか?

  • useEffectに渡した関数
  • イベントハンドラ

質問の背景

以下のコードは,refで参照するコンポーネントの外部をクリックするとコンポーネントを閉じる機能を提供するhooksになります.

useEffect内で,windowオブジェクトにclickイベントのイベントハンドラを登録しています.

javascript

1import { useState, useEffect, useRef } from "react" 2 3export const useCloserWhenClickOutside = () => { 4 const ref = useRef(null); 5 const [state, setState] = useState(false); 6 7 useEffect(() => { 8 console.log('in useEffect') 9 const clickOutsideModuleHandler = (e) => { 10 console.log('event handler'); 11 const moduleArea = ref.current; 12 if (!state || moduleArea?.contains(e.target)) return; 13 setState(false); 14 }; 15 16 window.addEventListener("click", clickOutsideModuleHandler); 17 return () => { 18 console.log('unmount') 19 window.removeEventListener("click", clickOutsideModuleHandler); 20 }; 21 }, [state, ref]); 22 23 return [state, setState, ref] 24}

以下のコードはaタグをクリックするとchildrenを表示し,children以外の部分をクリックするとchildrenを閉じるコンポーネントになっています.

javascript

1import { useEffect, useState } from "react"; 2 3export const ButtonWithPopup = ({ value, children }) => { 4 5 const [state, setState, ref] = useCloserWhenClickOutside(); 6 const onClick = (e) => { 7 console.log('open'); 8 setState((prev) => !prev); 9 }; 10 11 return ( 12 <div> 13 {" "} 14 <a onClick={onClick}> 15 {value} 16 </a> 17 {state && <div ref={ref}>{children}</div>} 18 </div> 19 ); 20};

この時

  • react-datepickerというライブラリの DatePickerを子要素とした場合
  • そうでない場合
    とで,custom hooks内でwindowに登録したclickイベントのハンドラと,useEffectに渡した関数の実行順序が異なる結果となりました.

javascript

1import DatePicker from "react-datepicker"; 2 3export const App = () => { 4 return ( 5 <div> 6 <ButtonWithPopup value="タイトル1"> 7 <DatePicker /> 8 </PopupButton> 9 <PopupButton title="メンバー"> 10 <div>hoge</div> 11 </PopupButton> 12 </div> 13 ) 14}

実行結果

aタグをクリックし子要素を表示しようとした場合,コンソールにはどのように表示されているかの結果となります.

子要素がDatePickerの場合

1. open(aタグをクリックしたため) 2. unmount (stateがfalseからtrueに変わったため) 3. in useEffect 4. event handler(custom hooks内で登録したイベントハンドラ)

子要素がDatePickerでない場合

1. open 2. event handler(先に実行される) 3. unmount 4. in useEffect

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

momo3159

2022/06/18 08:18

clickOutsideModuleHandler内で, `console.log(ref.current, e.target, state, moduleArea?.contains(e.target));` を呼び出して値を確認してみました.結果としては`moduleArea?.contains(e.target)`は正しく動いていました. また,分かったこととしては以下の通りです. 子要素にDatePickerを使わない場合は, `refで参照しているDOM,クリックしたDOM,false, false` となりました.一方で,DatePickerを使った場合は, `refで参照しているDOM,クリックしたDOM,true, false`となりました. 従って,以下の手順になっているためクリックしても表示されないという形でした. 1. aタグをクリックして子要素を表示しようとする. 2. イベントハンドラの実行よりも先にクリーンアップ関数が呼ばれ,**stateがfalseの時のイベントハンドラが消去される** 3. useEffectの再実行が行われ,**stateがtrueの状態でイベントハンドラが登録される** 4. aタグをクリックした時のイベントに反応して,**クリック前に登録されていたイベントハンドラ(2で消去されたもの)**ではなく3の時のイベントハンドラが実行される
guest

回答1

0

直接関係するかどうかはわからないですが、moduleArea?.contains(e.target)が、コンポーネントによっては正しく反応できないことがあります。

Reactにはポータルという仕組みがあって、React上は子要素であるけど、DOMとしては全く別な場所に表示する、ということが可能です。containsというDOMの仕組みでは、Portalで子要素になっているかというReactサイドの基準での判定はできません。

投稿2022/06/18 06:49

maisumakun

総合スコア147023

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.29%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問