1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactAdvent Calendar 2023

Day 4

[React]Hoisting、理解して使ってますか?(事例問題有)

Last updated at Posted at 2023-12-03

お世話になっております。あいおんです。

掲題の件、何気なくReactを使っていて、きちんと理解していないがゆえに起こったトラブルのお話をします

(若干謎解き感あるので、React少しでもわかる人は是非見てみてください。)

(何もわからない・・・おれは雰囲気でReactをやっている・・・)

前提

Reactアプリを作ろうと思い、ViteでCreateしました。(さくっと作ろうとしたので、Javascriptです。Typescritptではないです)

コード

まず、問題のコードは以下に置いておきます。(大したものじゃないです)
https://github.com/aion0721/hoisting-sample

ポイントの部分だけ抽出すると以下の通りです。ざっくり説明すると以下です。

  • 親コンポーネント(App.jsx)から、子コンポーネント(ChildConst.jsx)を呼び出している
    • その際、countをPropsとして渡す
  • ChildConst側はcountを受け取って表示
    • 親からのPropsをPropTypesとして定義
App.jsx(親)
import ChildConst from "./components/ChildConst";
import ChildFunction from "./components/ChildFunction";

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
      <ChildConst count={count} />
      <ChildFunction count={count} />
    </>
  );
}
ChildConst.jsx(子)
import PropTypes from "prop-types";

ChildConst.propTypes = {
  count: PropTypes.number.isRequired,
};

const ChildConst = ({ count }) => {
  return (
    <div>
      Child Const Component
      <br />
      ParentCount: {count}
    </div>
  );
};

export default ChildConst;

さて、このコードの何が問題でしょうか。

何が起きるか

詳しい人はもう何が起こるかわかると思います。
(ただ、私は通常開発時には「yarn dev」でローカルで開発サーバを起動しているので、”後からPropsを足した場合”でもエラーが出ないのです・・・)

そうです、ViteはHot Module Replacement (HMR) なので、あとからPropsを足しても”見た目上は”正しく動き続けます(エラーで画面が真っ白になるみたいなことはないです)

このあと、Gitlab等にPushし、GitlabPagesを見て動かないことに気づきました。

しかもエラーは以下のような文言で、なんのことだかさっぱりわかりません

image.png
(index-A1Ve6egb.js:40 Uncaught ReferenceError: Cannot access 'ic' before initialization)
※ic・・・?そんなの定義してないんだが・・・

なんで気づけなかったか

言い訳させてください。

このChildConstを作る前に、ChildFunctionとして別のコンポーネントを作ってました。以下のような感じです

ChildFunction.jsx(子)
import PropTypes from "prop-types";

ChildFunction.propTypes = {
  count: PropTypes.number.isRequired,
};

function ChildFunction({ count }) {
  return (
    <div>
      Child Function Component
      <br />
      ParentCount: {count}
    </div>
  );
}

export default ChildFunction;

構え方としては、ChildConstもChildFunctionも同じですよね。
ただし、このComponentはエラーになりません。
何で同じような感じなのに、ChildConstはエラーになって、ChildFunctionはエラーにならないんでしょうか。

まさにHoisting(関数巻き上げ)

コンポーネントを作るときに、const派とfunction派に分かれると思いますが、私は(なんかよくわかりませんが)constが好きです。

constとfunctionの違いとしては、functionはHosting、つまり関数巻き上げが発生します

こうなると、functionの方では、PropTypes定義よりも上に来るので、未定義状態になることはありません。
ただし、constだと、巻き上げは起こらないので、PropTypes定義で、未定義としてエラーが出るんですね。

ちなみに、開発サーバの時点で、ページをリロードすると真っ白になり、コンソールを見ると以下エラーが出てます。
(この表示であれば比較的早めに原因にたどり着けると思います。)

image.png
※初期化するより前には、ChildConstにアクセスすることはできません。そりゃそうかって感じですね。

勿論、Hosting自体は知識としてはありましたが、実際に当たるとうっかりしちゃうものですね。。。

教訓

以上です。原因がわかってしまえばなんてことはないんですが、何気に原因調査に時間がかかりました。

  • 原因調査に時間がかかった原因
    • icとかいう謎の定義(実際に起きたのはgiとかだったので、ターミナルと間違えてgiという文字が混在したかと最初思ってしまいました・・・

というわけで以下をきちんとやっていきましょう。

  • 開発の時点でもきちんとPush前にはReloadとかして確認する。(面倒がらない)
    • きちんとテストコード書くのも必要ですね。
  • functionかconstか統一しよう
    • 混在した理由は、ChatGPTを使ったからです。ChatGPTはFunction派でした。
  • ちゃんとConsoleみよう
    • これはもはや当然ですね・・・
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?