これは何?
最低限のReactの知識はあるけど、もっと詳しく知りたいと思って勉強した備忘録です。
何かの参考になれば幸いです。
使った教材↓
Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版(Udemy)
目次
レンダリングの最適化
様々なCSSのあて方
ReactRouter
AtomicDesign
グローバルなstate管理(context/recoil)
React×TypeScript
カスタムフック
感想
レンダリングの最適化
再レンダリングが起きる条件
- stateが更新されたコンポーネントは再レンダリング
- propsが更新されたコンポーネントは再レンダリング
- 再レンダリングされたコンポーネント配下の子コンポーネントは再レンダリング
不要な再レンダリングを回避する方法1
memo
を使用する(公式:memo)
// 親コンポーネント
import React, { useState } from 'react';
import { ChildArea } from './ChildArea';
export const Render = () => {
const [text, setText] = useState('');
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickOpen = () => setOpen(!open);
return (
<>
<input value={text} onChange={onChangeText}/>
<br/>
<br/>
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open}/>
</>
);
};
// 子コンポーネント
import React, { memo } from "react";
export const ChildArea = memo((props) => {
const { open } = props;
return (
<>
{open ? (
<div style={style}>
<p>子コンポーネント</p>
</div>
) : null}
</>
);
});
子コンポーネントをmemo
で囲ってあげることで、props
の変更のみに関心を持つようになる。そのため、親要素が再レンダリングされたとしても、props
が変更されない限り、子コンポーネントは再レンダリングされない。
不要な再レンダリングを回避する方法2
useCallback
を使用する(公式:useCallback)
// 親コンポーネント
import React, { useCallback, useState } from 'react';
import { ChildArea } from './ChildArea';
export const Render = () => {
const [text, setText] = useState('');
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickOpen = () => setOpen(!open);
const onClickClose = useCallback(() => setOpen(false), [setOpen]);
return (
<>
<input value={text} onChange={onChangeText}/>
<br/>
<br/>
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} onClickClose={onClickClose}/>
</>
);
};
callbackをメモ化する。アロー関数で生成した関数は、レンダリングされるたびに新しい関数として判定されるため、その関数(callback)を子コンポーネントに渡してた場合、変更がなくても子コンポーネントが再レンダリングされてしまう。useCallback
を使用すると、第2引数に配列として渡したものにだけ関心を持つようになるため、配列の要素に変更がなければ同じ関数として判定されるようになる。
不要な再レンダリングを回避する方法3
useMemo
を使用する(公式:useMemo)
const tmp = useMemo(() => 1 + 3, []);
変数をmemo化する。配列が空の場合は最初の1回だけレンダリングする。
様々なCSSのあて方
InlineStyle
コンポーネントの中に直接cssを書く。書き方はstyle={}
。
export const InlineStyle = () => {
const containerStyle = {
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
};
const titleStyle = {
margin: 0,
color: '#3d84a8',
};
const buttonStyle = {
border: 'none',
padding: '8px',
borderRadius: '8px',
};
return (
<div style={containerStyle}>
<p style={titleStyle}>-- Inline Styles --</p>
<button style={buttonStyle}>Fight!</button>
</div>
)
}
react内でcssを記述する場合はローワーキャメルケースで書く
例:
border-radius ×
borderRadius ◯
CSS modules
cssファイル or Sassファイルを作成して、それを読み込む。書き方はclassName={}
。
以下は、SASSファイルを読み込むやり方。
// CssModules.module.scss
.container {
border: solid 2px #392eff;
border-radius: 20px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
}
.title {
margin: 0;
color: #3d84a8;
}
.button {
background-color: #abedd8;
border: none;
padding: 8px;
border-radius: 8px;
&:hover {
background-color: #46cdcf;
color: #fff;
cursor: pointer;
}
}
// CssModules.jsx
import classes from "./CssModules.module.scss";
export const CssModules = () => {
return (
<div className={classes.container}>
<p className={classes.title}>-- Css Modules --</p>
<button className={classes.button}>Fight!</button>
</div>
)
}
通常のcssファイルの書き方ができる
cssのファイル名はxxx.modules.css
の形にする
scssでは&:hover
のような疑似要素を使うことができる
クラス名はimportした要素にしか関係しないので、同じクラス名を使用しても競合しない
importする際の変数名(上記だとclasses)はなんでもOK
styled-JSX
javaScriptの中にcssを書く。ライブラリなので事前にインストールする必要あり。
Next.jsにはデフォルトで入っているためインストールする必要なし。
インストール方法
npm install --save styled-jsx
書き方 className=""
export const StyledJsx = () => {
return (
<>
<div className="container">
<p className="title">-- Styled JSX --</p>
<button className="button">Fight!</button>
</div>
<style jsx="true">{`
.container {
border: solid 2px #392eff;
border-radius: 20px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
}
.title {
margin: 0;
color: #3d84a8;
}
.button {
background-color: #abedd8;
border: none;
padding: 8px;
border-radius: 8px;
}
`}</style>
</>
);
};
デフォルトでは&:hover
のような疑似要素は使えない
Sassのプラグインをインストールすれば使えるようになる
styled-components
cssをコンポーネントのように書く。ライブラリのため事前インストールが必要。
インストール方法
npm install --save styled-components
書き方
import styled from "styled-components";
export const StyledComponents = () => {
return (
<SContainer>
<STitle>-- StyledComponents JSX --</STitle>
<SButton>Fight!</SButton>
</SContainer>
);
};
const SContainer = styled.div`
border: solid 2px #392eff;
border-radius: 20px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
`;
const STitle = styled.p`
margin: 0;
color: #3d84a8;
`;
const SButton = styled.button`
background-color: #abedd8;
border: none;
padding: 8px;
border-radius: 8px;
&:hover {
background-color: #46cdcf;
color: #fff;
cursor: pointer;
}
`;
&:hover
などの疑似要素は使える
他のクラスからインポートしたコンポーネントなのか、cssコンポーネントなのかの見分けがつかないので、区別する方法を考える必要がある
emotion
色々な書き方ができるライブラリ。TypeScriptと相性が良いらしい。
インストール方法
npm i @emotion/styled @emotion/react
書き方 css={}
or コンポーネント
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx, css } from "@emotion/react";
import styled from "@emotion/styled";
export const Emotion = () => {
const containerStyle = css`
border: solid 2px #392eff;
border-radius: 20px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
`;
const titleStyle = css({
margin: 0,
color: "#3d84a8",
});
return (
<div css={containerStyle}>
<p css={titleStyle}>-- Emotion JSX --</p>
<SButton>Fight!</SButton>
</div>
);
};
const SButton = styled.button`
background-color: #abedd8;
border: none;
padding: 8px;
border-radius: 8px;
&:hover {
background-color: #46cdcf;
color: #fff;
cursor: pointer;
}
`;
/** @jsx jsx */
は、jsx を React.createElement ではなく jsx という関数への呼び出しに変換するよう、babel に指示しているため、必須。ないと機能しない。
普通のcssのように書くこともできるし、コンポーネントとして書くこともできる
ReactRouter
Reactでルーティング処理(ページ遷移)を簡単に記述できるようになるライブラリ。
こちらの記事がとても分かりやすかったので、参考にさせていただきました。
インストール方法
npm add react-router-dom@6
Routingの基本的な書き方
BrowserRouter
routerを使うために最初に記述する必要があるもの。
import { render } from "react-dom";
import App from "./App";
// react-router-domからBrowserRouterをimportする。
import { BrowserRouter } from "react-router-dom";
render(
// BrouserRouerタグでAppタグを囲む。
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
Routerを使えるようにするために、必須
Routes, Route, Link
Routeのpathとelementを設定することでURLを指定するとそのページに遷移ができるようになる。
またLinkタグを使うことでリンクを設定できる。
import { Routes, Route, Link } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";
import { Contact } from "./components/Contact";
export default function App() {
return (
<>
<nav>
<ul>
<li><Link to="/">HOME</Link></li>
<li><Link to="/about">ABOUT</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</>
);
}
Route
のタグを使う場合はRoutes
で囲ってあげる必要がある
path
はURLでの表記、element
は表示するコンポーネント
NavLink
Linkタグの特別バージョンが NavLink。
現在のURLとリンクのpath属性の値が一致している(アクティブかどうか)を判定できる。
// style属性を使った場合
<NavLink to="path" style={({ isActive }) => (isActive ? activeStyle : {})}
// className属性を使った場合
<NavLink to-"path" className={({ isActive }) => (isActive ? "activated" : "")}
404ページへのリダイレクト
*
を使うことで、いずれにも一致しないpath
だった場合のリダイレクト先を指定できる
<Route path='*' element={<Page404 />}/>
親ルートの下に子ルートを作成する
outlet
を使うことで子ルートの内容を表示させることができる
// 子ルートの設定
<Routes>
{/* Routeタグの内部にRouteタグを設置して、入れ子構造にする */}
<Route path="/" element={<User />}>
{/* 子側のRouteのpath属性は、親のRouteタグのpathから見た相対パスを設定する */}
<Route path="yamada" element={<Yamada />} />
<Route path="tanaka" element={<Tanaka />} />
</Route>
</Routes>
// Userコンポーネントの作成
import React from "react";
import { Alert } from "react-bootstrap";
// react-router-domライブラリから、Outletをimportする
import { Outlet } from "react-router-dom";
export const User: React.VFC = () => {
return (
<Alert variant="primary">
<h1>User</h1>
{/* Outletタグを設置 */}
{/* 子側のRouteタグのelement属性のコンポーネントが差し込まれる目印になる */}
<Outlet />
</Alert>
);
};
Route
タグの内部にRoute
タグを設置すると子ルートになる
Outlet
を使うことで子コンポーネントの要素を表示できる
リダイレクトさせる(useNavigate)
useNavigateを使用することでリダイレクトさせることができる
// react-router-domから、useNavigateフックをimportする。
import { useNavigate } from "react-router-dom";
// いったん、navigateメソッドを生成する。
const navigate = useNavigate();
// 第1引数には、リダイレクト先の相対パスを指定。
// 第2引数には、必要に応じてstateとreplaceを指定。
navigate("relative path", { state: { key1: value1, key2: value2 }, replace: booleran });
replace:trueの場合リダイレクト元ページの参照を履歴に残さない(ブラウザの戻るは使えない)
navigate(-1)
にすると戻るボタンを実装できる
パスパラメータを取得する(useParams)
path属性で「:」から始まる文字列を設定したものをuseParamsで取得できる
// 呼び出す側でidをパスに指定
<Route path=':id' element={<UserParameter />} />
import { useParams } from "react-router-dom";
export const UserParameter = () => {
let { id } = useParams();
return (
<>
<p>UserParameterページです</p>
<p>パスパラメータは { id } です</p>
</>
);
};
uriにxxx/id
(パスパラメータ)が含まれていた場合、上記でそのidを取得できる
クエリ文字列(クエリパラメータ)を取得する(useSearchParams or URLSearchParams)
クエリ文字列を取得する場合は、Reactの場合、useSearchParams
を使う。
もしくは、ReactのuseLocation
+ WebAPIのURLSearchParams
を使う。
useSearchParamsを使う書き方
// react-router-domライブラリから、useSearchParamsフックをimportする
import { useSearchParams } from "react-router-dom";
// searchParamsオブジェクトとsetterメソッドを生成する
const [searchParams, setSearchParams] = useSearchParams();
// searchParamsオブジェクトのgetメソッドにkeyを渡す
// クエリパラメーターが「?id=12345」なら、param には「12345」が格納される
const param = searchParams.get("id")
searchParams.get("xxx")
でクエリ文字列を取得できる
useLocation + URLSearchParamsを使う書き方
import { useLocation } from "react-router-dom";
export const UserParameter = () => {
let { search } = useLocation();
const query = new URLSearchParams(search);
return (
<>
<p>クエリパラメータは { query.get('name') } です</p>
</>
);
};
useLocation
は現在地のオブジェクトを返す
searchにはクエリパラメータが入っている
URLSearchParams
をインスタンス化する際の引数にsearchを渡す
stateを渡すページ遷移
Linkにstateを記述する
import { Link } from "react-router-dom";
export const Page1 = () => {
const arr = [...Array(100).keys()];
return (
<div>
<h1>Page1ページです</h1>
<Link to={`detailA`} state={ arr }>DetailA</Link>
</div>
);
};
import { useLocation } from "react-router-dom";
export const Page1DetailA = () => {
// 渡される側はuseLocationを使う
const { state } = useLocation();
console.log(state);
return (
<div>
<h1>Page1DetailAページです</h1>
</div>
);
};
Link state={{ xxx: "yyy" }}
の形でstateを渡すことができる
渡される側はuseLocation
からstate
を取り出すことで使うことができる
AtomicDesign
AtomicDesignとは…
- BradFrost氏が考えたデザインシステム
- 画面要素を5段階に分けて、組み合わせることでUIを実現する
- コンポーネント化された要素が画面を組み合わせているという考え方
- React用,Vue用というわけではない
- モダンjavaScriptと相性が良い
5段階のコンポーネントとは…
- Atoms(原子)→最も小さい単位。 例:ボタン単体
- Molecules(分子)→Atomを組み合わせたもの。 例:アイコン+メニュー名
- Organisms(有機体)→AtomやMoleculeの組み合わせ。 例:サイドメニュー
- Templates(テンプレート)→ページのレイアウトだけ。データは持たない。
- Pages(ページ)→最終的に表示される1画面。
参照:アトミックデザインとは?メリットや気を付けるポイントを徹底解説!
AtomicDesignはあくまで概念
最初から分けようとせず、定期的にリファクタリングすることで対応させていく
要素の関心を意識する
グローバルなstate管理(context/recoil)
グローバルなstateとは、どの画面からでも参照、更新ができる値のこと。
useStateを使った場合は、コンポーネント内だけで完結してしまうため、他のコンポーネントに渡す場合は、propsを使っていくしかない。しかし、規模が大きくなればなるほど、複雑になってしまうため、グローバルなstateを使用する。
createContext, useContextを使う
// UserProvider.jsxに記述
import { createContext, useState } from "react";
export const UserContext = createContext({});
export const UserProvider = (props) => {
const { children } = props;
const [userInfo, setUserInfo] = useState(null);
return(
<UserContext.Provider value={{ userInfo, setUserInfo }}>
{children}
</UserContext.Provider>
)
}
createContext
でcontextを作成する
createContext
の引数はデフォルト値だが、空のオブジェクトを渡せばOK
作成したcontextにはProviderコンポーネントが付属している
<UserContext.Provider value={/* 何らかの値 */}>
で子孫のコンポーネントにvalueを渡すことができる
Providerのvalueが変更されるたびに子孫のコンポーネントも再レンダリングされる
// UserProviderを呼び出す
import { Outlet } from "react-router-dom";
import { UserProvider } from "./providers/UserProvider";
export const AtomicDesign = () => {
return (
<UserProvider>
<Outlet/>
</UserProvider>
);
};
// AtomicDesignの下にあるUsers.jsx
import { HeaderOnly } from "../templates/HeaderOnly";
import { SearchInput } from "../molcules/SearchInput";
import { SecondaryButton } from "../atoms/button/SecondaryButton";
import { UserContext } from "../../../providers/UserProvider";
import { useContext } from "react";
export const Users = () => {
const { userInfo, setUserInfo } = useContext(UserContext);
const onClickSwitch = () => setUserInfo({ isAdmin: !userInfo.isAdmin });
return (
<HeaderOnly>
<h2>ユーザー一覧</h2>
<SearchInput />
<br/>
<SecondaryButton onClick={onClickSwitch}>切り替え</SecondaryButton>
</HeaderOnly>
);
};
contextを使用するにはuseContext
を使う
useContext
の引数には、作成したcontextを渡す
もし孫のコンポーネントがある場合には、不要な再レンダリングを避けるためにはmemo
で孫コンポーネントを囲う
Recoilを使う
Recoilはstateを管理するためのライブラリ。
recoilをインストール
npm install recoil
Recoilを使う
// グローバルなstateを定義
import { atom } from "recoil";
export const userState = atom({
key: "userState",
default: { isAdmin: false }
});
recoil
の中のatom
を使用する
defaultにはデフォルト値を指定する
// Recoilを使用するコンポーネントの親コンポーネントをRecoilRootで囲う
import { Outlet } from "react-router-dom";
import { RecoilRoot } from "recoil";
export const AtomicDesign = () => {
return (
<RecoilRoot>
<Outlet/>
</RecoilRoot>
);
};
RecoilRoot
で囲う
// 定義したuserStateを使用する
import { HeaderOnly } from "../templates/HeaderOnly";
import { SearchInput } from "../molcules/SearchInput";
import { SecondaryButton } from "../atoms/button/SecondaryButton";
import { useRecoilState } from "recoil";
import { userState } from "../../../store/userState";
export const Users = () => {
const [userInfo, setUserInfo] = useRecoilState(userState);
const onClickSwitch = () => setUserInfo({ isAdmin: !userInfo.isAdmin });
return (
<HeaderOnly>
<h2>ユーザー一覧</h2>
<SearchInput />
<br/>
<SecondaryButton onClick={onClickSwitch}>切り替え</SecondaryButton>
</HeaderOnly>
);
};
useRecoilState(userState)
でグローバルなstateを使用する
useState
と同じように、第1引数にstateの値、第2引数にstateを更新する値を取る
const userInfo = useRecoilValue(userState);
stateだけ使用する場合はuseRecoilValue()
を使用する
const setUserInfo = useSetRecoilState(userState);
更新する値だけを使用したい場合はuseSetRecoilState()
を使用する
React×TypeScript
TypeScriptと併せて開発するとエラーが起きづらいシステムになるので、TypeScriptを使うケースを見てみる。
React×TypeScriptのプロジェクトの作成
npx create-react-app --template typesciprt my-app
--template typesciprt
を加えることでTypeCciprtのプロジェクトが作成できる
基本的な型
// boolean
let bool: boolean = true;
// number
let num: number = 0;
// string
let str: string = 'hoge';
// Array
let arr1: Array<number> = [0, 1, 2];
let arr2: number[] = [0, 1, 2];
// tuple
let tuple: [number, string] = [0, 'hoge'];
// any どんな型でもOK, なるべく使わない
let any: any = false;
// void (戻り値はあえて記述しなくてもTypeScriptが勝手に判定してくれる)
const funcA = (): void => {
const test = 'TEST';
}
// null
let null1: null = null;
// undefined
let undefined1: undefined = undefined;
// object
let obj1: object = {};
let obj2: { id: number, name: string } = {id: 0, name: 'hoge'};
変数名の後に: 型名
で型を定義できる
引数、返却値(戻り値)に型を指定
const calcTotalFee = (num: number): number => {
return const total = num * 1.1;
}
引数、返り値にも型を定義できる
設定ファイル(tsconfig.json)の変更
{
"compilerOptions": {
// strictモードの設定
"strict": true,
// anyのみの設定
"noImplicitAny": false,
}
}
"strcit": true
の場合、型定義など厳格に判断する
"noImplicitAny":
はanyの型のみ判定する。falseの場合、anyを許容する
typeで新しい型を定義する
export type TodoType = {
userId: number;
id: number;
title: string;
completed: boolean;
};
// 作成した型を使用する1
const [todos, setTodos] = useState<Array<TodoType>>([]);
// 作成した型を使用する2
axios.get<Array<TodoType>>('https://jsonplaceholder.typicode.com/todos').then((result) => {
setTodos(result.data);
});
type
を使用することで新しい型を定義できる
useState
に型を定義する場合にはuserState<型>
の形にする
<Array<TodoType>>
でarrayの中の型を定義している
axios
に型を定義する場合にはaxios.get
の後に型を記述する
porpsに型を定義する
type TodoType = {
userId: number;
title: string;
completed?: boolean;
};
export const Todo = (props: TodoType) => {
const { title, userId, completed = false } = props;
// 省略
}
props: 型名
で型を定義できる
必須ではない値には?
を付ける
必須ではない値はデフォルト値を設定しておく(completed = false
)
共通の型定義を使用する(Pick, Omit)
共通の型を使用する場合、使うキーを指定するPickと、使わないキーを指定するOmitが便利。
// src/types/todo.ts
export type TodoType = {
userId: number;
id: number;
title: string;
completed: boolean;
};
// Pickを使うケース
import { TodoType } from "./types/todo";
export const Todo = (
props: Pick<TodoType, "title" | "userId" | "completed">
) => {
const { title, userId, completed = false } = props;
// 省略
}
// Omitを使うケース
import { TodoType } from "./types/todo";
export const Todo = (props: Omit<TodoType, "id">) => {
const { title, userId, completed = false } = props;
// 省略
}
Pick<型名、"使用するキー">
の形で型を指定することができる
Omit<型名、"使用しないキー">
の形で型を指定することができる
関数コンポーネント自体に型を定義する
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<型名>
で関数コンポーネントに型を定義できる
react v17以前の場合にはFC
ではなくVFC
を推奨(暗黙的にchildrenを含むため)
オプショナルチェイニング
要素をつなげていく時に、途中の要素がない場合にundefinedを返すようにする
<dd>{user.hobbies?.join(' / ')}</dd>
hobbiesの後に?
を付けることで、hobbiesがなかったらその時点でundefinedを返す
?がないとjoinでエラーになる場合がある
join
は受け取った配列の全要素を連結した新たな文字列を返すメソッド
ライブラリの型定義
ライブラリ自体にも型の定義があり、インストールする必要がある場合がある
例:react-router-domの型定義をインストールする
npm install --save @types/react-router-dom
axiosのようにライブラリ自体に型定義を包含しているものは型定義のインストールは不要
ライブラリのgithubにindex.d.ts
というファイルがあれば型定義がある
カスタムフック
カスタムフックとは...
- 関数のこと
- コンポーネントからロジックを切り離すために使う
- 「use~」という名前で自由に作れる
カスタムフックの作成
// useAllUsers.tsとして作成
import axios from "axios";
import { useState } from "react";
import { User } from "../types/api/user";
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<User>>('https://jsonplaceholder.typicode.com/users')
.then((result) => {
const data = result.data.map((user) => ({
id: user.id,
name: `${user.name}(${user.username})`,
email: user.email,
address: `${user.address.city}${user.address.suite}${user.address.street}`,
}));
setUserProfiles(data);
})
.catch(() => {
setError(true);
})
.finally(() => {
setLoading(false);
});
};
return { getUsers, userProfiles, loading, error };
}
use~
という名前で作成する
使いたい関数や変数をreturnする
カスタムフックを呼び出す
import { useAllUsers } from './hooks/useAllUsers';
function App() {
const { getUsers, userProfiles, loading, error } = useAllUsers();
const onClickFetchUser = () => getUsers();
return (
<div className="App">
<button onClick={onClickFetchUser}>データ取得</button>
<br/>
{error ? (
<p style={{ color:'red' }}>データの取得に失敗しました</p>
) : loading ? (
<p>Loading...</p>
) : (
<>
{userProfiles.map((user) => (
<UserCard key={user.id} user={user}/>
))}
</>
) }
</div>
);
}
useAllUsers()
で作成したカスタムフックを呼び出している
JavaScriptの分割代入でreturnした関数や変数を使用できる
感想
思った以上に幅広く勉強できたんじゃないかと思います。
フロントも楽しいなと思える内容でした。
今後はcssフレームワークやnext.jsにも手を出していきたいです。