Open13

[3章ー1]モダンWebフロントエンド

Shinya OkayamaShinya Okayama

こちらの続き。
https://zenn.dev/shinya_okayama/scraps/ee1d0a36e2c2a6
参考書籍
TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発

3章途中のメモ

  • 仮想DOMの描画エンジン。
  • 導入からプロジェクトの開始と最序盤のコンポーネントの記述くらいまでは出来たものの、書籍の目的としてNext.jsに内容を割いているようで、JavaScript自体の応用的なものやReactの用語や概念の説明が省かれている箇所が頻出してきているため、合わせて信頼性の高いドキュメントを読んでいく。
  • https://ja.javascript.info/
  • https://ja.react.dev/learn
  • https://developer.mozilla.org/ja/

2024/12-12

  • 基本的なフックの記述例に触れることはできたが、今のところ具体的な使い方までは考えが及びにくい。useStateやuseReducerで状態を記録して、それを他のフックで応用というのが基本的な流れだとは思えた。
  • しかしそれらのフックの主にメモ化に関するものは、実際の環境や挙動を考慮しないと適正化の考え方が分からない。
  • 実践でかなり使いそうなのはuseEffectだろうなと思う。
  • フックがユーザー体験的に重要なのは、再描画のコストを設計時にかなり調整できるところだろう。
  • バックエンドにつながりそうな部分で考えれば、分かりやすく扱いやすいコンポーネントの構造を考えたり、フックによる状態の受け渡しとその動的な扱いを考慮するところだと思う。
  • しかし深いところまでいくと、例えばJavaScriptのDateオブジェクトを扱う部分があったが、引き出し方を変えたりするときにはオブジェクトの構造が分かってないと型定義も合わせた記述の仕方が分からない感じがあった(実際そこまでやるケースがどれだけあるかわからないけど)。
  • Reactはライブラリなので、当然JavaScriptの理解が深ければ出来ることも増えるだろうと思う。
Shinya OkayamaShinya Okayama

2024/12-13

  • Next.jsの項目に入った。
  • しょっぱな、create-next-appで導入してみると、当たり前だが、書籍のバージョンとほとんど違う環境が自動でインストールされたので、調整に手間取る。
  • 現在はApp-Routerが推奨されているっぽいが、本ではその環境以前なので、デフォルト設定で導入されたディレクトリ構造が全く違った。
  • Githubで書籍の環境のバージョンが分かったので、ちょっとマニュアル操作を混ぜてそれぞれのバージョンを合わせる。
  • 合ってるか分からないので、内容を進めるうちに問題が発生したらそのときに対処する。
Shinya OkayamaShinya Okayama

2024/12-16

  • Next.jsのレンダリング手法。

  • 静的サイト生成(SSG)、クライアントサイドレンダリング(CSR)、サーバーサイドレンダリング(SSR)、インクリメンタル静的再生成(ISR)など複数ある。

  • このあたりが理解しづらいというのを伺ったし、実際いまのところ具体的な使い分けが思い至らない。

  • 大雑把に分けると、静的か動的か、どのタイミングで行うか、といった違いがある。

  • 領域で分けると、CSR以外はほとんどサーバーサイドの働きであり、CSR自体はフロントエンドで常習的に行われる手法である(サーバーと連携しない、ユーザー入力に応じたページ可変とか、APIを使った出力とか)。

  • 基本となるのがSSGで、内容が変わらないページやページの一部(AboutとかFAQとか、たぶんヘッダーとか)を事前に出力して用意しておく。

  • SSRはページのアクセスごとに(リクエストごとに)サーバー側で描画してクライアントに渡す方法。変動しやすい価格表示などのリアルタイム性を保つために使われると考えられる。サーバーでの処理を逐次行う関係から、負荷や遅延のリスクがある。

  • ISRはイメージでいうとSSGとSSRを合わせたようなもの。

  • 「SSRと違ってリクエスト時にサーバー側での処理が無い」と説明されているのだが、たしかに間違った内容ではないっぽいのだが、正確にはリクエスト時にSSGで用意したページデータをクライアントに渡すが、そのデータに有効期限を設定してキャッシュしておき、有効期限が切れたあとにリクエストがあった場合は一旦用意したデータを渡した後、SSGでページデータを最新の情報に更新し、それをキャッシュしてから渡すということらしい。

  • あくまでも「リクエストごとの処理負荷」が必ず発生せず、リクエスト時に待たされないという意味で「リクエスト時にサーバー側での処理が無い」ということらしい。わかりづら。

