コードロード

エラー討伐

【React】基礎のポイント振り返り

Reactの勉強記録。 こちらのトラハックさんのReact入門基礎編の振り返り

www.youtube.com

コンポーネント

見た目と機能を持つUI部品

大きく2つに分かれる

コンポーネントのいいところ

  • 再利用するため
    • 同じ記述を何度もする必要がない
  • コードの見通しをよくするため
    • 1コンポーネント=1ファイル
    • 責務を明確にする(何のためのパーツなのか)
    • 別ファイルに分ける
  • 変更に強くするため
    • 修正は1箇所

基本的な使い方

//App.jsx(親)
import Article from "./components/Article";

function App() {
    return (
        <div>
            <Article />
        </div>
    )
}

export default App;
// components/Article.jsx(子)
const Article = () => { // アロー関数バージョン
    return <h2>こんにちは</h2>
};

export default Article;

propsでデータを受け渡す

//App.jsx(親)
import Article from "./components/Article";

function App() {
    return (
        <div>
            <Article
                title={'勉強中'}
                content={'propsについて'}
            />
        </div>
    )
}

export default App;
// components/Article.jsx(子)
const Article = () => {
    return (
        <div>
            <h2>{props.title}</h2>
            <p>{props.content}</p>
        </div>
    )
};

export default Article;

#コンポーネントのimportとexport

JavaScriptのモジュール機能

  • プログラムをモジュールという単位に分割する
  • 原則、1ファイル=1モジュール
  • 必要なときに必要なモジュールのみ読み込む

default export(名前なしexport)

// アロー関数
const Title = (props) => {
    return <h2>{props.title}</h2>
};
export default Title;
// 名前付き関数
export default function Title(props) {
    return <h2>{props.title}</h2>
};
  • 推奨されるexport方法
  • 1ファイル=1export
  • 一度宣言したアロー関数をdefault export
  • 名前付き関数宣言と同時にdefault export

default import(名前なしimport)

// Article.jsx(export元)
const Article = (props) => {
    return (
        <div>
            <h2>{props.title}</h2>
            <p>{props.content}</p>
        </div>
    );
};
export default Article;
// App.jsx(import先)
import Article from "./components/Article";

function App() {
    return (
        <Article
            title={'タイトル'}
            content={'中身'}
        />
    );
};
  • default exportしたモジュールをそのまま読み込む

名前付きexport

// helper.js(1ファイルに便利関数が複数入っている)
export const addTax = (price) => {
    return Math.floor(price * 1.1)
}
export const getWild = () => {
    console.log('Get wild and touch')
}

// index.js
export {default as Article} from './Article'
export {default as Content} from './Content'
export {default as Title} from './Title'
// 別名(エイリアス)はなんでもいいけどファイル名にするのが一般的
  • 1ファイルから複数モジュールをexportしたいとき
  • Reactではエントリポイントでよく使う
  • エントリポイントでは別名exportも併用する

名前付きimport

// 直接コンポーネントを読み込むのではなく、エントリポイント(index.jsx)を通してimport
import {Content, Title} from "./index.jsx";

const Article = (props) => {
    return (
        <div>
            <Title title={props.title} />
            <Content content={props.content} />
        </div>
    );
};

export default Article;
  • 1ファイルから複数モジュールを読み込む
  • エントリポイントから複数コンポーネントを読み込む

コンポーネントの状態管理

Hooks

なぜstateを使うのか

  • Reactコンポーネント内の値を書き換えたい
    • ❌:DOMで直接書き換える
    • ⭕️:新しい値を使って再レンダリングさせる
  • Reactコンポーネントが再レンダリングするきっかけは?
    • stateが変更されたとき
    • propsが変更されたとき
      • 子から親にpropsを渡すことはできない
      • 親で更新用の関数を用意しておいて、子でその更新用の関数を実行して、親のpropsを変更させる

useStateの使い方

  1. userStatueによるstateの宣言
const [state, setState] = useState(initialState)
    現在の状態  更新関数                初期値
  1. stateの更新
setState(newState)
更新関数   新しい値
  1. 具体例
const [message, setMessage] = useState('test!!')
const [links, setLinkes] = useState(0)
const [isPublished, setIsPublished] = useState(false)
setIsPublished(true)

stateをpropsに渡す

// Article.jsx
const Article = (props) => {
    const [isPublished, setIsPublished] = useState(false)
    const publishArticle = () => {
        setIsPublished(true)
    }
    return (
        <div>
            <Title title={props.title} />
            <Content content={props.content} />
            <PublishButton isPublished={isPublished} onClick={publishArticle} />
        </div>
    );
};
// PublishButton.jsx
const PublishButton = (props) => {
    return (
        <button onClick={() => props.onClick()}>
            公開状態: {props.isPublished.toString()}
        </button>
    );
}
export default PublishButton;
  • 更新関数はそのままpropsとして渡さず関数化する
  • 関数をpropsに渡す時は注意する

propsへ関数を渡す際の注意点

  • OKな関数の渡し方
    • コールバック関数化関数自体を渡す
<PublishButton isPublished={isPublished} onClick={publishArticle} />

// コールバック関数
<PublishButton isPublished={isPublished} onClick={() => publishArticle()} />
  • NGな関数の渡し方(無限レンダリングが起こる)
    • propsに渡すときに関数を実行しない
// ()をつけて関数を実行してしまっている
<PublishButton isPublished={isPublished} onClick={publishArticle()} />

