てらブログ

てらブログ

日々の学習の備忘録

next/linkとnext/routerを理解する

目的

Next.jsの内部リンクの最適解を考えたい

リンクの種類

Linkコンポーネントをimportして使用する

import Link from "next/link";

// 省略
  <Link href="/">Back to home</Link>

aタグの場合は遷移先ページのhtmlファイルを取得して描画する。Linkコンポーネントの場合はクライアントサイドで新しいページを描画する。prefetchのため、ページ遷移が高速となる。
nextjs.org

useRouter

routerオブジェクトを作成し、変化を与えることで遷移する。

import { useRouter } from "next/router";

export const Page = () => {

  const router = useRouter();
  const linkToHome = () => {
    router.push("/");
  };

  return (
    <button onClick={linkToHome} className="bg-orange-500">
      Back to home
    </button>
  )
}

またrouterオブジェクトには様々な定義があり利用できる。
nextjs.org

Router

useRouterと類似点は多いが、Routerはフックではなくグローバル変数。画面遷移による不要な再描画が起きない。

LinkコンポーネントとuseRouterの使い分け

単純なクライアント側のページ遷移であればどちらでも違いは無い。
useRouterを使用すると、例えば関数内でルーティングを設定する場合に遷移前に処理を挟むことができるなど、より柔軟な扱いが可能になる。

感想

ちゃんと整理してみると、意外とシンプルだった。
プロジェクト内でリンク設定方法を統一していくのを考えると、useRouterを使用するのが良いと思う。
routerオブジェクトを使用することで、様々な扱い方ができることを知れてよかった。

CVAでTailwind CSSのコンポーネントにvariantsなどを持たせたい

目的

表題をやりたい。
用途としては、共通コンポーネントを作成するため。
1つの画面内でのスタイルをまず作成し、その後異なるスタイルが出た場合にvariantsを追加するだけで対応できるようにしたい。
cva.style

about CVA

Clsxのようなスタイルの管理ライブラリを、より機能的にしてTypeScriptの恩恵を受けられるようにした感じ。
"variants"を使用することで、コンポーネントライブラリのテーマカスタマイズみたいな感じにも思える。

usage

共通するスタイルを当てる箇所と、variantsのintentとsizeで特別なスタイルを当てる箇所に分けられる。

import { cva } from "class-variance-authority";
 
const buttonStyles = cva('text-white', {
  variants: {
    intent: {
      primary: 'bg-blue-400',
      secondary: 'bg-gray-400',
      danger: 'bg-red-400'
    }
    size: {
      small: 'w-4 h-2',
      medium: 'w-20 h-10',
      large: 'w-40 h-20'
    },
  },
  defaultVariants: {
    intent: "primary",
    size: "medium",
  },
});

const Button = ({intent, size}) => {
  return(
    <button className={buttonStyles({intent, size})}>
      Button
    </button>
  )
}
  
export default Button

Headless UIとの組み合わせ

Tailwind * Radix UIのshadcnで作成したコンポーネントとの相性も◎
as={}としてコンポーネントを渡し、propsを付加すれば良い。

import { Menu } from '@headlessui/react'
import { Button } from '@ui/Button'

const MyMenu = () => {
  return (
    <Menu>
      <Menu.Button as={Button} intent='secondary' size='large'></Menu.Button>
      <Menu.Items>
        <Menu.Item href='hoge1'>hoge1</Menu.Item>
        <Menu.Item href='hoge2'>hoge2</Menu.Item>
      </Menu.Items>
    </Menu>
  )
}

export default MyMenu

感想

CVAすごく良い。見通しが良く管理がしやすいコンポーネントになるし、オリジナルのコンポーネントを作成している感覚(?笑)がとても楽しかった。
また、cvaを調べることで、結果的にコンポーネントライブラリ(Chakra UIやMUI)とTailwindの比較や、ClsxとCVAの比較が出来て、全体的な理解にも繋がり良かった。