「生成」と「レンダリング」って違うのか?

  • 最終的に「HTMLを作る」といった意味では同じ。
  • 「事前に」用意される。静的などの意味合いでは生成。事前にファイルとして存在している点も違う。
  • 「その場で」HTMLを作る。実行タイミングが「リクエストやクライアントイベントに依存」している。動的などの意味合いではレンダリング
  • しかし先のISRなどの手法を考えると、このあたりはかなり曖昧になってくる可能性がある。
  • 静的・動的という意味合いやタイミングや領域での区別を付けるために分けて用いられてきた感じがするが、根本的にはWeb技術での事情が起点のような気がする。

歴史的な背景
レンダリング(Rendering)はもともとグラフィックやブラウザの表示プロセスに使われる用語です。
クライアントサイドJavaScriptの普及以前は、HTMLの生成はサーバーサイドの役割であり、主に「静的生成」と呼ばれるプロセスが中心でした。
クライアントサイドでHTMLを動的に生成する技術(CSR)の登場によって「レンダリング」という言葉がフロントエンドでも一般化。
この歴史的経緯から、生成とレンダリングという別々の用語が定着しました。
by ChatGPT

具体的な記述に関して。

  • いまのところ基本的な部分なので難しい部分は無いが、そこまでにやっている内容(TypeScriptとReact)の応用なので、ここまでを理解しきれていなかったら、かなりゴチャゴチャしてくるかもしれない。
  • TypeScript自体は本来のオブジェクト指向言語の仕組みをJavaScriptに多少搭載するみたいな奴っぽいので、ここまでで「TypeScriptいみわかんねえ🤪」ってなってるのは、たぶん仕方ない。
  • 実際、この基本的な部分というよりも、これを使って色々していく段階が複雑になってくると思うので、ここはしっかり押さえておきたい。
Shinya OkayamaShinya Okayama

[簡易]3章のまとめやメモ(React/Next.jsの基礎)

*前提(バージョン)
└React - 18.2.0
└Next.js - 12.2.3
(書籍の環境と合わせた)
※Next.jsの13.4からApp Routerになっているが、本の内容と乖離してしまうので、ここではPages Routerでやっていきます。最新の内容は本の内容が終わった後にやるかもしれない。

*要点

  • Next.jsとは何か?
  • ReactとNext.jsの関係性。

*まとめ
└Reactの基本(コンポーネント、Context、React Hooks)
└Next.jsの基本(生成とレンダリング方法、表示やリンクの具体的方法など)

Shinya OkayamaShinya Okayama

Next.jsとは何か?

└Reactをベースとして、Reactの拡張が出来るフレームワーク。
└ReactのCSRに加えて、SSG、SSR、ISRといった生成・レンダリング機能をプラスすることにより、SEOに適したパフォーマンス、そしてアクセスとレスポンス性能を高めることが出来る。
└デフォルトでルーティング機能を持つ。ReactにもReact Routerというライブラリが存在するが、Next.jsのルーティング機能は簡易かつ自動化されている。
└APIの初期構築が容易。高度な機能でないAPIなら、すぐに実装できる。

ReactとNext.jsの関係性

└Reactが「フロントエンドをAtomic Design化するもの」なら、Next.jsは「WebサイトやWebアプリケーションをAtomic Design化させるもの」だといえるかもしれない。
└Reactを拡張させるライブラリなどはいくつかあるが、Next.jsはReactを拡張させるフルスタックフレームワークとして、かなり完成されている。

Shinya OkayamaShinya Okayama

Reactの基本概念

