Open60

Udemy & 本 de お勉強

ろろろろろろ

Promise

resolve:解決したよの意味。成功したときに呼ばれる。
reject:拒絶したよの意味。失敗したときに呼ばれる。

  • Promise
    約束事を決める。
    約束守れたらresolve。守れなかったらreject
  • Promise.all
    引数に配列を持つ。
    指定した引数すべてのPromiseを実行する。
    たとえば、Promise.allの中で30回のfetchをするコードを書いたとする。
    30回分全部のfetchが完了するか、1つでもrejectになった時点で終了する。
ろろろろろろ

コンソールでみると・・

  • pending
    保留状態であることを示している。

    pending

    成功時
ろろろろろろ

エラー

Warning: Each child in a list should have a unique "key" prop.
各項目の配列にユニークなkeyを設定してね。の意味。
map関数を使ってJSX要素を記述する時には必要になる。
JSX要素が変更、削除された場合でもReactがkeyを使って要素を追えるようにしておきたい。
参考:keeping-list-items-in-order-with-key

ろろろろろろ

.gitignore

.gitignoreに記載されたファイル、フォルダはgit add実行時にインデックスに追加されなくなる。
#はコメント。
行の先頭に/があれば、.gitignoreが配置されたディレクトリが基準となって除外設定
ディレクトリ配下すべてを除外設定したい場合は[ディレクトリ名]/とする。
*および?のワイルドカードや[ ]を使用した正規表現も可能。

# どのディレクトリに関わらず、sample.txt を除外
sample.memo
# .gitignoreが配置されたディレクトリ内の sample.txt を除外
/sampe.txt
# sample-1 が見つかるパスを除外
sample-1
# sampleDirectory配下のすべてのパスを除外する。
sampleDirectory/
# sample.js や sample.json など sa から始まるファイルを除外設定
sa*
# すべてのファイルを除外設定
*
ろろろろろろ

タグの命名時の注意

googleSEOなどに反映されやすくなるために、明確なタグをつけておくとよい。

ろろろろろろ

CSS

align-items:centerは交差軸を中心にそろえる。
--□□□□--- みたいな□の要素の中心にそろえられる。
align-items:flex-startは上にそろえる。交差軸ではなくてっぺんから。
align-items:flex-endは下にそろえる。
flex-direction:columnは横軸を中央にそろえる。
下記のように書くと横軸の中央線と縦軸の交差点でそろえることができる。

  flex-direction: column;
  align-items: flex;

ろろろろろろ

ボタンをいい感じにする。

.sampleButton {
  padding: 10px 0;
  background-color: #3d77a5;
  border: none;
  box-shadow: 6px 8px 18px -3px #777777;
  color: white;
  border-radius: 5px;
  cursor: pointer;
  transition: all 0.3s;
}

box-shadowは影をつける。今っぽいボタンになるぞ。
cursor=カーソル。pointerでマウスカーソルが当たると指の形になるアレ。
transitionに秒数を指定すると、指定時間かけてホバーする。
マウスカーソルがホバーされたときはに沈みこみを発生させる。

.sampleButton:hover{
  transform: translateY(5px);
  box-shadow: none;
}

transformでY軸にマイナスを指定する。そうするとY軸に対して指定した分だけ沈み込みする。
bos-shadow:noneにしたのは視覚的にも沈んでいるようにするため。

ろろろろろろ

marign


オレンジの部分がmarginとなる。
margin: 0pxとすると消せるよ。

ろろろろろろ

firebase

JavaScript による Google を使用した認証

signInWithPopup(auth, provider).then((result) => {});

もはやこれだけでログインできるみたい。(このままだとリロードしたらログイン情報消えるけど。)

ろろろろろろ

ログインしているユーザーの名前を取得したい時は

import { getAuth, GoogleAuthProvider } from "firebase/auth";
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
(...のこりは略)
const userName = auth.currentUser.displayName;
ろろろろろろ

FirebaseError

Missing or insufficient permissions
Missing or insufficient permissions
insufficient:不十分な。insufficient permissions=不十分な許可ですよ。の意味。
firebaseのルールをみるとwrite:falseとなっている。

ここをtrueにすれば今回は解決。

ろろろろろろ

React-router-dom

<Router></Router>で囲まれた要素にpathを追加してあげるとルーティングできる。
自由にURLの指定もOK。

import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/page1" element={<Page1/>}></Route>
        <Route path="/page1" element={<Page2 />}></Route>
        <Route path="/page1" element={<Page2 />}></Route>
      </Routes>
    </Router>
  );
}
export default App;
ろろろろろろ

URLを変更したい時は・・・

useNagigate関数を使う。レンダリング時に現在のURLを変更[1]する。
props(引数)として移動したURLを指定する。

import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/");
脚注
  1. React Router(Navigateページ) ↩︎

ろろろろろろ

import時の{ }有無


{ }があると名前付きインポートとなる。
1つのモジュール内に1つだけエクスポートできるexport default [sample]をインポートしたい時は、{ }なしのデフォルトインポートを使用する。
1つのモジュール内に複数のエクスポートをもつファイルからインポートしたい場合は、{ }付きの名前付きインポートを使用する。
今回のaddDocは名前付きインポートで使用する。理由はfirebase側でaddDocの名指しでexportされているから。

ろろろろろろ

ちなみに・・・