残課題

特になし。これを元に共通コンポーネントを作っていきたい。

Next.js x GraphQL環境でのAPIモックについて調べる

目的

そもそもどんな選択肢があるのか調査する。
プロジェクトで使用されている方法(Apollo Server)についてハンズオンしたい。

モックの種類とそれぞれの特徴

Next.jsでのモックの選択肢を調べるとざっと下記が出てきた。

Playgroundで、APIをブラウザで直接実行・テストするためのツールを提供
モックの機能も提供しており、スキーマからモックデータを自動的に生成可能

  • MSW

実際のAPIリクエストをインターセプトして任意のレスポンスを返すことができる。
App Routerにまだ対応していないのが欠点か...

シンプルなREST APIサーバーだったら使い道がある??

エンドポイントが実際のと異なるのでちょっと違うかも。

こうしてみると、Next.js(App Router) * GraphQL環境では今のところApollo Serverでモック作成するのが良いように思える。

感想・残課題

上記やってみたが、Apollo Serverの作成はモック作成とは言わない気がする...
GraphQLがちゃんと理解できていないので、まずは今回ハンズオンした内容が分かるくらいにはキャッチアップしたい。
GraphQLのフロントエンド作成の流れや、バックエンドとのつながり、あとBFFについて理解する。

Tailwind CSSのコーディングルールを考える

目的

ルールを決めることで、自由度の高いTailwind CSSをちゃんと管理していきたい。

結論

Tailwindについては、まだこれだ!という運用方法が分かっていないので、継続的にアンテナを張って良い方法を検討していきたい。

なるべくコンポーネント分割し、JSXの可読性を上げる

下記コードだとパッと見て分かりにくい。

const Index = () => {
  return (
    <div className="flex items-center justify-center h-screen bg-gray-100">
      <div className="w-[400px] h-[300px] flex flex-col justify-center px-4 mx-auto bg-white rounded-lg shadow-md dark:bg-gray-800">
        <h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
          Tailwind好きです
        </h1>
        <p className="mb-4 text-gray-600 dark:text-gray-400">好きな理由は……</p>
        <button className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-500">
          こちらをクリック!
        </button>
      </div>
    </div>
  );
};

export default Index;

例えば下記のように各々をコンポーネント化すれば、JSX箇所は可読性が上がる。
ただし何でもかんでもコンポーネント化すると、意味的におかしな箇所が出てきそうだし、収拾がつかずそもそもやりたかった整理ができなくなる。
重複箇所や、コンポーネントとして外だしするべき箇所でコンポーネント化するべき。

const Card = ({ children }) => (
  <div className="w-[400px] h-[300px] flex flex-col justify-center px-4 mx-auto bg-white rounded-lg shadow-md dark:bg-gray-800">
    {children}
  </div>
);

const Title = ({ children }) => (
  <h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
    {children}
  </h1>
);

const Text = ({ children }) => (
  <p
    className="mb-4 text-gray-600 dark:text-gray-400"
  >
    {children}
  </p>
);

const Button = ({ children }) => (
  <button
    className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-500"
  >
    {children}
  </button>
);

const Index = () => {
  return (
    <div className="flex items-center justify-center h-screen bg-gray-100">
      <Card>
        <Title>Welcome to Tailwind CSS!</Title>
        <Text>Lorem ipsum dolor sit amet...</Text>
        <Button>Click me</Button>
      </Card>
    </div>
  );
};

テンプレートリテラルで改行する

下記のようにユーティリティが長くなった場合は

<DialogTrigger className="flex h-8 w-40 items-center justify-start rounded-lg px-2 font-bold  text-gray-800 hover:bg-blue-100 focus-visible:outline-blue-300 active:bg-blue-200">

下記のように改行したい。
これもなんでもかんでも改行するのは違うと思うが、2・3つくらいで改行を挟んであげると可読性は良くなりそう。