基本的なイメージ
【通常のDOMツリーによる描画のプロセス】

  • 通常のDOMツリーによる操作では、HTMLとCSSをそれぞれツリー状(抽象構文木)に変換し、それをDOMツリーに構成する。そこからJavaScriptによる操作が加わることで、さらに構造が変化したDOMツリーが構成される。
  • つまり基本DOMツリーとその加工DOMツリーという段階が発生している(それぞれのDOMツリーには正しい名称があるが、ここでは省く)。
  • 元々Webページはフロントエンドやバックエンドという領域の区別は無く、かなり混在していた。そもそもフロントエンドやバックエンドという概念自体、技術の発展とユーザーの需要により自然発生していき、洗練されていったという過程がある。
  • フロントエンドの動的な部分はJavaScriptが担当し、データベースやルーティングなどはバックエンドの領域として確立されていったと記憶している。
  • なのでDOMツリーを一度構成してから、さらに加工したDOMツリーを構成するというのは、一見して無駄な工程にも思えるが、役割を明確にするという考え方はプログラミングの基本であるだろう。

【React及びモダンフレームワークの場合】

  • JavaScriptの発展により、JavaScriptでフロントエンドとバックエンドの範囲をカバーできるようになった。
  • なので、既存のDOMツリーの構成から描画までのプロセスをより効率化し、さらに動的な変化を柔軟に行えるようにするのは、必然的な流れであったと思われる。
  • Reactは仮想DOMツリーを構成し、変化のあった部分だけを変更するというような仕様になり、変化をすぐに反映できるうえに、必要最低限の処理で済むという利点がある。
  • 「基本のDOMツリーを構成してから変更を加える」という点では同じだが、仮想であるという点が重要だ。
  • イメージ的には、既存の方法は完成品のイスを物理的に完成させてから、そのイスをバラバラにして再構築していたが、Reactでは物理的に完成品を作らずにイスの設計図を構成し、その設計図に変更を加えてから物理的なイスを完成させるという違いであると考えられる。
  • しかし、ある種シンプルで役割が明確に分かれていた既存のWebページの構成と比べて、Reactやモダンフレームワークは領域ごとの全般的な理解が足りていないと、仮想DOMツリーやそれによって発生する効率化や変化の自由度を発揮しにくい。
  • というより、操作や構成に慣れ、事前に準備が整っていればReactによるWebページやアプリケーションの構築は従来の構築よりも実装が速くかつクオリティの高いものを実現できる可能性が高いが、そこに至れるまでのハードルが高い。
  • 逆に既存の技術はハードルが低いが、効率性や自由度では劣る。
  • 例えばそれなりの高機能を実装しようとしたとして、Reactなどを利用すればそれを容易かつ高いクオリティで実現できるが、既存の技術でそれをやろうとすると逆に手間が掛かってしまうか、あるいは実装困難である可能性が考えられる。

コンポーネント
【概念上での定義】

  • 再利用可能性: 同じコンポーネントを複数の場所で再利用可能。
  • 独立性: 他のコンポーネントとは独立して動作する。
  • 状態管理: 状態(state)やプロパティ(props)を使用して動的なUIを管理。
  • ライフサイクル: コンポーネントにはマウント、更新、アンマウントなどのライフサイクルが存在。

【データ上の定義(関数コンポーネント)】

  • JavaScriptオブジェクトとして定義される。
  • Props(プロパティ)親コンポーネントから渡される読み取り専用のデータ。JavaScriptオブジェクトとして渡される。
  • State(状態)コンポーネント内で管理される動的なデータ。関数コンポーネントではuseStateで管理。
  • Children(子要素)コンポーネント内でレンダリングされる子コンポーネントや要素。
const component = {
    type: 'function',
    name: 'Greeting',
    props: {
        name: 'Alice'
    },
    children: null
}

Props(プロパティ)

  • Propsは基本的に「値としてコピーされる」が、参照型(オブジェクトや配列)の場合は参照が渡される。
  • Propsは読み取り専用。子コンポーネントで直接変更してはいけない。
  • 状態管理(State)は親コンポーネントで行い、子からはコールバック関数を通じてデータを更新する。
  • 一方向データフローを守ることで、Reactの再レンダリングやデバッグが容易になる。
Shinya OkayamaShinya Okayama

Reactの基本

  • コンポーネントを基本としたUI構築ライブラリ。
  • 仮想DOMを使った変更と差分更新。
  • UIの状態(State)の記録と、その変化による更新。
  • 親から子へ一方向のデータフロー。
  • JavaScript内でHTMLのようなコードを書けるJSX(TSX)構文。

