Open24

基本的なJS/Reactメモ

ゆーとゆーと

「モダンなJavaScriptの基礎から始める挫折しないためのReact入門」

ゆーとゆーと

モジュールバンドラー

複数のjs(css/image)ファイルを1つにまとめてくれる。
→ 開発時はファイルを分けて再利用性とかを向上して、本番時は一つにファイルにまとめて実行してくれる。
Webpackとか。

トランスパイラ

新しいJavaScriptの記法を、古い記法に変換してくれる。
ES6に対応してくれていないブラウザ対応とか。
BABELとか。
Next.jsではSWCがデフォルトで組み込まれている。


→ 「効率よく開発して、本番実行時はいい感じに変換」って機能。
→ Next.jsとか最近のフレームワークを使用すれば、この辺は内部的にうまいことしてくれるので、あまり考えなくて良くはなっている場合も。

ゆーとゆーと

SPA

Single Page Application

SPAは、ユーザーの操作によりページ全体を再読み込みすることなく、必要なデータのみを動的に更新するWebアプリケーションの形式。

初回アクセス時に必要なHTML、JavaScript、CSSを一度に読み込み、その後のページ遷移やデータの更新はJavaScriptが担います。

  • ページ遷移毎のちらつきが無くなる
  • サーバーとの通信量を減らし、高速なユーザー体験を提供
  • コンポーネントの分割が容易
  • etc.
ゆーとゆーと

const定義

プリミティブ型は値自体はイミュータブル。変更不可。
Object型やListはミュータブル。Object全体(そのもの)は変更不可だが、中身の要素は変更できるので。
→ 不変にするならfreezeとかでイミュータブルにするとかの対応必要。

Reactでは基本はconst定義。動的な値はstateで管理することが多い。


テンプレート文字列

バッククォート: `` で囲まれた文字列はテンプレート文字列${var}が使用可能


アロー関数と無名関数

アロー関数は単一の戻り値なら一行で返せる。
→ Objectも一行で返せる。
const func = (num1, num2) => ({Object定義})

通常の関数宣言
関数が明確、スコープがグローバルなら再利用可能。

無名関数
一度だけの関数処理などに有効。
関数宣言にして意図しない呼び出しなど防ぐ役割も。


分割代入

const myProfile = {
    name: 'John',
    age: 35,
}
// key名を指定
const {name, age} = myProfile;
const myProfile = ['John', 35];
// 順番に取り出すので変数名は自由に決めれる
const [name, age] = myProfile;

console.log(name);
console.log(age);

objectの場合はkeyの指定で順番は関係ないが、Listの場合は格納順に取り出すことになる。


デフォルト値

// 引数デフォルト値
const sayHello = (name = 'guest') => console.log(`Hello, ${name}!`);

// 分割代入のデフォルト値
const myProfile = {
  age: 30
}
const {age, name = "hoge"} = myProfile;


オブジェクトの省略記法

const name = "hoge";
const age = 30;

// keyと設定値が同じ命名なら省略可能
const myProfile = {name, age};
ゆーとゆーと

スプレッド構文

配列やObjectにスプレッド構文が付いてたら、「順番にまとめて処理する」イメージ。

const arr = [1, 2]
console.log(...arr)

// result
1
2
const arr2 = [1, 2, 3, 4, 5];
const [a, b, ...arr3] = arr2;
console.log(a);
console.log(b);
console.log(arr3);

// result
1
2
[3,4,5]
// コピー+結合
const arr4 = [1, 2, 3];
const arr5 = [4, 5, 6];
const arr6 = [...arr4, ...arr5];

// 以下のようにコピーにすると、参照先もコピーされるので注意。
const arr6 = arr4;

// memo
// スプレッド構文でのコピー:新しい配列の作成となり、異なるメモリアドレスを持つ。
// ただのコピー:同じ配列を参照することになり、どちらかの配列で要素を変更するともう一方にもその変更が反映される。
ゆーとゆーと

