Open7

memo SampleApp with React + TypeScript + Vite

mintiamintia

React + TypeScript + Vite でアプリを作成してみる
Vite

React: 何となく触ったことある
TypeScript: JavaScript なら触ったことある
Vite: なんか速いらしい

mintiamintia
npm create vite
npm install
npm run dev
o [+ Enter](ブラウザを開く)
q [+ Enter](サーバーを?終了する)

reactとtypescriptを選択
svelteとか速くて簡単って聞いたけどどうなんだろうか

hello react
hello react

mintiamintia

Q.何を作る? A.ぷよぷよ連鎖シミュレーター

まずはApp.tsxをきれいにしました。

App.tsx
import './App.css'

function App() {
  return (
    <>
    </>
  )
}

export default App

mintiamintia

フィールドと、フィールドの1マスを作成

とりあえず表示するだけ
Reactはコンポーネントと呼ばれるUIの部品を組み合わせていくらしい?
ボトムアップで作成していく

Field.tsx
const Cell = () => {
    return (
        <>
            <p>1マス</p>
        </>
    )
}

export const Field = () => {
    return (
        <>
            <Cell />
        </>
    )
}

002

とりあえず表示できた。

mintiamintia

Cellのデータをボタンに変更
四角くする
見た目をいい感じにする

コンポーネントに引数を渡す方法

それっぽい見た目になってきた

Field.css
.cell__button {
    height: 50px;
    width: 50px;
    padding: 0;
    border-width: 1px;
}

.cell__button:hover {
    opacity: 70%;
}

.field {
    display: grid;
    grid-template-columns: repeat(6, 50px);
}
Field.tsx
import { useState } from 'react';
import './Field.css'

const Height: number = 13;
const Width:  number = 6;

type CellData = {
    id: number;
}

const Cell = (props: {id: number}) => {
    return (
        <>
            <button key={props.id} className="cell__button">{props.id}</button>
        </>
    )
}


const initCellData = (h: number, w: number): CellData[] => {
    let data: CellData[] = [];

    for(let i = 0; i < h; ++i) {
        for(let j = 0; j < w; ++j) {
            const id = w * i + j;
            data.push({ id });
        }
    }

    return data;
}

export const Field = () => {
    const [cells, setCells] = useState<CellData[]>(() => initCellData(Height, Width));

    return (
        <>
            <div className='field'>
                {cells.map((cell) => {
                    return <Cell id={cell.id}/>
                })}
            </div>
        </>
    )
}
mintiamintia

いろいろあってこうなった
005

やったこと

  • CSSの改造
    • 後述するcolorプロパティに応じて色が変更
  • onclickイベントの実装
    • props には nullable な関数オブジェクトとして? onclick?: () => void を追加
    • Fieldコンポーネント内でCellに渡すときに () => handleCellClick(cell.id) とした

cssがムズすぎる。全然思ったように言うことを聞かない。

const [selectedColor, setSelectedColor] = useState<number>(0);

const handleCellClick = (id: number) => {
        setCells((cells) => {
            let newCells = [...cells];
            newCells[id].color = selectedColor;
            setSelectedColor((selectedColor + 1) % 7);
            return newCells;
        });
    }

選択されている色のuseState
この値に応じて、クリックしたCellの色が変わる。今はろーてーとしてるだけ。
余談だが、0の時は空白、1の時はおじゃまぷよの予定。2の時は色ぷよになるか、それとも固ぷよ(周囲4マスを1回消すとおじゃまぷよになるやつ)にした方が筋がいい気もする。
余談の余談だが、空白+おじゃまぷよ2種類+5色でちょうど8なので3bitに収まる。
3 * 13 * 6 = 234 bit なのでymm 256bit レジスタにいい感じに乗る。

App.tsx全体
import { useState } from 'react';
import './Field.css'

const Height: number = 13;
const Width:  number = 6;

type CellData = {
    id: number;
    color: number;
    onclick?: () => void;
}

const num2color: string[] = [
    "red",
    "blue",
    "yellow",
    "green",
    "purple",
];

const Cell = (props: {id: number, color: number, onclick: () => void }) => {
    let back: string = props.color > 1 ? "cell__circle" : "";
    if (props.color >= 2) {
        back += ` ${num2color[props.color - 2]}-circle`;
    }
    return (
        <>
            <button key={props.id} className="cell__button" onClick={() =>props.onclick()}>
                <div className={back}></div>
                <span className="cell__symbol">{props.color}</span>
            </button>
        </>
    )
}


const initCellData = (h: number, w: number): CellData[] => {
    let data: CellData[] = [];

    for(let i = 0; i < h; ++i) {
        for(let j = 0; j < w; ++j) {
            const id    = w * i + j;
            const color = 0;
            data.push({ id, color });
        }
    }

    return data;
}


export const Field = () => {
    const [cells, setCells] = useState<CellData[]>(() => initCellData(Height, Width));
    const [selectedColor, setSelectedColor] = useState<number>(0);

    const handleCellClick = (id: number) => {
        setCells((cells) => {
            let newCells = [...cells];
            newCells[id].color = selectedColor;
            setSelectedColor((selectedColor + 1) % 7);
            return newCells;
        });
    }

    return (
        <>
            <div className='field'>
                {cells.map((cell) => {
                    return <Cell key={cell.id} id={cell.id} color={cell.color} onclick={() => handleCellClick(cell.id)}/>
                })}
            </div>
        </>
    )
}
App.css全体
.cell__button {
    height: 50px;
    width: 50px;
    padding: 0;
    border-width: 1px;
    position: relataive;
    display: flex;

    justify-content: center;
    align-items: center;
}

.cell__button:hover {
    opacity: 70%;
}

.cell__circle {
    content: "";
    position: absolute;
    width: 40px;
    height: 40px;
    margin: 0;
    border-radius: 50%;
}

.red-circle {
    background-color: orangered;
}
.blue-circle {
    background-color: cyan;
}
.yellow-circle {
    background-color: yellow;
}
.green-circle {
    background-color: limegreen;
}
.purple-circle {
    background-color: magenta;
}

.cell__symbol {
    position: absolute;
    font-size: x-large;
    font-weight: bold;
    font-family: Arial, Helvetica, sans-serif;
}

.field {
    display: grid;
    grid-template-columns: repeat(6, 50px);
}