Reactの構築手順(JSX)

【基本】
1.コンポーネントを記述する。

export default function Image() {
  return (
    <div>
      <Sample src="https://sample.com/sample.jpg" alt="sample" />
    </div>
  )
}

2.propsで親から子へ

function Sample({ src, alt }) {
  return <img src={src} alt={alt} />
}

export default function Image() {
  return (
    <div>
      <Sample src="https://sample.com/sample.jpg" alt="sample" />
    </div>
  )
}

(見た目だけならば、上記のように親子の入れ子関係とそのデータフローで成り立つ)


【データ処理などの記述】
【Context】(直接的な親子関係でない場合に便利なデータの渡し方)

"Context(Provider, Consumer)"

import React, { createContext } from 'react';

const TitleContext = createContext('');

function Titlename() {
  return (
    <TitleContext.Consumer>
      {title => (
        <h1>{title}</h1>
      )}
    </TitleContext.Consumer>
  )
}

function Header() {
  return (
    <div>
        <Titlename />
    </div>
  )
}

function Test() {
  const title = 'lmao'
  return (
    <TitleContext.Provider value={title}>
        <Header />
    </TitleContext.Provider>
  )
}

("Consumer"はすでにあまり使用されない方法)

"useContext"

import React, { createContext, useContext } from 'react'

const TitleContext = createContext('')

function Titlename() {
  const title = useContext(TitleContext)
  return <h1>{title}</h1>
}

function Header() {
  return (
    <div>
      <Titlename />
    </div>
  )
}

function Test() {
  const title = 'lmao'
  return (
    <TitleContext.Provider value={title}>
      <Header />
    </TitleContext.Provider>
  )
}

("useContext"のほうが一般的かつ簡易な方法)

Shinya OkayamaShinya Okayama

【React Hooks】(関数コンポーネント中で状態や副作用を扱うことができる)
"useState"(状態を扱うための基本的なフック)

const [状態, 更新関数] = useState(初期状態)
// 以下の場合、初期値は0となる。
const [count, setCount] = useState(0)
// 以下のように呼び出されると、状態が変化する。
<button onClick={() => setCount(count + 1)}>+1</button>
// setCountのような更新関数を呼び出すと、その状態に関連するコンポーネントが再レンダリングされる。

"useReducer"(状態を扱うためのフック。useStateよりも複雑な用途に向いている)

// dispatch(actionを送信する関数)にaction(状態変更の内容)を渡す。
// reducerはactionと現在の状態を元に変更した値を返す。
reducer(現在の状態, action){
    return 次の状態
}
const [現在の状態, dispatch] = useReducer(reducer, reducerに渡される初期状態)

// 例
const reducer = (currentCount, action) => {
    switch (action) {
    case 'INCREMENT':
        return currentCount + 1
    case 'DECREMENT':
        return currentCount - 1
    default:
        return currentCount
    }
}
const Counter = (props) => {
    const { initialValue } = props
    const [count, dispatch] = useReducer(reducer, initialValue)
    return (
        <div>
            <button onClick={() => dispatch('INCREMENT')}>+</button>
            <button onClick={() => dispatch('DECREMENT')}>-</button>
    )
}

"useContext"(Contextから値を参照するためのフック。コンテキストAPIを使う)
(上記を参照)

"useRef"(値を参照するために使うが、再レンダリングされないという違いがある。DOM要素にアクセスすることができるというのも大きな違い)
(よく分からなかったので調べた。詳しくは後述する)

import { useRef } from 'react'

const FocusButton = () => {
  const inputElement = useRef<HTMLInputElement>(null)
  const focusInput = () => {
    if (inputElement.current) {
      // input要素にフォーカスを当てる
      inputElement.current.focus()
    }
  }
  return (
    <>
      <input type="text" ref={inputElement} />
      <button onClick={focusInput}>Focus Element</button>
    </>
  )
}

export default FocusButton

"useEffect"(副作用を扱うために使用される)
(副作用が何なのかを含めて、詳細を後述する)

import { useState, useEffect } from 'react'