<DialogTrigger
    className="
      flex h-8 w-40 
      items-center justify-start 
      rounded-lg px-2 font-bold  text-gray-800 
      hover:bg-blue-100 
      focus-visible:outline-blue-300 
      active:bg-blue-200
    "
>

tailwind-mergeを使用する

こちらは以前学んだ通り。

技術書の読書術を学んでみた

目的

現状の学習スピードに課題を感じている。時間をかけている割に、技術のキャッチアップに時間がかかっているように思う。
技術本の読書術を学び、より効率的にインプットを行えるようにしていきたい。
まとめサイトを参考に、取り入れられる視点が無いか探していく。

技術書で本当にスキルアップできる読み方とは?『「技術書」の読書術』の著者は「動くコードを自分で書く」

codezine.jp

 最初はとりあえず1冊の本に書かれている内容を把握するために読みます。使わないものを詳しく知っても意味がないため、「そんな機能があるんだな」というくらいで読み飛ばすのです。

これは知ってはいたが、できていない。意識してやってみたい。

そして、基本的な概念や使い方を確認します。その本で使われているソフトウェアであればインストールしてみて、数行のソースコードであれば入力して試してみてもいいでしょう。
このとき、うまく動かない、となったり、すぐに解決できないと感じたりするのであれば、とりあえず飛ばします。大切なのは「何がどこに書かれているか」を把握することです。

この「とりあえず飛ばす」っていうのをせずに、上手くいかないところに時間をかけてしまっている。

書籍『達人プログラマー』では「毎年少なくとも一つの言語を学習する」ことが推奨されています。実際、毎年でなくとも、2つ、3つとプログラミング言語を学んでいくと、1つのプログラミング言語では得られなかった知識が得られます。

これは今のところピンと来ない。理屈は分かるけど、どうなんでしょうか。フロントエンドは日々新しいライブラリが出てトレンドが変わっていく気がするので、Reactをマスターするだけでも大変だと思うけど、バックエンドの話かな...?「達人プログラマー」は一度読んでみたい。

よいコードより動くコード

学習段階で素早くインプットするために、まずはコード書いて動かすというのが大事

最初からきれいな「よいコード」を書くことを考えるのではなく、まずは汚くても「動くコード」を書くことを心がけます。たとえば、その本の全体を一度読み終えた段階で、章単位で学んだことを使って、何か動くものを作ります。本に書かれている内容をコピーするのではなく、ゼロから何か新しいテーマを考えて作ってみるのです。

これをちゃんとできるようにしていけば、早く成長できそう。そのあとちゃんとプロジェクトコードレベルにもっていくのは当然必要だけれども。

【技術書の読書術 実践してみた】3の発想で3冊の本を読んでみた

dev.classmethod.jp

「2」の発想の場合、積み木でいえば、Aが倒れかかってくればBも倒れてしまう。(中略)2つの関係だから、その先に思考が及ぶことはない。AとBの完結した世界である。一方、「3」の発想を考えてみると、積み木でいえば、3つ存在することである、まず、Aが倒れかかってくればBも倒れる。Bが倒れれば、次にあるCも倒れてしまう。ドミノ倒しの最小、「A→B→C」の連鎖である。「A→B」となり、「B→C」となれば、多分「C→D」と人は考える。完結しない世界、連鎖する世界、である。

「3の発想」というのが面白い。たしかにこの視点は技術書を読む時にとてもマッチしそう。技術書との相性のズレを埋められるし、複数から共通した部分を理解する意識が、1つに時間をかけすぎてしまうのを防ぐ気もする。

3の発想に基づいて以下のように視点が異なる本を3種くらい読むことで実務で使えるレベルになるでしょうと述べられています。

1冊目: 入門レベルの本
2冊目: 専門書
3冊目: 逆引き

たしかに、いきなり専門書レベルに手を出すと、難しく感じてしまいそう。何事も段階を踏むことでスムーズにいく。