他の人のコードをみるといろんなところでindex.js,index.tsをみる。
これはエントリーポイントと呼ばれ、一括でインポートできるようになる。
(コンポーネントが大量にあるとimportだらけになってとてもつらい思いをした・・・)

componentフォルダ
Component_1.js;
Component_2.js;
...
Component_300.js;

各コンポーネントをインポートしようとするとファイル毎に行頭から↓のようにimportだけで300行も書く羽目に・・・
(実際に300行かいた。びっくりするくらい修正めんどくさかったし読みにくかった。)

main.js
import Compornent_1 from "/component_1"
import Compornent_2 from "/component_2"
...
import Compornent_300 from "/component_300"

そこでエントリーポイントのindex.jsまたはindex.tsにまとめてしまう。

index.js
export Compornent_1 from "/component_1";
...
export Compornent_300 from "/component_300";

これで実際に使用したいファイルでimport * from "./index.js";としてしまえば、300個のコンポーネントを自由に使えちゃう!
一度300行のインポートをしてしまえばあとはindex.jsをインポートするだけ。いちいちファイル毎に書く必要ななくなる。楽だね。もっと早く知りたかった。

ろろろろろろ

階層の深い構造を処理するとき

data()関数を指定すると、楽に中身を持ってこれるよ。
スプレッド構文(...list),区切りでプロパティを指定すると属性を追加することができる。

sample:{
id,
name
}

const test = ({...sampe.data(), telNo:sample.telNo})とすると・・・

sample:{
id,
name,
telNo
}
ろろろろろろ

日付を表示

Date.now()でUTC1970年1月1日0時0分0秒から現在までの経過時間をミリ秒単位で取得できる。

const nowDate = Date.now();
console.log(nowDate);
>>> 1689247600796

人間でもわかるようにするにはインスタンス化する。

const nowDate = Date.now();
console.log(new Date(nowDate));
>>> Thu Jul 13 2023 20:33:44 GMT+0900 (日本標準時)

よく見るyyyy/mm/ddにしたい(しかも日本時間で)!ってときはtoLocalDateString()メソッドを使う。

const nowDate = Date.now();
console.log(new Date(nowDate).toLocaleDateString("ja-JP"));
>>> 2023/07/13

ほんでさらに時刻を取得したい!ってときはtoLocalDateString()メソッドの第二引数(option)を設定してあげればいい。(2-digit二桁の意味)

const nowDate = Date.now();
console.log(new Date(nowDate).toLocaleDateString("ja-JP",{
 hour:"2-digit",minute:"2-digit"
}));
>>> 2023/7/13 20:43

めっちゃ楽じゃん。これは苦労した覚えがある。

ろろろろろろ

クリックイベント等を呼び足すときの違い

以下の書き方だとページをリロードしたときにonClickHandler()が発火される。
デフォルトで絶対発火されることになる。

<button onClick={onClickHandler()}>ボタン1</button>

onClickHandler()に対して引数を取りたい時や、クリックされた時だけ発火するようにしたい場合は以下のようにアロー関数で記述する。

<button onClick={() => onClickHandler()}>ボタン2</button>
ろろろろろろ

filter

とある配列から指定した条件がTrueのデータのみを抽出して新たな配列を生成する。

const saibaiLists=[
  {category:"果物",name:"みかん"},
  {category:"野菜",name:"すいか"},
  {category:"果物",name:"ぶどう"},
  {category:"野菜",name:"いちご"},
]

こんな配列があったとする。この配列からcategory="果物"のデータを抽出したい。

const kudamonoLists = saibaiLists.filter((saibaiList)  => saibaiList.category === "果物");

kudamonoListsは以下のようになる。

ろろろろろろ

動的キー

[ ]囲みでkeyを指定すると、自分で設定した変数をkeyとして与えることができる。

const sampleKey1 = "ThisIsKey"
const sampleObj1 = {
[sampleKey1]:"ThisIsValue"
}
const sampleKey2 = "ThisIsKeySecond!!"
const sampleObj2 = {
[sampleKey2]:"ThisIsValue"
}

ろろろろろろ

再レンダリング

どんな時に再レンダリングするの?

  • stateが更新されたコンポーネントは再レンダリング
  • propsが変更されたコンポーネントは再レンダリング
  • 再レンダリングされたコンポーネント配下の子要素は再レンダリング

    この時、Bstateが更新されたら、レンダリングされるのはBCになる。
今の状態
App.jsx
import { useState } from "react";
import "./App.css";
import { ChildArea } from "./ChildArea";

function App() {
  console.log("app");
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => {
    setOpen(!open);
  };

  return (
    <div>
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
      <ChildArea open={open} />
    </div>
  );
}

export default App;
ChildArea.jsx
const style = {
  width: "100%",
  height: "200px",
  backgroundColor: "khaki",
};

export const ChildArea = (props) => {
  const { open } = props;
  console.log("ChildAreaがレンダリングされた");

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log("...");
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
};

ろろろろろろ

どう対策するの?

1 memo化
Propsの値をチェックして再レンダリングを判定。
同じpropsで再レンダリングが行われるときは、前回の再レンダリング結果を再利用するため、子コンポーネントを再レンダリングしない。
複数の要素から成り立つコンポーネントや肥大化が予想されるコンポーネントはmemo化するといい。

コード
ChildArea.jsx
import { memo } from "react";

const style = {
  width: "100%",
  height: "200px",
  backgroundColor: "khaki",
};