const ScrollEvent = () => {
  const [scrollY, setScrollY] = useState<number>(0)
  useEffect(() => {
    const handleScroll = () => setScrollY(window.scrollY)
    window.addEventListener('scroll', handleScroll)
    return () => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [])
  return (
    // スクロールの値の変化が分かりやすいように、極端なmaginを設定している。
    <div style={{ margin: '150vh 0' }}>
      Scroll Position
      <p
        style={{
          position: 'fixed',
          bottom: '0',
          left: '0',
        }}
      >
        スクロール位置: {scrollY}px
      </p>
    </div>
  )
}

export default ScrollEvent

他に以下がある。
(この辺りは実践例を交えて理解していった方が良いと思ったので、ここでは詳しく書かない)
(というか、調べだしたらしばらく終わらなそうなので)
"useLayoutEffect"
"useMemo"
"useCallback"

Shinya OkayamaShinya Okayama

"useState"と"useReducer"を利用して、簡単な計算や数字を表示するサンプルを試作した。
オブジェクトと関数を使って、かなり簡単に扱えた印象。
だが、本格的な電卓にしていくと、かなりロジックの構築に手間が掛かりそうである。

Shinya OkayamaShinya Okayama

以下はChatGPTに画像生成してもらったReactコンポーネントのイメージ。

まだデータ構造の詳しい内容は学習不足だが、ツリー状(構文木)は応用がかなり効く上に、データ構造が分かりやすく、構造を示すのみならデータ容量も低いという性質があるかもしれない。一番の利点はオブジェクトで表せることだと思う。

例えば、3次元的なデータ構造を示すならルービックキューブのような形になるかもしれないが、構造としては理解しやすいにしても、複雑なのでデータ操作を加えるのが煩雑になるだろう。

細かい設計思想は分からないが(仮に調べたとしても理解できない気がするし)、ReactコンポーネントはDOMツリー構造に適用する前提で構成されていると考えられる。
セキュリティ的な面でも、ツリー構造に則る上でも、PropsとStateという仕組みはJavaScriptにもブラウザの仕様にも適していると思われる。

Shinya OkayamaShinya Okayama

"useRef"について
*基本的な概要
= DOM要素やコンポーネント間で保持したい値を参照するために使用する。

Q. なんで"useState"とかじゃダメなの?

  • 教本ではReactに主眼を置いていないので、このあたりの詳細が省かれていた。
  • 簡潔に結論を述べると仮想DOMによる描画を行うReactの仕様から必要とされたフックなのだと考えられる。
  • Reactは既存のDOMツリーによる描画の上に仮想DOMという抽象化層を作り、柔軟かつ効率的な描画と状態管理を行っている。
  • ReactはJSXやTSXの記述からDOM APIへ連絡している形であり、つまり"return <div>create</div>"を"const div = document.createElement('div'); div.textContent = 'create';"というように変換して、「こういう感じに作って」と指示を出している。
  • なので直接的にDOMツリーにアクセスして変更するようなものではないし、基本的にReactでは描画と状態管理に関わるもの以外のブラウザAPIには関与しない設計になっている。
  • しかし基本的には関与しないが、上で示した"focus()"などがUIとして必要になってくる場面がある。後でまとめる"useEffect"の副作用も同様であると思われるが、これらに対して仮想DOMを介したアクセスを行うとそのたびに再描画が発生してしまう可能性も出てくるので不都合になってくる(一応そういう場合では、たぶん"useMemo"などを使用するのかもしれない)。
  • そうした事情から「再描画が発生しない」、「DOMアクセスが可能」なフックが必要とされたのだろう。

*使用上の注意

  • ここまで考えると分かるが、基本的に「仮想DOM経由の描画」を行っているReactで「直接的なDOMアクセスによる操作や描画」が合わさると、処理の競合が発生する可能性がある。
  • それを避けるためには"useState"や"useReducer"は状態管理に用い、"useRef"は「Reactが関与しない部分でUI構築に必要な部分」に使用すべきである。
  • 具体的にはDOM要素のフォーカス管理要素の測定(サイズや位置)外部ライブラリとの連携などが考えられる。
Shinya OkayamaShinya Okayama

"useEffect"について
*基本的な概要
= Reactで副作用を処理するための標準的な方法。

Q. 副作用(Side Effects)ってなに?

  • 副作用とはReactが主にやっているUIの描画、コンポーネントのレンダリングとは直接関係のない処理のこと。
  • よく用いられるのは「APIコール」、「イベントリスナーの登録」、「タイマー」など。
  • Reactは純粋関数のような性質を理想としているため、UIの描画に集中し、描画に直接関係のないものを副作用として扱っている。
  • 同じ入力は同じ出力を返す = 同じ引数(props)に対して、常に同じ出力を返す。
  • 副作用を持たない = 外部のデータや状態を内部に影響させない(API呼び出しなど)。
  • 不変性を保つ = 状態やpropsを直接変更せず、新しい値を生成して返す(stateに代入したりなど)。
  • ここで疑問になってくるのは「けど、useEffectを使わなくても、onClickとかのハンドルをそのまま設定してなかった?」である。
  • さらに細かく考えると、何が副作用で、何が副作用ではないのかを理解していないとこの辺りは上手く理解できず、useEffectに何を使うべきなのかもよく分かりづらくなると思うので、イベントとReactのイベントの扱いを掘り下げてみる。

*そもそもイベントとは何で、何が扱っている?

  • イベントとは、ユーザー操作やブラウザ内部のイベント(例: ページ読み込み完了)のこと。
  • ブラウザ(イベントディスパッチャ)、JavaScriptエンジン、DOMが扱いに関わるが、ほとんどブラウザが主体といえる。
  • ブラウザがDOM要素を監視し、対応するイベントオブジェクトを生成する。このオブジェクトにはイベントの種類やイベントが発生した要素など、プロパティやメソッドが含まれている。
  • ブラウザはオブジェクトの生成・管理、扱いの指示を行う。JavaScriptエンジンはJavaScriptの記述に従って命令を実行し処理する。DOMは定義された構造を持ち、構造の参照元になる。

Q. 通常の場合とReactの場合のイベントの扱いの違いは?

  • 突っ込んで調べたことが無かったので、改めて「イベントリスナー」と「イベントハンドラー」についてまとめる。
  • イベントリスナー = 特定のイベント(クリック、キーボード入力、マウス移動など)が発生するのを「待機」し、対応するイベントハンドラーを呼び出す(リスナーが"イベントを監視している"という表現がよく使われるのだが、色々調べると監視しているのはブラウザなので、リスナーは"待機・待ち受け"という方が正確だと思う。たぶんプログラミングにおけるリスナーの役割が"監視"という定義だからだと思う)。
  • イベントハンドラー = イベントリスナーによって検出されたイベントに対して、実際の「処理」を行う。イベント発生時に呼び出される「コールバック関数」。
  • 調べたが、リスナーやハンドラーはセットで扱われたりする関係もあり、記事や資料によっては少し定義がブレるようである。処理全体をイベントハンドリングといったりもする。
  • ただハンドラーはリスナーによって呼び出される関数であり、リスナーはイベントの発生を感知してハンドラーを呼び出す仕組みや関数というのは、おそらく言い切れることだろうと思われる。

イベントハンドラー、イベントリスナー、イベントハンドリングは、プログラミングにおいてユーザーの操作やシステムの動作に応じて特定の処理を実行するための重要な概念です。

イベントハンドラー(event handler): 特定のイベントが発生した際に実行される関数や処理のことを指します。例えば、ボタンがクリックされたときに実行される関数がイベントハンドラーです。イベントハンドラーは、イベントが発生した要素に直接関数を割り当てる方法で実装されます。
イベントリスナー(event listener): 特定のイベントの発生を監視し、イベントが発生したときに対応するイベントハンドラーを呼び出す仕組みや関数のことを指します。イベントリスナーは、イベントが発生した要素を監視し、関数を呼び出す方法で実装されます。
イベントハンドリング(event handling): ユーザーの操作やシステムの動作に応じて、適切なイベントハンドラーを実行する一連の処理全体を指します。これにより、プログラムはユーザーの入力や他のイベントに動的に対応することができます。
by ChatGPT


*ネイティブイベント(通常の場合)の伝達の仕組み

  • ブラウザはイベントを監視しており、例えばWebページのどこかにボタンが設置されている場合、ユーザーがボタンをクリックするとブラウザはそのクリックのイベントオブジェクトを生成する。
  • Reactのコンポーネント間のやり取りと同様に、「親子関係(ツリー)を伝って情報をやり取りする」。渡されるのはイベントオブジェクト。
  • ルート(document)からイベントが発生したDOM要素に向かって、DOMツリーを辿ってイベント伝播が発生する。その流れは下記のフェーズである。
  • キャプチャリングフェーズ
  • まず、DOMツリーのルート(document)からイベントが発生した要素へとイベントオブジェクトを渡す。
  • 例: document → <html> → <body> → <div> → <button>
  • ターゲットフェーズ
  • 発生したターゲット要素(この場合だと <button>)に到達する。
  • バブリングフェーズ
  • イベントオブジェクトは再び親要素に向かって渡される。
  • 例: <button> → <div> → <body> → <html> → document
  • この流れの途中にイベントリスナーが設定されているとリスナーはイベントを感知し、イベントハンドラーを呼び出す。
  • これだけを見ると「イベントが2回発生しないのは何故だろう?」と思うかもしれない。
  • 調べると、addEventListenerを設定するとき第三引数に真偽値の"true"を設定しない場合、デフォルトでバブリングフェーズでリスナーが働くようになるということであるらしい。
  • なので基本的にイベントリスナーを用いるときはバブリングフェーズで発生していると考えて良いだろう。
  • バブリングがデフォルトになった理由は、初期のIEにはキャプチャリングフェーズが存在しなかったこと、バブリングフェーズの方がハンドリングの利便が高いこと、キャプチャリングフェーズを明示的に使う場合が少ないことなどがあるという。
  • なのでキャプチャリングフェーズでイベントリスナーの設定されている要素に伝わっても、そのときリスナーはイベントを感知せず、ターゲットからルートへと戻っていくバブリングフェーズにイベントを感知してハンドラーが実行される。

*Reactのイベント伝達の仕組み

  • Reactは上記のネイティブイベントの仕組みをラップし、抽象化と仮想化によって効率化している。
  • 基本的にまずはネイティブイベント(通常の場合)の伝達の仕組みの通りになるが、Reactにおいてはリスナーの設置や仕様などが変わってくる。
  • Reactは、rootやdocumentに最上位のイベントリスナーを登録して、そこでイベントをキャッチする。通常のように要素ごとにリスナーを設置せず、この最上位のリスナーにまとめる形となる。
  • そしてイベントを合成イベント(SyntheticEvent)としてラップし、ブラウザごとの差異が発生しないように抽象化することが出来る。
  • SyntheticEventは仮想DOMを通じて、イベントが発生した実際のDOM要素に対応するコンポーネントを特定し、そのコンポーネントに設定されたイベントハンドラーが実行される。
  • Reactでは、だいたいのDOMイベントをサポートしている。
  • "onClick"、"onChange"、"onScroll"など、ほとんどである。

*では、なぜuseEffectを使用する場合があるのか?

  • 説明を繰り返すようになるが、useEffectで扱うのは副作用であり、副作用とは描画・レンダリングとは直接関係のない処理のことである。
  • なので、単純な"onClick"のようなハンドリングは内部で完結する形態であるので、描画を連動させるにしてもuseStateに紐付ければそのまま使用できるということだと思う。
  • しかし、そもそもReactの設計上ではwindowとdocumentのイベントをそのまま扱えるようになってはいないので、ネイティブイベントとして直接的に設定する必要がある(Reactでは仮想DOM要素を対象としており、windowとdocumentはReactのイベントハンドラーからでは指定できない)。
  • だが、ネイティブイベントとしてそのまま設定すると、Reactの動きとは別の流れで処理が発生してしまう。
  • 場合によっては、DOM要素を直接操作するものと、仮想DOMからDOM要素を操作する処理が競合してしまうことになる。
  • なので、useEffectとして明示的に分けたうえで、useStateなどに連絡できるものはそちらに収束させ、再描画に関わりを持たないものであれば明確に分ける必要がある。
  • ReactはUIの構築と管理を目的としたものであり、その方法論としてコンポーネントの状態管理と再描画を扱うものであるが、再利用性や純粋関数の性質を再現する上では、再描画に関連の薄いロジックを分離してから統合するためにuseEffectというフックが設計されたということだろう。