学習を効率化してくれる読書術

zenn.dev

学習を効率化させるために読書をするにあたってやってはいけないことを最初に述べる。それは以下の3つである。

本を最初から最後まで順番に読む
本に書いてある内容を暗記する
すべての内容を理解する
これらのことをやめるだけで読書の効率が大幅に上がる。学習のために本を読むなら全部を読むのは絶対にやめよう。

はい。これは理解しました。やっぱりそうですよね。

読書はインプットするだけでは無駄に終わってしまう。学習のために読書をするなら、その読んだ本の内容を具体的に自分が活かせられるように具体的なアクションプランに変換してそれを実行しよう。それが難しいならSNSやブログ等に自分の感想や要約をアップしても構わない。なんでもいいので、自分が目に見える形でアウトプットを残しておくことが非常に重要である。

読むだけで満足するな。その読んだ本の内容を学習や仕事に活かせられるように最大限の工夫を凝らすべきである。

すごく同意。そのためにこのはてなブログも始めたし、なるべくたくさんコードを書くようにしている。

ほとんどの場合、本に書いてあることの10割が役立つことはない。学習のために読書をするなら、辞書やリファレンスのように読み進めることが重要である。必要な知識や考え方は、常に書籍の中のほんの一部にすぎない。興味のない部分やわからないところは積極的に読み飛ばそう。本をよむ上で最初にやっておきたいことは目次を熟読することである。目次にはその本の内容や要点がすべて書かれている。目次を読んで気になる部分や学びたいところをピックアップして読んでいこう。

目次に着目するのは面白い。

学習を加速させるインデックス読書術

qiita.com

本は、あなたの外部ストレージです。

必要なのは

その本に何が書いてあるか、なんとなく把握している
必要なときに本を引いて調べられる
ということです。

極端な意見。たしかにそうか。

読んだものをすべて覚えておきたがるのは、
食べたものをみな身体にとどめておきたがるようなものだ。

ショーペンハウアーさんの名言。良いこと言うなあ。

マーシャル・マクルーハンはこう言っていました。

トンカチは腕の拡張であり、望遠鏡は眼の拡張、
メガホンは声帯の拡張であり、車は足の拡張ということになる。[4]

技術書も同じですね。
それは私たちの脳を拡張し、技術力を拡張する。

あらゆるメディアは人間のなんらかの心的ないし身体的な能力の拡張である。[4]

だから、本を本と思わない方がいい。

ロマンと哲学。

一番いけないのは、わからないままに読み進めることです。
これはだいたい失敗します。
謎が徐々に大きくなって、最後は紙面の上を視線がスベるようになるだけです。

読めないということも、また財産です。
読みたいという気持ちが、また学習に向かうでしょう。
大事なのは読んだふりをしないこと。

読めないことも、また財産。。。名言。わからないままに読み進んでいいと思ってやってたけど、たしかに視線がすべって何も得られない感覚はあった。読むのやめちゃっていいんですね。

本にもウェブにも教科書的なものと、参考書的なものがあります。

教科書は定義が書かれていますが、参考書は理解するための道筋が書かれています
教科書は正確性を重視しますが、参考書はわかりやすさを重視します
教科書は網羅的に書かれていますが、参考書は濃淡つけて解説しています

これも入門書→専門書→逆引きの例のことかな

優れた技術書を体得することは、エンジニアのレベルに直結します。
つまり、読書術はエンジニアのレベルをあげるための
最上のチートコードだということです。

よく一冊ずつ丁寧に読み進める人がいますが
(稀に本当にそういう読み方がハマる人もいますが)
たいていの人はただ遅く読んでいるだけです。

つまみ食い上等、読み飛ばし上等で、
何冊も何十冊も書物をカラダに通していきましょう。
いつしかそれは、あなたの手足となっているはずです。

良いまとめ。

forwardRefってなんだっけ

目的