export const ChildArea = memo((props) => {
  const { open } = props;
  console.log("ChildAreaがレンダリングされた");

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log("...");
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
});

しかし、これでは以下のようにpropsに関数を渡すと子要素も再レンダリングされてしまう。

App.jsx
  const [open, setOpen] = useState(false);
   const onClickClose = () => setOpen(false);
  return (
    <div>
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
+      <ChildArea open={open} onClickClose={onClickClose} />
    </div>
  );
ChildArea.jsx
  return (
    <>
      {open ? (
        <div style={style}>
          <p>子コンポーネント</p>
+        <button onClick={onClickClose}>閉じる</button>
        </div>
      ) : null}
    </>
  );
ろろろろろろ

2 useCallback(関数のmemo化)
propsに渡す関数が前回実行された関数と変更なければ前回のレンダリングを再利用する。
useEffect同様に第二引数に監視する値をセットする。
(第二引数に変更があったときにCallbackする)

App.jsx
- const onClickClose = () => setOpen(false), [setOpen]; 
+ const onClickClose = useCallback(() => setOpen(false), [setOpen]);
ろろろろろろ

3 useMemo(変数のメモ化)
変数をメモ化する。
useEffect同様に第二引数に監視する値をセットする。から配列を指定すると最初に読み込まれた一回だけ実行される。
変数に設定する処理が複雑になるときに使用すると、再レンダリングの回数が減るのでgood。

App.jsx
+ const temp = useMemo(() => 1 + 3, []);
+ console.log(temp);
ろろろろろろ

404ページの作成(React-Router v5)

404ページ自体はメッセージとホームへのリンクのみ。

Page404.jsx
import { Link } from "react-router-dom";

export const Page404 = () => {
  return (
    <div>
      <h1>ページが見つかりません。</h1>
      <Link to="/">TOPに戻る</Link>
    </div>
  );
};

どのルートにも一致しなかった場合に404ページに遷移させる。Routerコンポーネントの最後を追記。
path="*"べて一致したらの意味。
URLが変更されたら<Switch>タグのpathを順番にレンダリングしていき、最後まで来たとき(<Route path="*">)はどれにも一致していないので<Page404 />をレンダリングする。

Router.jsx
+      <Route path="*">
+        <Page404 />
+      </Route>
Router.jsx全体
Router.jsx
import { Switch, Route } from "react-router-dom";
import { Home } from "../Home";
import { Page404 } from "../Page404";
import { Page1Routes } from "./Page1Routes";
import { Page2Routes } from "./Page2Routes";