頻出するuseStateの使い方

引数を使って更新する

import React, {useState} from 'react';

const TextInput = () => {
    const [name, setName] = useState('')

    const handleName = (event) => {
        setName(event.target.value)
    }

    return (
        <input
            onChange={(event) => handelName(event)} // eventはonChangeイベントの戻り値
            type={'text'}
            value={name}
    />
    );
};
export default TextInput;
  • 入力フォームでよく使う
  • onChangeイベントでhandleName関数に渡す
  • handelName関数のパラメータであるeventを更新関数に渡す

prevStateを活用する

import React, {useState} from 'react';

const Counter = () => {
    const [count, setCount] = useState(0)
    const countUp = () => {
        setCount(prevState => prevState + 1)
    }
    const countDown = () => {
        setCount(prevState => prevState - 1)
    }

    return (
        <div>
            <p>現在のカウント数: {count}</p>
            <button onClick={countUp}>up</button>
            <button onClick={countDown}>down</button>
        </div>
    );
};
export default Counter;
  • useStateの更新関数で使える特殊はprevState
  • prevStateは更新前のstate
  • prevStateに変更を加えてreturn

こっちの書き方は良くない!

import React, {useState} from 'react';

const Counter = () => {
    const [count, setCount] = useState(0)
    const countUp = () => {
        setCount(count + 1)
    }
    const countDown = () => {
        setCount(count - 1)
    }

    return (
        <div>
            <p>現在のカウント数: {count}</p>
            <button onClick={countUp}>up</button>
            <button onClick={countDown}>down</button>
        </div>
    );
};
export default Counter;
  • countを直接書き換えている
  • 更新をしますとしてから、実際に更新されるまで多少のタイムラグが起こり、正しく変更されない(非同期で変更するため、実際に更新されるまでは前回の状態を参照している)

ON/OFFを切り替えるボタン

import React, {useState} from 'react';

const ToggleButton = () => {
    const [open, setOpen] = useState(false)
    const toggle = () => {
        setOpen(prevState => !prevState)
    }

    return (
        <button onClieck={toggle}>
            {open ? 'OPEN' : 'CLOSE'}
        </button>
    );
};
export default ToggleButton;
  • prevStateで受け取った値を ! で反転してreturnする
  • 三項演算子によってopenがtrue/falseで表示を切り替える

ライフサイクルと副作用(useEffect)

ライフサイクルとは

  • コンポーネントが生まれてから破棄されるまでの時間の流れ
  • ライフサイクルメソッドを使うと、時点に応じた処理を実行できる
  • Class Component時代は以下の3メソッドが頻出だった
    • componentDidMout()
    • componentDidUpdate()
    • componentWillUnmount()
  • Hooks時代はuseEffectでライフサイクルを表現

3種類のライフサイクル

副作用(effect)フックを使おう

constCounter = () => {
    const [count, setCount] = useState(0)

    const countUp = () => {
        setCount(prevState => prevState + 1)
    }

    const countDown = () => {
        setCount(prevState => prevState - 1)
    }

    // countというstateが変更されるたびに再レンダリング起きて、その度に起こされる処理
    useEffect(() => {
        console.log("Current count is...", count)
    })

    return (
        <div>
            <p>現在のカウント数: {count}</p>
            <button onClick={countUp}>up</button>
            <button onClick={countDown}>down</button>
        </div>
    );
};

第二引数の依存関係を理解する

// 毎回実行される
useEffect(() => {
    console.log("Current count is...", count)
})

// 初回レンダリング後のみ実行される
useEffect(() => {
    console.log("Current count is...", count)
}, [])

// triggerが変更されるたびに実行される
useEffect(() => {
    console.log("Current count is...", count)
}, [trigger])

// trigger1かtrigger2が変更されるたびに実行される
useEffect(() => {
    console.log("Current count is...", count)
}, [trigger1, trigger2])
  • useEffectの第二引数には配列を渡すことが可能
  • 第二引数はdeps(dependencies)と呼ばれ、副作用が引き起こされるどうかの依存関係となる

クリーンアップを理解する

const ToggleButton = () => {
    const [open, setOpen] = useState(false)
    const toggle = () => {
        setOpen(prevState => !prevState)
    }

    useEffect(() => {
        console.log("Current state is", open)
        if (open) {
            console.log("Subscribe database...")
        }
        return () => {
            console.log("Unsbscribe database!")
        }
    })

    return (
        <button onClieck={toggle}>
            {open ? 'OPEN' : 'CLOSE'}
        </button>
    );
};
  • コンポーネントないで外部データベースを購読したい
  • useEffectないで購読処理を呼び出す
  • 必要無くなったらクリーンアップ関数を使って掃除する
    • useEffectの中でreturnする

useEffect内でAPIを呼び出そう

useEffectのユースケース

  • APIやデータベースから非同期通信でデータを取得(fetch)する
  • 特定の値が変わったらデータを再取得(refetch)する

useEffect内で非同期通信

useEffect(() => {
    fetch(`https://api.github.com/users/${id}`)
        .then(res => res.json())
        .then(data => {
            console.log(data)
            setName(data.name)
        })
        .catch(error => {
            console.error(error)
        })
}, [id])
  • 初回レンダリング後に呼び出される
  • 第二引数に指定した値が変わるたびに再度呼び出される
  • 取得した値をuseStateの更新関数に渡す