下記コードを理解したかった。

import { ReactNode, forwardRef } from "react";

export const InputSelectBox = forwardRef<
  HTMLSelectElement,
  JSX.IntrinsicElements["select"] & {
    children: ReactNode;
  }
>((props, ref) => {
  return (
    <select
      {...props}
      ref={ref}
      className="XXX"
    >
      {props.children}
    </select>
  );
});

InputSelectBox.displayName = "InputSelectBox";

forwardRefについて

about

ドキュメントを読むと下記の記載がある。

forwardRef は、コンポーネントが親コンポーネントに DOM ノードを参照として公開することを可能にします。

つまり、コンポーネント内のDOMにRefオブジェクトを渡すための機能。refは特殊な属性らしく、propsとして渡したり、受け取ったりができない。そもそも関数コンポーネントにrefを渡すことはできない。

usage

const SomeComponent = forwardRef(render)

refを渡すことで、DOMを参照したり、外側から操作したりすることが可能。React Hook Formのregisterの利用にも◎

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} ref={ref} />
    </label>
  );
});

forwardRef<refの対象, props>()で定義する。

参照:
ja.react.dev
ja.react.dev

最終的なコード

propsを明示的にし、型を外に出すようにリファクタリングした。

import { ReactNode, forwardRef } from "react";

type SelectProps = JSX.IntrinsicElements["select"] & {
  children: ReactNode;
};

export const InputSelectBox = forwardRef<HTMLSelectElement, SelectProps>(
  (props, ref) => {
    const { children, ...restProps } = props;
    return (
      <select
        {...restProps}
        ref={ref}
        className="rounded-md border border-gray-300 p-1 focus:border-blue-500 focus:ring-blue-500"
      >
        {children}
      </select>
    );
  },
);

InputSelectBox.displayName = "InputSelectBox";

Tailwind CSS ・ tailwind-mergeについて

目的

Tailwind CSSについて基本的なところは理解できてきた。よりコードの質を高めるためにtailwind-mergeについて調べてみた。

tailwind-merge

about

TailwindのCSSクラスを、スタイルの衝突なしにJSに効率的にマージするユーティリティ関数です。

www.npmjs.com

やっていることは非常にシンプルで、デフォルトのスタイルに良い感じに追加のスタイルを上書きしてくれる。
簡単にカスタムの幅を増やしてくれる。

usage

上書きではなく、単なるスタイルの連結はtwJoinを使うと良い。

// React components with JSX syntax used in this example

import { twJoin } from 'tailwind-merge'

function MyComponent({ forceHover, disabled, isMuted }) {
    return (
        <div
            className={twJoin(
                TYPOGRAPHY_STYLES_LABEL_SMALL,
                'grid w-max gap-2',
                forceHover ? 'bg-gray-200' : ['bg-white', !disabled && 'hover:bg-gray-200'],
                isMuted && 'text-gray-600',
            )}
        >
            {/* More code… */}
        </div>
    )
}

スタイルを上書く場合はtwMergeを使用する。

// React components with JSX syntax used in this example

import { twMerge } from 'tailwind-merge'

function MyComponent({ forceHover, disabled, isMuted, className }) {
    return (
        <div
            className={twMerge(
                TYPOGRAPHY_STYLES_LABEL_SMALL,
                'grid w-max gap-2',
                forceHover ? 'bg-gray-200' : ['bg-white', !disabled && 'hover:bg-gray-200'],
                isMuted && 'text-gray-600',
                className,
            )}
        >
            {/* More code… */}
        </div>
    )
}

※注意点

tailwind-mergeはスタイルの変種を扱う主要なツールではない、とある。つまりvariantを持たせて管理したい場合は、Tailwind Variantsを使うなどする。
また、スタイルの自由度が増す分、管理がめちゃくちゃになる可能性があるため、使い方のルールを設けるのが良い。
再利用性の高いコンポーネントには使用しない。
github.com