export const Router = () => {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route
        path="/page1"
        render={({ match: { url } }) => (
          <Switch>
            {Page1Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />

      <Route
        path="/page2"
        render={({ match: { url } }) => (
          <Switch>
            {Page2Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route path="*">
        <Page404 />
      </Route>
    </Switch>
  );
};
ろろろろろろ

共通のデザインを適用する(styled-component)

デザインだけをもったコンポーネントをエクスポートすることによって何度も書かずともデザインを適用できる。

BaseButton.jsx
import styled from "styled-components";

export const BaseButton = styled.button`
  color: #fff;
  padding: 6px 24px;
  border: none;
  border-radius: 9999px;
  outline: none;
  &:hover {
    cursor: pointer;
    opacity: 0.8;
  }
`;

BaseButtonのデザインを適用したいコンポーネントでインポートする。
さらにデザインを追加したい場合はインポートしたBaseButtonにデザインを追加してあげるイメージ。

PrimaryButton.jsx
import React from "react";
import styled from "styled-components";
+ import { BaseButton } from "./BaseButton";

export const PrimaryButton = (props) => {
  const { children } = props;
  return <SButton>{children}</SButton>;
};

+ const SButton = styled(BaseButton)`
+   background-color: #40514e;
+   }
`;
SecondaryButton.jsx
import React from "react";
import styled from "styled-components";
+ import { BaseButton } from "./BaseButton";

export const SecondaryButton = (props) => {
  const { children } = props;
  return <SButton>{children}</SButton>;
};

+ const SButton = styled(BaseButton)`
+  background-color: #11999e;
+   }
`;

ろろろろろろ

グローバルなstate管理

1 useContext
createContextはReactで用意されているフック。Providerを使って、ほかのコンポーネントにデータを提供する。
Providerで囲ったコンポーネントでは、Contextのvalueを呼び出せる。

UserProvider.jsx
import React, { createContext } from "react";

export const UserContext = createContext({});

export const UserProvider = (props) => {
  const { children } = props;
  const contextName = "名前だよ!!!!";
  return (
    <UserContext.Provider value={{ contextName }}>
      {children}
    </UserContext.Provider>
  );
};
Appjsx
function App() {
  return (
    <UserProvider>
      <Router2 />
    </UserProvider>
  );
}

今回の例では、<Router2>UserProvidercontextName(名前だよ!!!!) を呼び出すことができる。
実際に使うときは、useContextをインポートする。
どのコンテキストをつかうか?というのはUserProvider.jsxでエクスポートしているUserContext を指定する。

実際に使うコンポーネント.jsx
import React, { useContext } from "react";
const context = useContext(UserContext);
console.log(context)
→ 名前だよ!!!!

実際には変数を使うことが多い。その場合はvalueに使いたい変数を渡す。
以下のように書いてあげることによって、UserContext.Provider配下でstate‘もsetState‘を呼び出すことができる。

UserProvider.jsx
export const UserProvider = (props) => {
  const { children } = props;

+  const [userInfo, setUserInfo] = useState(null);
-  const contextName = "名前だよ!!!!";

  return (
+    <UserContext.Provider value={{ userInfo, setUserInfo }}>
-    <UserContext.Provider value={{ contextName }}>
      {children}
    </UserContext.Provider>
  );
};

ろろろろろろ

Recoil

まだ正式公開されていないが、Metaが開発しているグローバルなstate管理ができる状態管理ライブラリ。
jsxを返却するわけではないから、.jsファイルでok。
https://recoiljs.org/
npm install recoilでインストール。
keyにはユニークな値を設定。(ファイル名とそろえるといいかも)
defaultデフォルトで設定する値。(初期値だと思えばいいかな)

userState.js
import { atom } from "recoil";

export const userState = atom({
  key: "userState",
  default: { isAdmin: false },
});

グローバルで使えるようにRecoilRootタグで囲む。

App.jsx
import {  RecoilRoot } from "recoil";
function App() {
  return (
+    <RecoilRoot>
      <UserProvider>
        <Router2 />
      </UserProvider>
+    </RecoilRoot>
  );
}

export default App;

stateとset関数を参照したい場合はuseRecoilStateをインポートする。

User.jsx
+ import { useRecoilState } from "recoil";

export const Users = () => {
+  const [userInfo, setUserInfo] = useRecoilState(userState);
-  const { userInfo, setUserInfo } = useContext(UserContext);

  const onClickSwith = () => {
    setUserInfo({ isAdmin: !userInfo.isAdmin });
  };

  return (
    <SContainer>
      <h2>ユーザー一覧</h2>
      <SearchInput />
      <br />
      <SecondaryButton onClick={onClickSwith}>切り替え</SecondaryButton>
      <SUserArea>
        {users.map((user) => (
          <UserCard key={user.id} user={user} />
        ))}
      </SUserArea>
    </SContainer>
  );
};

値のみを参照したい場合はuseRecoilValueをインポートする。

userStateのみを参照したいコンポーネント.jsx
+ import { useRecoilValue } from "recoil";

+  const userInfo = useRecoilValue(userState)
-  const { userInfo } = useContext(UserContext);

set関数のみを参照したい場合はuseSetRecoilStateをインポートする。
値は参照しないけどstateだけ更新したい!ってときに使う。
更新するset関数を受け取っているコンポーネントはstateの値が更新されても再レンダリングはされない

set関数のみを参照したいコンポーネント.jsx
+ import { useSetRecoilState } from "recoil";
+ const setUserInfo = useSetRecoilState(userState);
- const { setUserInfo } = useContext(UserContext);
ろろろろろろ

Pick<>

ある型から特定の要素を取得して使用したい場合はPickを使う。
Pick<"元の型","欲しい型1" | 欲しい型2">

todo.js
export type TodoType = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

userId,title,completed`だけを取得したいときは・・・

Todo.tsx
export const Todo = (
+  props: Pick<TodoType, "userId" | "title" | "completed">
) => {
  const { title, userId, completed = false } = props;
  const completeMark = completed ? "[完]" : "[未]";
  return <p>{`${completeMark} ${title}(ユーザー:${userId})`}</p>;
};

Omit<>

Pickの逆で、特定の要素を除いたものを使用したい場合に使う。
id以外の要素を取得したいときは・・・

Todo.jsx
+ export const Todo = (props: Omit<TodoType, "id">) => {
  const { title, userId, completed = false } = props;
  const completeMark = completed ? "[完]" : "[未]";
  return <p>{`${completeMark} ${title}(ユーザー:${userId})`}</p>;
};
ろろろろろろ

関数の型定義

関数名にFC,VFCを追加して関数を記述する。
FCは暗黙的にchildrenが含まれていたが、React V18以降は明示的に指定しないとエラーになるため注意

Text.jsx(FC)
+ import { FC } from "react";

type Props = {
  color: string;
  fontSize: string;
};

+ export const Text: FC<Props> = (props) => {
  const { color, fontSize } = props;
  return <p style={{ color, fontSize }}>テキストです</p>;
};

FCからchildrenを除くためにVFCが用意されていたが、React V18以降は明示的に指定しないといけなくなったため非推奨となった

Todo.tsx(VFC)
import { VFC } from "react";

type Props = {
  color: string;
  fontSize: string;
};

export const Text: VFC<Props> = (props) => {
  const { color, fontSize } = props;
  return <p style={{ color, fontSize }}>テキストです</p>;
};
ろろろろろろ

オプショナルチェーン

存在しないプロパティにアクセスしてもエラーにならずundefindを返す。
以下のようなコードがあったとする。usernamehobbiesがそれぞれ存在していればエラーなく表示される。

export const UserProfile: VFC<Props> = (props) => {
  const { user } = props;
  return (
    <dl>
      <dt>名前</dt>
      <dd>{user.name}</dd>
      <dt>趣味</dt>
      <dd>{user.hobbies.join(" / ")}</dd>
    </dl>
  );
};
渡すデータ
const user: User = {
  name: "namae",
  hobbies: ["映画", "散歩"],
};


hobbiesが存在しないとエラー。

渡すデータ
const user: User = {
  name: "namae",
-  hobbies: ["映画", "散歩"],
};


そこで、オプショナルチェーンを使うと上述の通り、存在しないプロパティはundefindとなる。

export const UserProfile: VFC<Props> = (props) => {
  const { user } = props;
  return (
    <dl>
      <dt>名前</dt>
      <dd>{user.name}</dd>
      <dt>趣味</dt>
+      <dd>{user.hobbies?.join(" / ")}</dd>
-      <dd>{user.hobbies.join(" / ")}</dd>
    </dl>
  );
};


エラーもなしヨシ!

ろろろろろろ

カスタムフックって?

  • 関数。
  • useState,useEffectだったりのReactの各関数を利用できる。
  • コンポーネントからロジックを分離。
  • ロジックから分離することによって、使いまわし、テストが楽になる。
  • use○○という名前にする。
ろろろろろろ

例(ユーザー一覧を取得)

onClickに書いている内容をそのまま関数化するイメージ。
他のファイルでも使用できるようにするためには、配列またはオブジェクトでreturnする。
個人的にはオブジェクトのが好み。

useAllUsers.ts
import { useState } from "react";
import axios from "../../node_modules/axios/index";
import { UserApi } from "../types/api/userApi";
import { UserProfile } from "../types/userProfile";

//全ユーザー一覧を取得するカスタムフック
export const useAllUsers = () => {
  const [userProfiles, setUserProfiles] = useState<Array<UserProfile>>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  const getUsers = () => {
    setLoading(true);
    setError(false);

    axios
      .get<Array<UserApi>>("https://jsonplaceholder.typicode.com/users")
      .then((res) => {
        const data = res.data.map((user) => ({
          id: user.id,
          name: `${user.name}(${user.username})`,
        }));
        setUserProfiles(data);
      })
      .catch(() => {
        setError(true);
      })
      .finally(() => {
        setLoading(false);
      });
  };

  return {
    getUsers,
    userProfiles,
    loading,
    error,
  };
};

作成したカスタムフックを使うにはこのように書く。

App.tsx
import { useAllUsers } from "./hooks/useAllUsers";
function App() {
const { getUsers, userProfiles, loading, error } = useAllUsers();

  const onClickHandler = () => {
    axios
      .get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos")
      .then((res) => {
        setTodos(res.data);
      });
  };

return (
    <>
      <button onClick={onClickFetchUser}>データ取得</button>
      <br />
      {error ? (
        <p style={{ color: "red" }}>データ取得に失敗しました。</p>
      ) : loading ? (
        <p>Loading....</p>
      ) : (
        <>
          {userProfiles.map((user) => (
            <UserCard key={user.id} user={user} />
          ))}
        </>
      )}
   </>
  );
}

呼び出したconst { getUsers, userProfiles, loading, error } = useAllUsers();はそれぞれのコンポーネントで独立となる。
他のコンポーネントでuseAllUsersを呼び出したとしても競合することはない。

ろろろろろろ

プロパティ名の動的生成

プロパティ名をブラケットで括ることによって、式の値からプロパティ名を生成できる。(算出プロパティ名 )
iの値によって、element1element2・・とプロパティが生成される。

let i = 0;
const array = {
 [`element${++i}`]: '○○ ○○',
 [`element${++i}`]: '性別',
 [`element${++i}`]: '年齢',
};

console.log(array);

ろろろろろろ

他の例

[e.target.value]:e.target.value

要素の名前(e.target.name)をそのままプロパティ名として、入力値(e.target.value)を渡しなさい 意味。

ろろろろろろ

分割代入

const originalList = [10,20,30];
const [x, y, z] = originalList;
console.log(x,y,z);
(結果) 10 20 30

左辺に要素の数だけ変数を列挙、し、全体を[ ]で括る。
実際の配列サイズより代入先が少なければ残りの要素は無視される。

const [a,b] = originalList;
console.log(a,b);
(結果) 10 20

実際の配列サイズより代入先が多ければ、その分はundefinedになる。

const [c,e,f,g] = originalList;
console.log(c,e,f,g)
(結果) 10 20 30 undefined

一部だけの場合は・・・

const [h, ,j] = originalList;
console.log(h,j);
(結果) 10 30
ろろろろろろ

オブジェクトがブロックとみなされてしまう例

const teamMember ={
 fullName: '○○ 太郎',
 gen:'male',
 age: 30
};
let fullName, gen, memo;

とする。

{ fullName, gen, memo =  'none' } = teamMember;
(結果) Uncaught SyntaxError: Unexpected token '='

左辺の{ }がブロックとしてみなされずに、ただの文として扱われたためにエラーとなる。
正しくは、代入文の前後を( )で括る。

({ fullName, gen, memo =  'none' } = teamMember);
(結果) {fullName: '○○ 太郎', gen: 'male', age: 30}
ろろろろろろ

これはアロー関数でも同じこと。
自分はよくやりがちだから注意する。

間違った記述
const function1 = () => { title: 'タイトル' };
(戻り値) undefined

{ }のブロック内に、ラベル + 文字列が存在するだけの関数とみなされている。オブジェクトリテラルとして正しく認識されるようにしたい場合は( )で括る。

正しい記述
const function1 = () => ({ title:'タイトル' });
(戻り値) () => ({ title:'タイトル' })
ろろろろろろ

可変長引数の関数

仮引数の前に...を付与するとこと可変長引数となる。任意の引数を配列として取得する。

function sum(...hikisu){
 let result = 0;
 for (const val of hikisu){
  result += val;
 }
return result;
}
console.log(sum(10,11,12));
(結果) 33
console.log(sum(1,2,3,4,5,6,7,8,9,10));
(結果) 55

普通の引数と併用したいときは、引数の最後に配置すること。

×: function badFunction(...kahenHikisu, hikisu){};
: function goodFunction(hikisu,...kahenHikisu){};
ろろろろろろ

(ざっくりとでも) ReactDOM.createRoot って?

createRoot(hikisu1,[option])メソッドはReactアプリを埋め込むべきルート要素を特定する。

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

これは、
<React.StrictMode>配下の<App>コンポーネントを実行して、その結果をid="root"に描画してね
の意味になる。

ろろろろろろ

関数コンポーネントとクラスコンポーネント

どちらも同じ動作をするコンポーネント。

App.js(関数コンポーネント)
import logo from "./logo.svg";
import "./App.css";

// Appコンポーネントを定義
function App() {
// 戻り値
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          こんにちは、 React!
        </a>
      </header>
    </div>
  );
}

// Appコンポーネントをエクスポート
export default App;
App.js(クラスコンポーネント)
// import は省略
// AppClassコンポーネントを定義
class AppClass extends React.Component{
// 描画する内容
render(){
 return(
  <div className="App">
   // 関数コンポーネントと同じ内容のため省略  
  </div>
 )
}
}

//AppClassコンポーネントをエクスポート
export default AppClass;

クラスコンポーネントは、ReactComponentクラスを継承し、renderメソッドで出力することがクラスコンポーネントを定義する上で必要。

んでどっちのがいいの?

シンプルな関数コンポーネントのがいいかも。(というか今までの学習のほとんどが関数コンポーネントだった)
Meta的にも関数コンポーネントがメインだから倣って進めよう。

ろろろろろろ

エスケープ

{ }で括られた式はエスケープ処理される。
{ }&などの文字を表したい場合は、エスケープシーケンスを使おう。

<div>{"Guri &amp; Gura"}</div>
(結果) Guri &amp; Gura
<div>Guri &amp; Gura</div>
(結果) Guri & Gura

// エスケープシーケンス
<div>{'Guri \u0026 Gura'}</div>
(結果) Guri & Gura
ろろろろろろ

Propsの分割代入

引数としてpropsを受け取るのはよいが、関数内ではprops.○○と記述する。
Propsの数が増えると面倒になる。分割代入を使うとコードが少しだけ短くなる。また規定値を宣言できるようになる。

分割代入を使わない方法
export default function MySamplePorps(props) {
  return <div>これがmyPropsの値だよ→ {props.myProps}</div>;
}
分割代入を使う方法
export default function MySamplePorps({ myProps }) {
  return <div>これがmyPropsの値だよ→ {myProps}</div>;
}
index.js
<MySamplePorps myName="分割代入を使ってないよ!" />
<MySamplePorps myName="分割代入を使っているよ!" />


結果
表示された結果は同じだよ。
規定値を指定した場合は以下の通り。

規定値を指定
export default function MySamplePorps({ myProps = 'これは規定値だよ!' }) {
  return <div>これがmyPropsの値だよ→ {myProps}</div>;
}
index.js
<MySamplePorps />


結果

ろろろろろろ

map関数におけるkeyについて

配列要素が主キーを持たない場合にはmap関数のindexを使うことがある。

{
 datas.map((data,index) => (
  <React.Fragment key={index}>
    <dt>()</dt>
    <dt>{data.summary}</dt> 
  </React.Fragment>
  )
 )
}

一見するとindexは一意の値に見えるが、要素の追加や削除、並び替えによって変化する可能性がある。
将来的に見て、バグを引き起こすリスクを持つくらいならリスト内の要素は一意のkeyを持つようにすること。

ろろろろろろ

入れ子構造のコンポーネントに対してmap関数を使う場合は、keyを属性の意味を考えてから設定すること。
たとえば、リスト全体を管理するZentai.jsと個々の内容を管理するItem.jsがあったとする。

Zentai.js
import Item from "./Item";

export default function Zentai({ src }) {
  return (
    <dl>
      {src.map((elem) => (
        <ForItem itemNaiyo={elem} key={elem.cd} />
      ))}
    </dl>
  );
}
Item.js
export default function Item({ book }) {
  return (
    <React.Fragment>
      {book.summary}
    </React.Fragment>
  );
}

keyを追加する必要があるのは、Itemコンポーネント配下の<>~</>ではなく、Itemコンポーネントそのものに対して追加する必要がある。
map呼び出しの中に現れる要素に対して追加する。

ろろろろろろ

classnames

className属性へのスタイル指定をする場合に{ }が混雑になってくる。
そんなんときに便利になってくるのがclassnamesライブラリ。
https://github.com/JedWatson/classnames
動的なクラスの使い分け、複数のクラス名をつけるときに使うとよい。

classNames.js
import classNames from 'classnames;

export default function ClassNameStyles({hikisu}){
return(
 <div className={classNames('box', hikisu === 'light' ? 'light' : 'dark')}>
  内容だよ!!!
 </div>
);
}
index.js
<SelectStyle hikisu='light' />


結果をコンソールでみると・・・

このライブラリを使うことによって、className属性にスタイル指定をするときに、見やすくなる(特にある条件のみにスタイル適用する場合など)

<div className={hikisu === 'light' ? 'light' : 'dark'}>
(省略)
</div>
ろろろろろろ

childrenに対してパラメーターを引き渡すときの注意

props.childrenが表すコンテンツは呼び出し元のスコープであるから、コンポーネント配下の情報にはアクセスできない。
次の例は、配列sampleArrayを出力するテンプレートをindex.jsで指定できることを目的としているが、エラーになる。

ListSampleTemplate.js
export default function ListSampleTemplate({src,children}){
 return(
  <dl>
   {src.map(data => (
     <React.Fragment key={data.keyValue}>
      {children}
     </React.Fragment>
   }
  </dl>
 );
}
index.js
(省略)
<ListSampleTemplate src={sampleArray}>
 <dt>
  <p>{data.firstName} {data.lastName}</p>
 </dt>
</ListSampleTemplate>


結果
index.jsdata参照できない。
なぜならdataは、ListSampleTemplatemapで定義されている変数だから。
このエラーを解決したい場合は呼び出し元配下のテンプレートを関数化することによって解決できる。
関数化することによって、mapで取り出した配列要素を引き渡すことによって、呼び出し元からそれぞれの情報を参照することができるようになる。

ListSampleTemplate.js
export default function ListSampleTemplate({src,children}){
 return(
  <dl>
   {src.map(data => (
     <React.Fragment key={data.keyValue}>
      {children(elem)}
     </React.Fragment>
   }
  </dl>
 );
}
index.js
(省略)
<ListSampleTemplate src={sampleArray}>
 {
  <>
    <dt>
      <p>{data.firstName} {data.lastName}</p>
    </dt>
  </>
 }
</ListSampleTemplate>
ろろろろろろ

描画のための関数 レンダープロップ

レンダープロップを利用することによって、データの取得や演算のような共通機能をコンポーネントで定義し描画方法は利用する人が自由に決められるようになる。

ListSampleTemplate.js
+ export default function ListSampleTemplate({src,render}){
- export default function ListSampleTemplate({src,children}){
 return(
  <dl>
   {src.map(data => (
     <React.Fragment key={data.keyValue}>
+      {children(elem)}
-      {render(elem)}
     </React.Fragment>
   }
  </dl>
 );
}
index.js
(省略)
+ <ListSampleTemplate src={sampleArray} render={(elem) => (
- <ListSampleTemplate src={sampleArray}>
+  <>
+   <dt>
+    <p>{data.firstName} {data.lastName}</p>
+   </dt>
+  <>
+ )} />

- {
-  <>
-    <dt>
-      <p>{data.firstName} {data.lastName}</p>
-    </dt>
-  </>
- }
- </ListSampleTemplate>
ろろろろろろ

setStateを使ったstateの更新タイミング

何度も読んだ通りReactではStateを非同期に更新する。
Stateが新しい値に更新されるのは、イベントハンドラーを終えた後である。

setCnt(cnt + 1); // 11
setCnt(cnt + 1); // 11
(結果) 11

常に最新の値を参照したい場合は、setState関数に関数方の引数を渡すとよい。

setCnt(cnt => cnt  + 1); // 11
setCnt(cnt => cnt  cnt + 1); // 12
(結果) 12
ろろろろろろ

コンポーネント間の情報引き渡し(子→親)

コンポーネント間の情報を引き渡す手段は
親→子:Props
子→親:State
となっている。

Parent.js
(import は省略)
export default function ParentComponent() {
  // count初期化
  const [count, setCount] = useState(0);
  // update関数を準備
  const update = (addValue) => setCount((cnt) => cnt + step);
  return (
    // update関数子コンポーネントへ渡す
    <>
      <p>合計カウント:{count}</p>
      <StateCounter addValue={1} onUpdate={update} />
      <StateCounter addValue={10} onUpdate={update} />
      <StateCounter addValue={-1} onUpdate={update} />
    </>
  );
}
ChildComponent.js
export default function StateCounter({ addValue, onUpdate }) {
  // State(count)に加算
  const handleClick = () => onUpdate(step);
  return (
    <button className="cnt" onClick={handleClick}>
      <span>{addValue}</span>
    </button>
  );
}

親コンポーネントからStateを更新するためのupdateを子コンポーネントに渡したことによって、子コンポーネントでは任意のタイミングでupdateを呼び出しStateを更新できる。

ろろろろろろ

キーイベント

ReactではJavaScriptの標準なイベントを利用できる。
(アニメーション、ドラッグアンドドロップ、マウス操作、キーボード押下などなど・・・)
キーボードイベントを発生させたい時は以下のようにする。

EventHelp
export default function EventHelp() {
  // ctr + h でヘルプ表示
  const handlreKey = (e) => {
    if (e.ctrlKey && e.key === "h") {
      alert("ヘルプメッセージの内容だよ");
    }
  };

  return (
    <form>
      <label>
        ラベル:
        <input type="text" size="20" onKeyDown={handlreKey} />
      </label>
    </form>
  );
}
index.js
<EventKey />
ろろろろろろ

イベントハンドラーに任意の引数を渡す

イベントハンドラーになんらかの値を引数として渡す倍は、アロー関数を使うことによって渡すことができる。
以下の例では、アロー関数はイベントオブジェクトeを受け取る。必要に応じて任意の引数と合わせて、本来のイベントハンドラーcurrentに対しても引数を渡している。
イベントハンドラーに対して任意の引数を渡すこの方法は、実行時に引数の値が変化するときに使うようにする。

EventSample.js
export default function EventSample() {
  // 独自の引数を追加したイベントハンドラー
  const current = (e, type) => {
    const d = new Date();

    switch (type) {
      case "date":
        console.log(d.toLocaleDateString());
        break;
      case "time":
        console.log(d.toLocaleTimeString());
        break;
      default:
        console.log(d.toLocaleString());
    }
  };
  return (
    <div>
      {/* アロー関数経由でハンドラー呼び出し。 */}
      <button id="dt" onClick={(e) => current(e, "datetime")}>
        日時
      </button>
      <button id="date" onClick={(e) => current(e, "date")}>
        日付
      </button>
      <button id="time" onClick={(e) => current(e, "time")}>
        時刻
      </button>
    </div>
  );
}
index.js
<EventSample />
ろろろろろろ

もう一つの手法として、独自データ属性を使った方法もある。
独自データ属性とは、任意のタグにdata-○○○○の形式で指定できる属性のことを指す。
独自データ属性にアクセスするときはe.target.dataset.○○としてdatasetプロパティにアクセスする。
この手法はあらかじめ引数の値が決定しているときに使うようにする。

EventSample2
export default function EventSample2() {
  const current = (e) => {
    // datasetプロパティにアクセス
    const type = e.target.dataset.type;
    const d = new Date();

    switch (type) {
      case "date":
        console.log(d.toLocaleDateString());
        break;
      case "time":
        console.log(d.toLocaleTimeString());
        break;
      default:
        console.log(d.toLocaleString());
    }
  };
  return (
    <div>
      {/* 独自データ属性で指定 */}
      <button id="dt" data-type="datetime" onClick={current}>
        現在日時
      </button>
      <button id="dt" data-type="date" onClick={current}>
        現在日付
      </button>
      <button id="dt" data-type="time" onClick={current}>
        現在時刻
      </button>
    </div>  );
}
ろろろろろろ

非制御コンポーネント

入力値をStateで保持しないコンポーネントのこと。
入力値が欲しし場合は、<input><select>に直接アクセスする必要がある。
非制御コンポーネントは、要素オブジェクトから直接値を取得するため、対象の要素に対して参照を用意する必要がある。
その手段として、useRef関数を使用する。

useRef(初期値)

要素に紐づける場合は、初期値をnullとする。

sample.js
const firstName = useRef(null)

return(
 <intput id="firstName" ref={firstName} />
)

↑だと
1 useRef関数からRefオブジェクトを生成(ここでfirstName)
2 <input (省略) ref={firstName} />inputのエレメントを代入
3 Refオブジェクトではinputへの参照を保持
currentプロパティにアクセスすることによって、valueプロパティなどの各プロパティを取得できる。

console.log(`value: ${firstName.current.value}`)


結果

ろろろろろろ

splice

Arrayオブジェクトに含まれるメソッドの1つ。指定した範囲の用をそ別の要素に置き換える。

Array.splice([開始インデックス])
Array.splice([開始インデックス],削除する要素数)
Array.splice([開始インデックス],削除する要素数,追加要素・・・)
  const [form, setForm] = useState({
    animal: ["dog", "bigCat"],
  });

  // Stateに反映
  const handleFormFukusuCheck = (e) => {
    const fa = form.animal;

    if (e.target.checked) {
      fa.push(e.target.value);
    } else {
      fa.splice(fa.indexOf(e.target.value), 1);
    }

    // 編集済みの配列をStateに反映
    setForm({
      ...form,
      [e.target.name]: fa,
    });
  };

  const show = () => {
    console.log(`動物: ${form.animal}`);
  };

return (
    <form>
      <legend>好きな動物:</legend>
      <label htmlFor="animal_dog"></label>
      <input
        id="animal_dog"
        name="animal"
        type="checkbox"
        value="dog"
        checked={form.animal.includes("dog")}
        onChange={handleFormFukusuCheck }
      />
      <br />
      <label htmlFor="animal_normalCat"></label>
      <input
        id="animal_normal_cat"
        name="animal"
        type="checkbox"
        value="normalCat"
        checked={form.animal.includes("normalCat")}
        onChange={handleFormFukusuCheck }
      />
      <br />
      <label htmlFor="animal_bard">小さい猫</label>
      <input
        id="animal_small_cat"
        name="animal"
        type="checkbox"
        value="smallCat"
        checked={form.animal.includes("smallCat")}
        onChange={handleFormFukusuCheck }
      />
      <br />
      <button type="button" onClick={show}>
        送信
      </button>
    </form>
  );
ろろろろろろ

Refを使ったファイル情報を取得の例

生成したRefオブジェクトはcurrentプロパティで目的の要素を取得できる。

  // ファイル入力ボックスへの参照
  const file = useRef(null);

  // ファイル情報をログ出力
  function show() {
    const fs = file.current.files;
    // 取得したファイル群を順に出力
    for (const f of fs) {
      console.log(`ファイル名: ${f.name}`);
      console.log(`種類: ${f.type}`);
    }
  }

  return (
    <form>
      <input type="file" ref={file} multiple />
      <button type="button" onClick={show}>
        送信
      </button>
    </form>
  );


結果

ろろろろろろ

スプレッド構文

オブジェクトの複製を生成し分解する。
下記のコードは既存の入力値を複製し、e.target.nameだけを上書きするという意味になる。

setState({
 ...state,
[e.target.name]: e.target.value
})

オブジェクトを複製させたいだけなら下記のようにする。

const sampleObj = { a:'Aだよ', b:'Bだよ'}
const copyObj = {...sampleObj}


結果