はじめに
「TypeScriptとReact/Next.jsでつくる 実践Webアプリケーション開発」でNext.js×Type Scriptを勉強中の初学者。
アウトプットを前提にインプットをすることでインプットの質を高めていきたいと思っており、
単元毎の記事化にチャレンジしてみようと思った。
作ったもの
今回、3.5.5 useRefの単元についてアウトプットをする。
useRefの機能として、
▶︎データの保持
▶︎DOMの参照
の2つがあり、この二つの機能を使ったアプリを作りたい。
refオブジェクトに保存された値は更新しても再描画が発生しません。
作ったものとしては、HTML内のいくつかの要素のうえをマウスでhoverした後、「軌跡を表示」ボタンを押すと、どの要素に順番にhoverしたかが表示される。
開発環境
各種最新のバージョンで開発した。今回はNext.jsはあまり関係ないが慣れるためにも使っている。
useRefの機能理解に特化したいため、CSSフレームワークは使用せず、スタイルも最小限にとどめた。
Next.js:13.0.0
React:18.2.0
typescript:4.8.4
ディレクトリ構成は以下。
pages
└─index.tsx //表示のみ
src
├─components
│ └─ Button.tsx
│ └─ Container.tsx
│ └─ Board.tsx
└─features
└─ Trajectory.tsx //Trajectory=軌跡の英訳
コード
import React, {FC, useRef, useState} from 'react';
import {Container} from "../components/Container";
import {Button, ClearButton, SubmitButton} from "../components/Button";
import {Board} from "../components/Board";
const initialState :string[] = ['']
export const Trajectory:FC = () => {
const [ state, setState ] = useState<string[]>(initialState)
const [ array , setArray] = useState<string[]>(initialState)
const btnRef1 = useRef<HTMLButtonElement>(null)
const btnRef2 = useRef<HTMLButtonElement>(null)
const btnRef3 = useRef<HTMLButtonElement>(null)
const btnRef4 = useRef<HTMLButtonElement>(null)
const btnRef5 = useRef<HTMLButtonElement>(null)
const btnRef6 = useRef<HTMLButtonElement>(null)
return (
<>
<Container>
<Button title='primary' color='red' btnRef={btnRef1} state={state} setState={setState}/>
<Button title='secondary' btnRef={btnRef2} state={state} setState={setState}/>
<Button title='dangerous' color='red' btnRef={btnRef3} state={state} setState={setState}/>
<Button title='safety' btnRef={btnRef4} state={state} setState={setState}/>
<Button title='hoge' color='red' btnRef={btnRef5} state={state} setState={setState}/>
<Button title='fuga' btnRef={btnRef6} state={state} setState={setState}/>
</Container>
<Container>
<SubmitButton title='軌跡を表示' setArray={setArray} state={state}/>
</Container>
<Container>
<ClearButton title='軌跡をクリア' setArray={setArray} state={state}/>
</Container>
<Board state={array} />
</>
);
}
import React, {Dispatch, FC, forwardRef, RefObject, useImperativeHandle, useState} from 'react';
import styles from '../../styles/Home.module.css'
type btnProps = {
title: string;
btnRef?: RefObject<HTMLButtonElement>;
state: string[];
setState: Dispatch<React.SetStateAction<string[]>>;
color?: string
}
type Props = {
title: string;
setArray: Dispatch<React.SetStateAction<string[]>>;
state: string[];
}
export const Button: FC<btnProps> = (props) => {
const {title, color, btnRef, state, setState} = props
const handleHover = (): void => {
btnRef?.current?.style.color === 'red'
? setState(prevState => ([...prevState, `${btnRef?.current?.textContent}`]))
: console.log(btnRef?.current?.textContent)
}
return (
<button
className={styles.button}
style={{color: `${color}`}}
ref={btnRef}
onMouseEnter={handleHover}
>{title}</button>
);
}
// eslint-disable-next-line react/display-name
export const SubmitButton:FC<Props> = (props) => {
const {title,setArray,state} = props
const handleClick = (): void => {
setArray(state)
}
return (
<button className={styles.button} onClick={handleClick}>{title}</button>
);
}
export const ClearButton:FC<Props> = (props) => {
const {title,setArray} = props
const handleClick = (): void => {
setArray([])
}
return (
<button className={styles.button} onClick={handleClick}>{title}</button>
);
}
はまった・わからなかったこと
要素毎にhoverしたらstateに保存していけばuseRefを使わなくても実現できてしまう。
今回は、useRefの意味を見出すために、赤字の要素にhoverした時は、stateを更新してそうでない時はstateを更新しない=レンダリングが走らないようにした。
本当は、hover中はどこかにデータを保持し、表示をしたらレンダリングが動くようにしたかったが、できなかった。
おわりに
useRefの機能のうち、DOMの参照くらいしかできなかった。しかも参照自体も、そこまでうまくつかえてはいない。使い所が難しい。
参考文献