map

for文の省略版。

const arr = [1,2,3];
// for文
for (let index = 0; index < arr.length; index++) {
    console.log(arr[index]);
}

// map
const arr22 = arr.map((item) => {
    return item;
});
arr.map((item) => {
    console.log(item);
});


filter

条件に一致する値のみを返却。

const arr = [1,2,3,4,5];

const arr_filter = arr.filter((item) => {
    // return の後にfileterの条件式を書く
    return item % 2 === 1;
});
console.log(arr_filter);

// result
// [1, 3, 5]

for文のようにindexが欲しい場合は、第二引数にindexを指定できる。

const arr_filter = arr.filter((item, index) => ...省略
ゆーとゆーと

truthy, falsy

JavaScript において、真値 (truthy) は論理値のコンテキストに現れた時に true とみなされる値のことです。偽値 (falsy) として定義された値 (つまり、false, 0, -0, 0n, "", null, undefined, NaN) を除くすべての値は真値です。

//  || : 左側が truthy の時、その時点で返却する
const num = undefined;
const fee = num || "未設定です";
console.log(fee);

// && : 左側が falsy の時、その時点で返却する
const num = "abc";
const fee = num && "設定されました";
console.log(fee);
ゆーとゆーと
<StrictMode>  // linterみたいなもの。基本つけるで良さそう。
  <App />
<StrictMode>
const App = () => {
  return (
-     // <h1>Hello, world!</h1>
-     // <p>This is an example</p>

// 関数コンポーネントの戻り値は一つのタグでくくる必要がある。
    <div>
      <h1>Hello, world!</h1>
      <p>This is an example</p>
    </div>

// 現在は空タグでそれぞれのタグを表示できる。
// divがいいかどうかはケースバイケースで使用する。
-   // <React.Fragment> ← 少し古い書き方。空タグと同じ効果。
    <>
      <h1>Hello, world!</h1>
      <p>This is an example</p>
    </>


  );
}
ゆーとゆーと

関数コンポーネントは.jsでも.jsxでも定義できる。
が、関数コンポーネントの定義はjs処理関数と見分けるためにも.jsxにした方が良さそう。ファイルのiconもreactのiconになるし。

関数コンポーネント名は大文字始まり。
ファイル名は決まり無し。

関数コンポーネントはhtmlタグを返す。→ htmlコード内でjsを使用する場合は全て{ }をつけて呼び出す。
styleを当てる場合も{ }をつけて(オブジェクトで)返す。

export const App = () => {
  const onClickButton = () => alert();;
  const contentStyle = {
    color: "red",
    fontSize: "18px",
    margin: 10,
  };

  return (
    <>
      <h1>Hello, world!</h1>
      <p style={contentStyle}>This is an example</p>
      <button onClick={onClickButton}>Click</button>
    </>
  );
}
ゆーとゆーと

props

下記のようなコンポーネント定義だと、Helloはprops.childrenで取得できる。

<Hoge color='blue'>Hello</Hoge>

propsの取り方

どの方法がいいかはケースバイケース。

// ①propsから直接プロパティを取得。propsの値だと明示的。
export const ColorfulMessages = (props) => {
    const contentStyle = {
        color: props.color,
        fontSize: "18px",
    };

    return <p style={contentStyle}>{props.children}</p>;
};
// ②propsを分割代入で使用
export const Hoge = (props) => {
    const { color, children } = props;
    const contentStyle = {
  // 命名が一緒なのでプロパティ名省略できる
        color,
        fontSize: "18px",
    };

    return <p style={contentStyle}>{children}</p>;
};
// ③propsの引数定義部分で分割代入するパターン
export const ColorfulMessages = ({ color, children }) => {
    const contentStyle = {
        color,
        fontSize: "18px",
    };

    return <p style={contentStyle}>{children}</p>;
};
ゆーとゆーと

useState

import { useState } from 'react';

export const App = () => {
// 第一引数:Stateを保持する変数名。
// 第二引数:Stateを更新する関数名。慣例で<set変数名>。
 const [num,setNum] = useState(0);

下記二つはstateの更新挙動が違う事注意。

  const onClickCountUp = () => {
    setNum((prevNum) => prevNum + 1);
    setNum((prevNum) => prevNum + 1);
  };

  const onClickCountUp = () => {
    setNum(num + 1);
    setNum(num + 1);
  };

ゆーとゆーと

useEffect

useEffectの乱用は可読性が悪いので、本当に必要なのかは考えて使用する。

export const App = () => {
  const [num, setNum] = useState(0);

  // ①第二引数に何も指定しないと、再レンダリングの度に実行される
  useEffect(() => {
    // ...処理
    console.log('useEffect!');
  });

  // ②第二引数にから配列を指定すると、初回レンダリング時のみ実行される
  useEffect(() => {
    // ...処理
    console.log('useEffect!');
  }, []);

  // ③第二引数に変数を指定すると、「初回レンダリング時+指定した変数に変更があった時」に実行される
  useEffect(() => {
    // ...処理
    console.log('useEffect!');
  }, [num]);

  return (
ゆーとゆーと
// named import: 括弧をつけることで、明示的に使用するコンポーネントを指定している。
// default import: 括弧をつけない場合は、default export されたコンポーネントを自由な命名で使用できる。
// defaultだとコンポーネント名を自由に決めれてしまうので、ややこしいため基本的にはnamed importで良さそう。
import { App } from "./App";
ゆーとゆーと
  • コンポーネント定義内でのstyle割り当ては、classではなくclassName

  • onClickなどのEventには引数がデフォルトで付与されている。
    const onChangeTodoText = (event) => setTodoText(event.target.value);

  • .map直下のタグにはkeyを指定する。
    <li key={todo}>

  • onClickなどのEventにはJS関数を渡す→関数そのもの(関数定義)を渡す必要があること注意。
    <button onClick={onClickDelete}> // OK
    <button onClick={onClickDelete(index)}> // NG。これだと関数定義ではなく関数の実行となる。
    <button onClick={() => onClickDelete(index)}> // OK。引数指定したい時は関数定義にする。

  • propsに関数を渡すのか、コンポーネント内で関数定義するかはケースバイケース。
    state変更処理をする場合は、関数として渡す方が良い場合が多いかも。

ゆーとゆーと

コンポーネント内でif文みたいに動的に表示。

      {incompleteTodos.length >= 5 && (
        <p style={{ color: "red" }}>hogehoge</p>
      )}


引数のkey名がコンポーネント定義のkey名と違っても(誤字っても)ワーニング出ないから気づかないのなんとかならないか、拡張機能とかで

export const InputTodo = (props) => {
    const { todoText } = props;

// =================

      <InputTodo
       // key名が誤字ってても知らせてくれない
        todoText={todoText}
      />

ゆーとゆーと

「Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版」

ゆーとゆーと

再レンダリングタイミング

  • stateが更新されたコンポーネント
  • propsが更新されたコンポーネント
  • 再レンダリングされたコンポーネント配下の子コンポーネント
ゆーとゆーと
  • memo: コンポーネント定義に付与すると、propsの変更以外は再レンダリングしなくなる。

    • 親コンポーネントの変更が子コンポーネントにpropsの変更以外は影響しなくなる。
    • 基本的にはコンポーネント定義にはmemoをつけておいた方が良さそう。
    export const ComponentName = memo((props) => { 
      const {variable} = props;
      ...省略 
    });
    
  • useCallback: 関数をメモ化(memoization)を行い、その関数の参照が再レンダリングされても参照先が変わらないようにする。これによって子コンポーネントが不必要に再レンダリングされることを防ぐ。

    • 上記の理由によってuseCallbackを使用しない場合は、propsに渡される関数は新しい関数とみなされ、memoをつけていても再レンダリングされる。
    // 子コンポーネント
    export const ComponentName = memo((props) => { 
      const {variableName, functionName} = props;
      ...省略 
    });
    
    // =================
    // 親コンポーネント
    // NG: 毎回生成されて新しい関数としてpropsに渡されてしまう。
    const onClickClose = () => setIsOpen(false);
    
    // OK: 以下の定義だとレンダリング初回しか生成されないため、再レンダリングされても参照は変わらない。
    const onClickClose = useCallback(() =>
    setIsOpen(false), [ ]);
    
    <ComponentName  onClickClose={onClickClose}  />
    
    
  • useMemo: useCallbackの変数版のイメージ。
    関数と違って変数の値の参照先が変わる事は無いので、propsに影響を与える事はないが、計算が重い処理や結果が頻繁に変わらない処理の時に使用するイメージ。

ゆーとゆーと

React Router

import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/page1">Page1</Link>
        <Link to="/page2">Page2</Link>

        {/* pathによって表示するコンポーネントをSwitchする */}
        <Switch>

          {/* exact: pathの完全一致かどうかを判定 */}
          <Route exact path="/">
            <Home />
          </Route>

          <Route path="/page1">
            <Page1 />
          </Route>

          {/* renderを使用すると記載省略できる */}
          <Route exact path="/page2" render={() => <Page2 />} />

        </Switch>
      </div>
    </BrowserRouter>
  );
}

ネストされたルーティング

ネストする際も基本的には上記の記載方法をネストしていく感じ。
ネスト部分は別途ファイルに分けると良さそう。

export const Router = () => {
    return (
        <Switch>
            <Route Route exact path="/" >
                <Home />
            </Route >

            <Route
                path="/page1"
                render={({ match: { url } }) => (
                    <Switch>
                        {page1Routes.map((route) => (
                            console.log(route),
                            <Route
                                key={route.path}
                                exact={route.exact}
                                path={`${url}${route.path}`}
                                render={() => route.children}
                            />
                        ))}
                    </Switch>
                )}
            />

            <Route exact path="/page2" render={() => <Page2 />} />
        </Switch >

    )
};
// ===================
export const page1Routes = [
    {
        path: "/",
        exact: true,
        children: <Page1 />
    },
    {
        path: "/detailA",
        exact: false,
        children: <Page1DetailA />
    },
    {
        path: "/detailB",
        exact: false,
        children: <Page1DetailB />
    }

];


詰まったポイント

  • renderを使用するなら小要素は指定しない。
    小要素を指定しているとrenderは無視される。
          <Route
            path="/page1"
            render={(props) => (
              <Switch>
                  ...省略
              </Switch>
            )}
          >
            {/* </ComponentName> // このように小要素を指定するとrender部分は無視される。 */}
          </Route>
  • <ComponentName/><ComponentName></ComponentName>の違い。
    構文的な違い。機能は基本的には一緒。
    • <ComponentName/>:子要素がないコンポーネントによく使用される。より簡潔な書き方。
    • <ComponentName></ComponentName>:子要素を含むコンポーネントによく使用される。
ゆーとゆーと

URLパラメータ

usePrams: 現在のURLパラメータを動的に取得できる。

<Route path ="/user/:id" ... <Component />

// =================
export const Component = () => {
  const { id } = useParams();

クエリパラメータ

useLocation: 現在のURLの情報を取得できる。

export const Component = () => {
    const location = useLocation();
    console.log(location);

// ======== 出力 =========
hash: ""
key: "hrrd1w"
pathname: "/page2/100"
search: "?name=hogehoge"
state: undefined

stateをパラメータとして渡す際のLink設定は以下。

- <Link to="/page1/detailA">DetailA</Link>
+ <Link to={{ pathname: "/page1/detailA", state: "hogehoge" }}>DetailA</Link>

URLSearchParams

URLのクエリストリング(?の後に続く部分)を操作するためのメソッドを提供するクラス。

export const UrlParameter = () => {
    // 分割代入でserchを取得
    const { search } = useLocation();

    const query = new URLSearchParams(search);
    console.log(query);

// ======== 出力 =========
[[Prototype]]: URLSearchParams
    append: ƒ append()
    delete: ƒ delete()
    entries: ƒ entries()
    forEach: ƒ forEach()
    get: ƒ ()
    getAll: ƒ getAll()
    has: ƒ has()
    keys: ƒ keys()
    set: ƒ ()
    size: (...)
    sort: ƒ sort()
    toString: ƒ toString()
    values: ƒ values()

useHistory()

コンポーネント内でのブラウザ操作(履歴操作)ができる。

export const Page1 = () => {
    const history = useHistory();
    const onClickDetailA = () => history.push("/page1/detailA");
    // ...
            <button onClick={onClickDetailA}>DetailA</button>

// useHistoryから提供されてるメソッド例
- block
- createHref
- go
- goBack
- goForward
- length
- listen
- location
- push
- replace
ゆーとゆーと

useContext

Provier定義例
export const TestContext = createContext({});

export const TestProvider = (props) => {
    const { children } = props;
    const [isTestInfo, setIsTestInfo] = useState(false);

    return (
        <TestContext.Provider value={{ isTestInfo, setIsTestInfo }}>
            {children}
        </TestContext.Provider>
    )
}
Globalに使用したいComponentに対して囲む
      <TestProvider >
        <Router />
      </TestProvider>
Providerで囲んだ配下ではGlobalに使用できる
export const Component = () => {
    const context = useContext(TestContext);
    const onClickChange = () => context.setIsTestInfo(!context.isTestInfo);

    return (
        <div>
            {context.isTestInfo ? <p>true</p> : <p>false</p>}
            <button onClick={onClickChange}>State Change</button>
        </div>
    )
};
  • 1画面に複数のComponentを組み合わせている場合、再レンダリング不要なComponentにはmemoをつける。
  • どこまでの範囲をGlobal参照範囲かも考えて使用する。
ゆーとゆーと

TypeScript

stateの型定義

type TodoType = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

function App() {
  // state
  const [todos, setTodos] = useState<Array<TodoType>>([]);

propsの型定義

型定義すると、呼び出し元で引数の型が違うとエラー出てくれるようになる。→jsとは違うとこ。

type TodoType = {
    title: string;
    userId: number;
   // オプション引数の場合は?つける
    completed?: boolean;
}

export const Todo = (props: TodoType) => {
    const { title, userId, completed = true } = props;

型定義の共通化

基本はtypesフォルダとかに型定義をまとめる。
呼び出し時に、不要なプロパティがある場合は、いい感じにできるメソッドあり。

export type TodoType = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};
// ================
// Omit: 特定のプロパティを除いて使用
export const Todo = (props: Omit<TodoType, "id">) => {
    // ...省略

// Pick: 特定のプロパティを指定して使用
export const Todo = (props: Pick<TodoType, "userId" | "title" | "completed">) => {
    // ...省略

コンポーネントの型定義

コンポーネントの型定義まですると、以下のようになる。
※react17の暗黙的なchildrenプロパティ注意。

import { FC } from "react";

export const Todo: FC<Omit<TodoType, "id">> = (props) => 
    const { title, userId, completed = true } = props;

オプショナルチェイニング

dartのnull saftyみたいなやつ。

- ${props.user.id}  // idがoptionalで必須で無い場合、存在しないのでエラーになる。
+ ${props.user.id?}  // idがoptionalで必須で無い場合、nullを返す。

ゆーとゆーと

ライブラリの型定義

ライブラリに型定義が提供されている場合は、
*.d.tsがあるか、package.jsonにtypingの定義があるはず。
記載がない場合は別途型定義ライブラリ(@types/packageNameなど)をinstallする必要あり。

ゆーとゆーと

カスタムhook

  • 再利用性
  • ロジック部分とUI部分との分離
  • 機能群の独立化、テストしやすさ