NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    React(Javascript)おさえておくべき3つのポイント



    はじめに

    こんにちは。新入社員の嘉地です。
    まさかブログを同時期に2本出すとは思ってもみませんでした。しかも技術系で。。。書き始める前は、とても腰が重かったのですが、いざ書き始めてみると、すごく楽しくて、筆が止まりませんでした(言ってみたかっただけ)

    私のブログを初めて見るよ!という方で、こちらのブログからご覧になった方は、下記のブログもぜひ覗いてみてください。なんとなんと、下記のブログも読んでいただくと、、、?
    もれなく、嘉地が喜びます。はい。期待させた方、すみません。こちらのブログ、React初学者向けに書いているので、簡単に知識がつくこと間違いなし。

    どちらから見た方がいいということはありませんが、Reactについて何も知らない!!という初学者の方は、下記のブログを見てからこちらのブログを読んでいただいた方がより理解が深まるかもしれません。
    もちろん、このままこちらを読んでいただいてもかまいません。
    ですが、こちらを読んだ後にでも、ぜひ!!下記のブログをご覧くださいませ!!(めちゃくちゃな念押し)

    tech.nri-net.com


    それはさておき、こちらのブログでは、現在、Reactを使って実装を行っている私が、これはおさえておいたほうがいいなと思うことを3点あげると同時に、解説を行っていくものとなっております。
    私もブログを書きながら、より深く理解し、整理していこうと思っておりますので、共に頑張っていきましょう!!!


    そもそも書き方が慣れない

    新人研修の時は、Javaを触っており、Javascriptも初めてだったので、書き方にそもそも戸惑いました。

    アロー関数ってなに??

    const arow = (int1, int2) => {
            return int1*int2;
    }
    ...
    console.log( arow(3, 7) ); //21
    

    最初この=>を見たとき、頭にはてなが浮かびました。何がどうなっているのか。Javaのメソッドのように書くだけではいけないのか。。。
    でも意外と動きはシンプルで、

    const arow = (引数1, 引数2...) => {
            return (
                  //引数を使って行いたい処理を書く
            )
    }
    ...
    console.log( arow(引数1, 引数2...) ); //呼び出し元には、処理された結果が返ってくる
    

    このようなJavaのメソッドとあまり大差ない動きになります。そして、この=>の正体は、アロー関数と呼ばれます。
    アロー関数についての詳細は、下記をご参照ください。

    developer.mozilla.org

    その他の独特な書き方(等価、三項演算子)

    私がコードを書く上で慣れなかった書き方を2つご紹介します。


    1.値の比較の仕方(等価と厳密等価)

    const stringNum = "123";
    const num = 123;
    
    stringNum == num; //true
    stringNum === num; //false
    

    ==を使って値を比較する方法はもちろんご存知かと思いますが、Javascriptでは、イコールが2つでも、3つでも値を比較することができます。
    上記の例では、文字列の123である変数stringNumと数字の123である変数numが定義されています。
    これを比べてみると、イコールが2つの結果はtrueで、3つだとfalseになります。もうお分かりだと思いますが、

    const stringNum = "123";
    const num = 123;
    
    stringNum == num; //型は比較対象にならず、値を比較対象とする(等価)
    stringNum === num; //変数の型と、値の両方を比較対象とする(厳密等価)
    

    このように、イコールが2つだと、型は違っていても、値が同じであればtrueになります。これを等価と言います。一方、イコールが3つだと、型が一致しているかどうかも比較対象となるため、完全一致でtrueになります。これを厳密等価と言います。
    比較を否定的にしたい場合は、

    stringNum != num; //型は比較対象にならず、値を比較対象とする(不等価)
    stringNum !== num; //変数の型と、値の両方を比較対象とする(厳密不等価)
    

    このようにすることで否定的に比較することができます。こちらは、型を対象にしない場合を不等価、型も対象にする場合を厳密不等価と言います。等価と厳密等価、不等価と厳密不等価の詳細については、下記をご参照ください。

    developer.mozilla.org

    developer.mozilla.org

    developer.mozilla.org

    developer.mozilla.org


    2.三項演算子

    onigiri1 = "konbu";
    onigiri2 = "mentaiko";
    
    ...
    
    <p> { onigiri1 === "konbu" ? "このおにぎりは昆布です" : "このおにぎりは昆布ではありません" } </p>
    //はてなより前が、判断条件になり、trueであれば、':'より前が返り、falseであれば、':'の後ろが変える
    

    { 比較したい値 === 比較したい値 ? 真の場合に返したいもの : 偽の場合に返したいもの }のように書くと、 マークアップの中で真か偽かで、返る値を変えたいとき、このように実装することで返り値を変えることができます。
    三項演算子の詳細については、下記をご参照ください。

    developer.mozilla.org


    Stateってなに??

    Stateの概念

    正直このStateの概念を捉えることが難しかったです。頭でわかったつもりでも、コンポーネントがどんどんネスト化していくと、頭が混乱します。ひとつひとつゆっくり学んでいきましょう。

    まず、Stateというものは、その名の通り状態を意味し、値の状態を管理します。どういう時に使うかというと、ユーザーの操作に応じて、値を更新するといった場合、その変数の状態を管理する目的で使用されます。そして、このStateの値の状態は、コンポーネントごとに保持されます。
    例えば、parents.tsx内でStateを定義すると、そのコンポーネント(parents.tsx)内でしかそのStateの値を保持できないということです。そのため、値が更新されるときには、そのStateを持っているコンポーネントが再レンダリングされることによって値が更新されるという特性があります。

    もし、Stateを定義したコンポーネントとは別のコンポーネント内で、Stateを更新したい場合、そのStateをPropで渡すか、useContextというものを使ってそこにStateを定義することで、子コンポーネント内でStateを更新したりすることができます。useContextについては、後ほど解説します。
    このStateですが、ReactのライブラリにはuseStateという状態を管理するフックがライブラリとして存在します。Reactでは一般的にこのuseStateを用いてStateの管理を行います。

    useStateについて

    (useStateの使い方)

    //reactからuseStateをimportしてくる
    import { useState } from 'react';
    
    ...
    
    const [ index, setIndex ] = useState(1);
    //const [状態管理される変数(現在の値を保持), 状態を変更するための関数(セッター関数)] = useState(状態の初期値);
    
    setIndex(2);
    //indexを2に更新
    

    上記のように書いて使用します。値を更新したい場合は、useStateの配列の2つ目にあるセッター関数に値を渡すことによって値を更新することができます
    では、これを踏まえたうえで、下記のコードを読んでみてください。indexの初期値は、1ですが、このボタンを一度押した際にコンソールに表示される値は何になると思いますか。

    const Parents = () => {
    const [ index, setIndex ] = useState(1);
    
    
    const btnClick = () => {
            setIndex(index+1); //indexに+1する
            console.log(index);
    };
    
       return (
          <button onClick = { btnClick } > +1 </button>
           <p> { `index: ${index}` } </p>
       )
    };
    

    おそらく、2だと思う方が大半かと思われますが、実は1が正解です。

    値が更新されない問題

    実は、このuseStateのセッター関数に値をセットした段階では、まだ値の更新はされておらず、次にこの値が入るという予約をしたような状態になります。
    いわゆる非同期で処理されるということです。なので、このイベントハンドラ内ではindexはまだ更新はされていません。(この処理が終われば更新されるため、画面上は、index: 2と表示される)
    そのため、

    const btnClick = () => {
            setindex(index+1);  //indexは1
            setindex(index+1);  //indexは1
            console.log(index);
    };
    

    このようにイベントハンドラ内でセッター関数を2回実行してもindexは、コンソールでは1のままになります。また、画面上はindex: 2と表示されます。この中で更新された値を扱いたいときには、下記のような実装を行います。

    const btnClick = () => {
            setindex(index+1);  //indexは1
            setindex(prev => prev + 1);  //前回更新された値を使って更新することができる
    //アロー関数になっているので、 setindex(prev => { return prev + 1 } )と同じ意味
    //prevに前回セットした値(2)が渡ってきて、その値に対して+1した値がセットされる
    };
    

    そのため、上記のように実装すると画面上はindex: 3となります。この特性を理解していないと、例えば、StateAがこの値になったときにStateBを更新したいという時に一段階のずれが生じます。

    その値のずれが起きる例をコードで実際に書いてみます。おにぎりの具が書いたボタンを押すと、その具が入ったおにぎりの名前が表示されるものを実装してみます(下記図1参照)。

    useStateの罠(おにぎりボタンを使った簡単な実例)

    図1 おにぎりボタンの実装の動き

    const Parents = () => {
    
    const [ onigirinoGu, setOnigirinoGu ] = useState( "明太子" );
    //おにぎりの具を管理するState
    
    const [onigiri, setOnigiri] = useState( "明太子おにぎり" );
    //おにぎりの種類を管理するState
    
    const onigiriSwitch = ( { gu } : string ) => {
          setOnigirinoGu( gu )
          //渡ってきたおにぎりの具をセットする(だが、非同期のため、1回ボタンを押したときは、明太子のまま)
    
           if ( onigirinoGu === "明太子" ) {
                      setOnigiri( "明太子おにぎり" );
          } else if ( onigirinoGu === "鮭" ) {
                        seOnigiri( "鮭おにぎり" );
            } else if ( onigirinoGu === "昆布" ) {
                           setOnigiri( "昆布おにぎり" );
              }
    };
    //非同期のため、OnigininoGuに渡ってきた値をセットしてもOnigininoGu内ではまだ値がセットされていないため、うまく動かない
    
       return (
        <div>
           <span>選んだおにぎりの具:</span>
           <span>{ onigirinoGu }</span>
        </div>
           <span>選んだおにぎり:</span>
           <span>{ onigiri }</span>
        <div>
            <button onClick = { onigiriSwitch('明太子') }>明太子</button>
            <button onClick = { onigiriSwitch('鮭') }></button>
            <button onClick = { onigiriSwitch('昆布') }>昆布</button>
        </div>
       )
    };
    

    例えばボタンを鮭→昆布→明太子→明太子の順で押すと、 鮭ボタン押下→setOnigirinoGu( 鮭 )がセットされるはずが、OnigiriSwitchの関数の中では、まだ初期値の明太子のままであるため、明太子おにぎりと出てしまいます。
    そのあとは、昆布ボタン押下→鮭おにぎり、明太子ボタン押下→昆布おにぎり、明太子ボタン→明太子おにぎりというように、1段階遅れて表示されてしまいます。

    図2 初回鮭ボタン押下時の流れ

    useStateの罠から抜け出すには

    引数で条件処理を書く
    これが一番簡単で良いかと思います。例えば、ユーザーが入力した値を引数に取る場合には、この方法で解決することがよくあります。

    const onigiriSwitch = ( { gu } : string ) => {
          setOnigirinoGu( gu );
          //渡ってきたおにぎりの具をセットする
    
           if ( gu === "明太子" ) {
                      setOnigiri( "明太子おにぎり" );
          } else if ( gu === "鮭" ) {
                        seOnigiri( "鮭おにぎり" );
            } else if ( gu  === "昆布" ) {
                           setOnigiri( "昆布おにぎり" );
              }
         //Stateの変数ではなく、引数の値で分岐処理を書く
    };
    

    分岐処理は渡ってきた値で処理することで、うまく実装ができます。useStateの変数を用いて条件分岐を実装するのは、うまく挙動しない場合がありますので、その点ご留意ください。



    Contextってめっちゃ便利やねん

    先ほどuseStateの話をしていた時に、Stateはコンポーネントごとに保持されるというお話をしたと思います。そのため、ある一つのStateを複数のコンポーネントで更新したいとなると、Propsでバケツリレーのように子コンポーネントから子コンポーネントへと渡さないといけないのかという疑問が出てくるかと思います。しかし!!そんな時に便利なのが、Contextです。私がReactで一番好きなフックです。(本当に便利)

    Cotextの概念

    Contextは、簡単に言うと、このContext内に変数や関数等を定義すると、別コンポーネントで、そのCotext内から定義したものを取り出すことができるというものです。イメージとしては、下記の図のようなイメージになります。またReactでは一般的に、Reactのライブラリ内にあるcreateContextuseContextを用いて実装します。createContextがContextを作成するもので、useContextがそのContextから変数や関数を取り出すときに使用するものです。

    図3 Contextのイメージ
    ただ一つ注意しておいてほしいのは、Contextは自由にどこからでも値などを引っ張ってこれるわけではありません。Contextを定義したところよりも下の構造にいる子コンポーネント内でなら使用できます。
    図4 Contextの注意点

    では、このイメージを持ったまま、コードの書き方を説明していきます。(型を明示的に記載していく形で実装します)


    (フォルダ構成について)

    src/
     ├ Parent.tsx //4つの孫コンポーネントをまとめて表示させる
     ├ FamilyContext.tsx //Contextを実装するコンポーネント
     ├ child1/
      └ GrandChildA.tsx //孫コンポーネントA
     ├ child2/
         ├  GrandChildB.tsx //孫コンポーネントB
         ├ child3/
             └ GrandChildC.tsx //孫コンポーネントC
             └ GrandChildD.tsx //孫コンポーネントD



    FamilyContext.tsx

    import { createContext, useState, SetStateAction } from 'react';
    
    interface FamilyContextType {
       //子コンポーネントに渡したい変数や関数の型をここに書く(Context内にセットしたいもの)
            name: string;
            setName: Dispatch<SetStateAction<string>>; //Stateを定義したい場合
            eventHandler:  () => void;
    };
    
    type Props = {
           children: React.ReactNode;
    //子コンポーネントたちがここに入り、ネストされることで、このContext以下の子コンポーネントでContext内の物を使用できるようになる
    };
    
    export const UseFamilyContext =  createContext<FamilyContextType>({
    //ここでContext内に入れたい変数や関数を定義する。左の値は変数を定義する際の初期値のようなもの
    //ここに定義しないとContext内には含まれない
    //子コンポーネントでContext内から取り出す際(useContextを使用する際)は、このuseFamilyContextから取り出す
            name: '',
            setName: () => {},
            eventHandler: () => {},
    });
    
    export const FamilyContext = ({ children }: Props) => {
            const [name, setName] = useState("kaji");
    
            const eventHandler = () => {
            //処理を記載
            };
    ...
    
     return (
            <UseFamilyContext.Provider
    //おまじない的に.Providerというものを付ける
    //Contextプロバイダーで子コンポーネントをラップすることで、これより下のツリー構造のコンポーネントで使用ができるようになる
                  value = { { 
                           name,
                           setName,
                           eventHandler,
                  } }
             >
                  { children } //子コンポーネントがラップされる
            </UseFamilyContext.Provider>
     )
    }; 
    

    Contextを定義するコンポーネントは上記のように実装します。書くことは多少ありますが、これをセットしてしまうととても便利になります。
    あとは、このコンポーネント(FamilyContext.tsx)をどこかで呼び出してあげないと、使うことができないため、子コンポーネントをまとめているコンポーネントで呼び出してあげます。今回だと、Parent.tsxが最適ですので、そこで呼び出します。

    Parent.tsx

    import { FamilyContext } from './FamilyContext.tsx'
    
    ... 
    
    return (
          <FamilyContext>
                <GrandChildA />
                <GrandChildB />
                <GrandChildC />
                <GrandChildD /> //子コンポーネントを挟む形で書く
          </FamilyContext>
    )
    

    上記のように子コンポーネントをContextのコンポーネントで挟むような形で書きます。ここまでセットすることで、子コンポーネントでContextからの取り出しが可能な状態になります。今回はParent.tsx内で呼び出しましたが、その時の状況で、呼び出す最適な場所は異なります。ですが大抵は、その変数等を使いまわしたい子コンポーネントがまとまっているすぐ上の階層等で呼び出してあげると良いかと思います。では次に、子コンポーネントでContextから変数や関数を取り出すコードを実際に見ていきましょう。

    GrandChildA.tsx

    import { useContext } from 'react';
    import { useFamilyContext };
    
    const GrandChildA = () => {
           const { name, setName, eventHandler } = useContext( useFamilyContext );
      //useContextを使って、useFamilyContextから定義した変数、関数等を取り出します
    }
    

    上記のように実装することで、Context内の変数等を取り出すことができます。値取得は、中括弧を用いて、分割代入を使用して値を取得します。使う側は、これを記載するだけで使用することができます。

    さいごに

    さて、いかがでしたでしょうか。JanaScriptの独特な書き方から、Reactのフックまでを深掘りしたため、かなりボリューミーだったのではないでしょうか。ここまで読んでくださり、本当にありがとうございます。さらに深掘ろうとすれば、まだまだ深掘っていけるReact。これを機にReactの魅力に気づき、Reactユーザーがさらに増えてくれると嬉しいです。
    では、また会う日まで。

    執筆者:嘉地 華香
    最後の晩餐は絶対数の子。痛風予備軍の新人フロントエンドエンジニア。
    ジョジョの推しはジョセフ・ジョースター。幽波紋よりも波紋戦士。