20210914のReactに関する記事は6件です。

React + Typescriptで型安全にColumnとRowを指定できるテーブルComponentを作成してみた

前書き Material-uiを使ったテーブルを作成したい 共通仕様にできるコンポーネントとして独自設計したい ColumnとRowをObject型、かつPropsとして指定する設計にする ColumnとRowの対応関係をなるべく型安全に定義したい これらを満たすテーブルコンポーネントをReact + TypeScriptで 作成してみました。 技術 React 17.0.2 TypeScript 4.4.2 Material-ui/core 4.12.3 コードでは以下のExampleをラップしたコンポーネントを作成 https://material-ui.com/components/tables/#fixed-header コード Component type TColumns<T extends string> = { id: T; label: string; width?: number; }; type TRows<T extends string> = { [key in T]: string | number | React.FC; }; type Props<T extends string> = { columns: TColumns<T>[]; rows: TRows<T>[]; }; /** ※公式引用 */ const useStyles = makeStyles({ root: { width: "100%", }, container: { maxHeight: 440, }, }); const GenericTable = <T extends string>(props: Props<T>): JSX.Element => { const classes = useStyles(); const [page, setPage] = React.useState<number>(0); const [rowsPerPage, setRowsPerPage] = React.useState<number>(10); return ( <> <TableContainer className={classes.container}> <Table stickyHeader aria-label="sticky table"> <TableHead> <TableRow> {props.columns.map((column) => ( <TableCell key={column.id} align="left" style={{ minWidth: column.width }}> {column.label} </TableCell> ))} </TableRow> </TableHead> <TableBody> {props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, i) => { return ( <TableRow hover role="checkbox" tabIndex={-1} key={i}> {props.columns.map((column) => { return ( <TableCell key={column.id} align={column.align}> {row[column.id]} </TableCell> ); })} </TableRow> ); })} </TableBody> </Table> </TableContainer> <TablePagination rowsPerPageOptions={[10, 25, 100]} component="div" count={10} rowsPerPage={rowsPerPage} page={page} onPageChange={(event: any, newPage: number) => { setPage(newPage); }} onRowsPerPageChange={(event: React.ChangeEvent<HTMLInputElement>) => { setRowsPerPage(+event.target.value); setPage(0); }} labelDisplayedRows={({ from, to, count }) => { return `${from}から${to} 件 / ${count}件中`; }} labelRowsPerPage={<>表示件数</>} /> </> ); }; export default GenericTable; 使用例 import GenericTable, { TColumns, TRows } from "...GenericTable"; // カラム値を定義 type ColumnType = "id" | "name"; const columns: TColumns<ColumnType>[] = [ { id: "id", label: "ユーザーID", width: 240 }, { id: "name", label: "ユーザー名", width: 240 }, ]; const rows: TRows<ColumnType>[] = [ { id: 1, name: "トム" }, { id: 2, name: "ブラウン" }, ]; // コンポーネント呼び出し <GenericTable<ColumnType> columns={columns} rows={rows} /> 良い点 何を列としているのか分かり易い 依存性を1つ(=ColumnType)に集約できる 以下のような事象をエラー検知できる type ColumnType = "id" | "name"; // カラムに定義外のidが入る const columns: TColumns<ColumnType>[] = [ { id: "id", label: "ユーザーID", width: 240 }, { id: "name", label: "ユーザー名", width: 240 }, { id: "neta", label: "ネタ", width: 240 }, // ダメ〜 ]; // データに定義外のカラム値を混ぜる const rows: TRows<ColumnType>[] = [ { id: 1, name: "トム", neta: "エラーを" }, { id: 2, name: "ブラウン", neta: "放置します" }, // ダメ〜 ]; 微妙な点 ColumnTypeなる型を別途定義する必要がある 重複するid値を持つなどのケースはエラー検知できない type ColumnType = "id" | "name"; // 例 const columns: TColumns<ColumnType>[] = [ { id: "id", label: "ユーザーID", width: 240 }, { id: "name", label: "ユーザー名", width: 240 }, { id: "name", label: "ユーザー名2", width: 240 }, // エラー出ない ]; 後書き Tableコンポーネントを作る機会があったので、 TypeScriptのGenericsを使って設計を考えてみました。 ご意見ある方いたらご教授いただけたら嬉しいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

props.childrenにpropsを渡す。【React/cloneElement】

はじめに props.childrenに対してpropsを持たせたい!となったので実装方法を探してみました。 *こちらは学習用メモです *ドキュメントにあります。 cloneElement() ReactにはcloneElementというAPIがあります。 今回はこちらを使用し、propsを渡していきました。 cloneElementの役割 element から新しい React 要素を複製して返します。結果の要素は元の要素の props と新しい props が浅くマージされたものを持ちます。新しい子要素は既存の子要素を置き換えます。key と ref は元の要素から保持されます。(引用: https://ja.reactjs.org/docs/react-api.html) 上記の通りにelementから新しいReact要素を複製という部分のelementがprops.childrenに値するという考えです。 使用する際はkey と ref は元の要素から保持されます。こちらに注意しながら実装を行うといいかもしれません。 cloneElement/引数 React.cloneElement( element, // <= 複製するelement [props], // <= 複製するelementに適応させるprops [...children] // <=複製するelementのchildrenとなる要素を指定 ) 使用例 今回紹介する例はあくまで例なので、都合上props.childrenに渡したいという時に使用することと 本当にコンポーネント設計は正しいか確認の必要があると思います。 親コンポーネントの作成 ItemList.tsx import { Item } from './Item' import { ItemChild } from './ItemChild' export const ItemList = () => { return ( <div> <Item> <ItemChild /> </Item> </div> ) } props.childrenにpropsを渡す設定 Item.tsx export const Item = (props) => { const items = [ {id: 1, name: 'hoge1'}, {id: 2, name: 'hoge2'} ] // ここで渡す設定を行うコンポーネントを作成。 const childrenItem = (props) => { // children === props.children, 渡したいprops === ...newProps const { children, ...newProps } = props; const childrenWithProps = React.Children.map(children, (child: React.ReactElement) => React.cloneElement(child, { ...newProps })); } return ( { items.map((item, index) => { // item、 indexがprops.childrenに渡したいprops <childrenItem item={item} index={index}> {props.children} </childrenItem> } } ) } MyItemコンポーネント (propsを渡される props.children) MyItem.tsx export const MyItem = (props) => {  // ItemListコンポーネントではpropsを設定していないがcloneElementの設定により受け取ることができる。 const { item, index } = props return ( <div> <div>index: { index }</div> <div>itemName: { item.name }</div> </div> ) } 終わりに 実際に開発で使用した実装内容だと必要になったのですが、propsの順を追う時に若干厄介になる可能性があるので使用の際は本当に使う必要があるのかなど考える必要があることがわかりました。 参考 【React の最上位 API】 cloneElement() https://qiita.com/NeGI1009/items/e6ad87320391c836bcf9 React.Chilrenについてあまり説明をしていなかったので知りたい方は下記 Reactのchildren探訪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザビリティをあげるボタンの工夫(フロント)

はじめに 本記事では、ボタンでユーザービリティをあげるワザをちょっとご紹介します。 すぐ実践できるものばかりなので、是非参考にしてみてください。 ボタンの活性化・非活性化 例えば、フォームを送信するのに2つの質問に答えないといけないものがあるとします。 条件に当てはまっていないとき(質問に1つしか答えていないときなど)は、ボタンを非活性化 条件クリアしたときに、ボタンを活性化しましょう。 こすうることでユーザーが「直感的」に「ボタンが押せないこと」、「何かが足りていないこと」に気づけます。 むやみにボタンを押して「あれ、、、なんで何も反応しないの、、、押してるよね???」なんて混乱を防げます。 ※↓いったい何のアプリだよっていうツッコミは一旦置いといてください。 ?ワンポイントアドバイス グレーアウトではなく、透明化をしよう! 非活性化時に、ボタンをグレーにする場合があります。 しかし、これは ・ボタンの有効時のフルカラーのデザインとかけ離れているためユーザーを驚かせる ・グレーは背景色に馴染まず浮いてします。目立つことになるため、ユーザーは「非活性」であることに気づかずクリックしてしまう を引き起こす可能性があるためお勧めしません。 代わりに透過させることで、ユーザーを驚かせることなくボタンの無効状態を伝えられます。 参考記事 https://coliss.com/articles/build-websites/operation/work/why-you-shouldnt-gray-out-disabled-buttons.html hover, tap時に変化をつける ユーザーがボタンをhoverしたときや、クリックしたとき ボタンの色を変えたり、サイズを変えたり、変化を起こしましょう。 ユーザーが「直感的」に「押せるもの」であること、「正しくクリックできている」ことを認識できます。 アニメーションを付ける 促したい動作にはアニメーションを付けて見ましょう。 ユーザーに気づいてもらうこと、正しくネクストアクションを提示させることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタートアップの小規模Webサービスのリアルな技術スタック

はじめに プレースホルダというスタートアップのWebエンジニア兼マネージャーのAkahoriです。 弊社はエンジニアは10人以上いるものの、Webエンジニアは私含め3人ほどです。 3人のWebチームで、どのような理由で、どのような技術を使っているか、苦労している点などを共有します。 サービス概要 先月、リトルスパークというサービスをリリースしました。 子ども向けの、オンラインでの習い事プラットフォームで、先生と生徒をマッチングしています。 技術的にはいくつかの特徴を持ち、今回サンプルとして解説します。 授業はライブ授業のみで、お互いにZoomで行います。 ZoomのIDは弊社で管理し、先生側、生徒側、双方が参加ボタン1つで参加できるようになっています。 コース登録(審査有り)や日程登録、プロフィール更新などは全て先生が行うため、その仕組みがあります。 言語・フレームワーク・ライブラリ サーバーサイドもフロントエンドもTypeScriptで統一しています。 いくつかのメリットを感じています。 学習効率が上がる 使い回せるコードがある 型やデータのやりとりがしやすい フロントエンド Next.jsを使っています。 弊社は元々はReact(CRA)を使っていたのですが、ルーティングの煩わしさと、SSR/SSGなどの技術が必要な場面が出てきたため、1年ほど前から切り替えています。 また、以下のようなライブラリを使っています。 material-ui コンポーネントのベースに利用 dayjs 日付関連操作に利用 formik フォーム管理に利用 yup フォームのバリデーションに利用 swr データ取得・状態管理に利用 next-seo タイトルタグなどのSEO関連設定やOGPの設定に利用 fullcalendar カレンダーコンポーネントとして利用 react-player 動画プレイヤーとして利用 styled-components スタイリングに利用 storybook 個別コンポーネントのデザイン確認に利用 next/bundle-analyzer バンドル内容の確認 サーバーサイド NestJSを使っています。 基本はExpressをコアにしたフレームワークで、スケーラブルなコードが書けます。 DBはMySQLを使っており、ORMとしてTypeScriptと相性の良いTypeORMを使っています。 最初はPrisma2を使っていましたが、トランザクション周りが微妙であったために移行しています。 また、以下のようなライブラリを使っています。 moment 日付関連操作に利用 ical-generator 自動更新カレンダーの生成に利用 passport 認証に利用 slack/webhook slackへの通知に利用 google-cloud  GCPの操作に利用、@google-cloud/tasksと@google-cloud/logging-winstonを使っています class-validator リクエストデータの検証に利用 class-transformer レスポンスデータの変換等に利用 googleapis スプレでマスタを管理しているため、通信に利用 連携 REST APIで通信しています。 NestJSのソースから、Open APIのライブラリでAPIクライアントを生成し、フロントではそれを利用することで型安全に通信ができています。 利用した外部サービス Stripe 決済周りに利用しています。 APIはとても良くできているのですが、Stripe Connectという仕組みを使ったため、ここが複雑で非常に苦労しました。 Stripe Connectは、自社、消費者、以外の、サービス提供者などに送金できる仕組みです。 次のような特徴を持ちます。手数料もそれほど高くはないです。 簡易にサービス提供者の顧客確認(KYC)ができる 消費者の支払いから、サービス提供者への銀行振り込みまでAPIで完結できる Zoom サーバーからZoom APIを叩いています。 ユーザーの作成やミーティングの作成を利用しています。 そこそこ複雑です。 Cloudinary 画像の保管・加工・配信に利用しています。 パブリックな画像であれば、特定のURLから自動でリソースを紐づけることもできます。 この機能を利用し、メインの保管先であるGCPのCloud Storageから自動でアップロードしています。 Sendgrid メールの送信に利用しています。便利です。 リポジトリ monorepo構成を取っています。 ユーザー向けクライアント、サーバーアプリ、弊社管理用のクライアントの3つのアプリがあるのですが、一つのリポジトリで管理しています。 管理や全体の把握が容易なところや、連携しやすいというメリットがあります。 lernaというライブラリを使っており、基本便利なのですが、ライブラリのバージョンアップをする際などに煩わしい手順が必要になったりします。 ディレクトリ クライアント クリーンアーキテクチャ+Atomic Designのような構成でやっていますが、全く厳密ではないです。 サーバーサイド クリーンアーキテクチャ+NestJSの推奨、のような構成でやっています。 アーキテクチャ 全面的にGCPを使っています。 コストパフォーマンスと、複雑でない用途であれば使い勝手が良いことから選択しています。 まだ規模が大きくないため、シンプルな構成にしています。(矢印は適当です) スタートアッププログラムも充実しています。 まだ利用されていない会社の方は是非検討いかがでしょう。 まとめ 大手以外ですと技術を公表しているプロジェクト・サービスは少ないですが、いかがでしたでしょうか。 何か一つでも、参考にしてもらえる箇所がありましたら嬉しく思います。 個別のライブラリ等についても随時見直しを行っているため、またしばらく経ちましたら更新した内容を公開したいと考えています。 サービスを一緒に成長させるメンバー募集中です! 弊社では、エンジニアが楽しく効率的に働けることが生産性に直結するという考え方から、多くの裁量を持っています。 これより、「技術好きが楽しめる」会社であるかと考えています。 是非一度カジュアルにお話ししませんか? 正社員・業務委託、共に募集しています! https://www.wantedly.com/projects/410613
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基礎から学ぶReact/React Hooks学習メモ 5-4 useRef

React Hooksを基礎から理解する 5-4 useRef 参考 基礎から学ぶReact/React Hooks useRefとは 要素の参照を行う。主にDOMへのアクセスに利用される。 コンポーネント内において、変数に値を保持することができる。 値を更新してもコンポーネントが再レンダリングされることはない。→ useState()と異なる点 インポート import React, { useRef } from "react"; useRefの基本構文。関数コンポーネントのトップレベルで宣言する。 const refObjct = useRef(初期値); refObjectのcurrentプロパティの値は1000になる。 const refObject = useRef(1000); console.log(refObject.current); // 1000 useRefの利用例(useStateとの比較) ref属性で、inputRefObjectとinput要素を関連付ける ボタンをクリックすると、inputRefObject.current.focus()でテキストボックスにフォーカスする。 useStateのほうに文字を入力すると「レンダリング!」のログが出るが、useRefのほうは出ない import React, { useState, useEffect, useRef } from "react"; import "./styles.css"; const SampleComponent = () => { const inputRefObject = useRef(null); const [inputValue, setInputValue] = useState(""); const handleClick = () => { inputRefObject.current.focus(); }; // useEffectの第2引数がない場合、レンダリングされるたびに呼び出される useEffect(() => { console.log("レンダリング!"); }); const handleChange = (e) => { setInputValue(e.target.value); }; // ref属性で、inputRefObjectとinput要素を関連付ける return ( <> useState: <input onChange={handleChange} type="text" /> <br /> useRef: <input ref={inputRefObject} type="text" /> <button onClick={handleClick}>入力エリアをフォーカス</button> </> ); }; export default function App() { return <SampleComponent />; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 電卓作ってみた

気が向いたのでReact+Javascriptで電卓を作ってみました。 「1+1」とか「3*10」みたいな 2つの整数の四則演算 にか対応していません。(細かすぎると趣旨から外れるので) TypeScriptは使っていません。 ソースコードはGithubにあげているので適当に見てください。 一緒に作りたい方向け ハンズオン形式で作ってみたい方は以下を参照 想定しているレベル感 公式のチュートリアルをやったり、いろいろな教材を触ってみたがいまいちピンと来ていない useStateやpropsの使い方は分かるけどいつ使うべきかはわからない Reactの基礎を学んだが何か自分で作ってみたい Todoリストを作ったけどいまいちピンとこない 目次 準備中 完成イメージ 設計 コンポーネント アプリ >> Appコンポーネント ボタン >> Buttonコンポーネント State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 環境構築 必要なツール Node.js(NPM)とgitをインストールしておいてください。 読者の方の多くはもうすでに大丈夫だと思いますが念のため。 ※不安な人は以下のコマンドを叩いて確認してください.. バージョン確認 node -v git -v インストール 本当はcreate-react-appしてCSS書いてとかいろいろやらないといけないのですが、それでは本記事の趣旨にそぐわぬためテンプレートを用意しています。以下のコマンドを叩いて環境をサクッと整えてください。 git clone https://github.com/TBSten/react-calc.git cd react-calc npm install git checkout base npm run start npm run startで開発用サーバが立ち上がります。 特にエラーが出なければ以上で環境構築終了です! テンプレートのコード確認 まずはテンプレートファイルの内容を見ていきます。 まずはプロジェクトフォルダ(先ほど環境構築したreact-calcフォルダ)をお好みのテキストエディタで開いてください。(筆者はVSCodeを使ています)するとフォルダ構成は以下のようになっているはずです。 react-calc +--node_module +--public +--src ...(その他いろいろ) Reactで開発するときは基本的にプロジェクトフォルダ/srcフォルダ(以下srcフォルダ)内のファイルしかいじりません。なのでsrcフォルダを開きます。srcフォルダ内は以下のようになっています。 src +--styleフォルダ +--index.js +--App.js ...(その他いろいろ) index.jsファイルで基本的な設定(画面への描画やCSSの適用など)を行っています。その中でAppコンポーネントを読み込んでいるので、App.jsにいろいろ書いていきます。 src/App.js function App() { return ( <div className="calc"> <header>電卓</header> <div className="display"> {/* ここに計算結果表示 */} </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; ところどころにコメントがうってある通り、その位置にJSXなどを記述していきます。 試しに{/* ここに計算結果表示 */}を書き換えて電卓だよと表示してみましょう。 <div className="display"> 電卓だよ </div> 表示できました!後でここを計算結果に書き換えるイメージで今は大丈夫です。 Stateの実装 ここで設計で上げたStateの設計を振り返ってみます。 State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 まずはこれら4つのStateを使うことが分かっているので、これを実装します。 useStateを使うのでimportする const [state,setState] = useState(初期値);の形式でそれぞれ記述する。 各Stateの初期値は数字のものは0(入力がないときはデフォルトで0になるように)、それ以外はnull(nullは「まだ分からない」を表す値です)を設定しておきましょう。 src/App.js //ファイルの先頭 import {useState} from "react" ; ..... //コンポーネント内 const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); ここまででApp.jsの中身はは以下のようになっているはずです。 src/App.js import {useState} from "react" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); return ( <div className="calc"> <header>電卓</header> <div className="display"> 電卓だよ </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; Stateを描画 このままだとStateを宣言しただけなのでこれを描画に使っていきましょう。 まず、先ほど電卓だよと書いたところにStateを表示していきましょう。とりあえず今はleft,ope,right という順番で表示させていきたいので、電卓だよの部分を以下のように変えましょう。 {left} {ope} {right} こうすることでleft,ope,rightを順番に描画していきます。今は0,null,0なので以下のように表示されます。 ※JSX内の{ null }は空文字として扱われるためopeの部分に何も表示されていません。 これで表示がとりあえずできましたが、一番初めはopeとrightの部分は隠れていてopeに何か入力されたら(演算子が入力されたら)opeとrightを表示するように三項演算子を使ってみましょう。 {left} {ope} {right} //↓↓↓ {left} {ope === null ? "" : ope} {ope === null ? "" : right} こうすることで leftは常に表示 opeは opeがnullじゃない時だけ表示 rightは opeがnullじゃない時だけ表示 されるようになります。 ボタンを追加する 今のままではボタンがありません。ボタンを作っていきましょう。 設計にて次のように設計したので アプリ >> Appコンポーネント ボタン >> Buttonコンポーネント ボタンはButtonコンポーネントに分離していきましょう。コンポーネントは「src/components」フォルダ内に記述するとファイルの整理がきれいにできるので、「src/components」フォルダ内にButton.jsxファイルを作成しましょう。またそのなかでButtonコンポーネントを定義し、export defaultして外部から使用できるようにしましょう。 src/components/Button.jsx export default function Button(props){ return ( //ここにJSXを書く ) ; } Buttonコンポーネントはただのボタンなので//ここにJSXを書くにはbuttonタグを記述しましょう。 src/components/Button.jsx //..... <button> </button> //..... またButtonコンポーネントは呼び出されるときに次のように呼び出すことで,textプロップスを出力してほしいです。 Buttonコンポーネントの使い方 <Button text={"ぼたんだよー"} /> //↓↓↓↓↓ 出力 <button>ぼたんだよー</button> なのでpropsからtextプロップスを受け取ってそれをbuttonタグ内で表示するようにしましょう。 src/components/Button.jsx //..... <button> { props.text } </button> //..... また今回のような汎用的なコンポーネントを作る際は作る時点で意図していなかったプロップス (例えば今のところtextプロップスしか渡されることを考えていないので,onClickプロップスなどが該当します) を渡されたらをとりあえずそれをbuttonタグに渡しておくようにするといいでしょう。 src/components/Button.jsx //..... const { text , ...other } = props ; //..... こうすることでprops.textが変数textへ、それ以外のpropsの中身はotherへオブジェクトとして渡されます。そしてこのotherをbuttonタグに渡しましょう。 src/components/Button.jsx //..... <button {...other}> {text} </button> //..... これでButtonコンポーネントがひとまず完成しました。ここまででApp.js,Button.jsxコンポーネントは以下の通りになっているはずです。 src/components/Button.jsx export default function Button(props){ const { text , ...other } = props ; return ( <button {...other}> { text } </button> ) ; } src/App.jsx import {useState} from "react" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); return ( <div className="calc"> <header>電卓</header> <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; ボタンをAppに組み込む 先ほど作ったButtonコンポーネントをAppコンポーネントの中に組み込みましょう。Appコンポーネントに組み込めば画面に表示されるはずです。まず{/* ここに数字のボタンや「.」、「=」ボタン */}の部分に追加してみます。 src/App.jsx //App.jsxファイルの先頭 import Button from "./components/Button" ; //..... <Button text="0"/> //..... ボタンが表示されました! あとは0,1,2,3,4,5,6,7,8,9のボタンを追加していきましょう。 src/App.jsx //..... <Button text="0"/> <Button text="1"/> <Button text="2"/> <Button text="3"/> <Button text="4"/> <Button text="5"/> <Button text="6"/> <Button text="7"/> <Button text="8"/> <Button text="9"/> //..... ※「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。 「ボタンが押されたら」を実装する 設計を振り返ってみると 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 となっているのでボタンが押されたときの処理を書いていきましょう。ボタンが押されたときの処理はonClickプロップスに関数として渡します。例えば0のボタンが押されたときの処理は、 0のボタンが押されたときの処理 <Button onClick={ ()=> {/*ここにやりたい事*/} } /> という風に記述します。 とはいっても0を押したときと1を押したときの動作の違いはほぼないので、keyPressedという関数を用意して、0のボタンが押されたときはkeyPressed(0)を、1のボタンが押されたときはkeyPressed(1)を...というように指定すると同じコードを何度も記述しなくて済みます。 src/App.js function keyPressed(key){ //keyのボタンが押されたら... } //..... <Button text="0" onClick={ ()=> {keyPressed(0)} } /> <Button text="1" onClick={ ()=> {keyPressed(1)} } /> <Button text="2" onClick={ ()=> {keyPressed(2)} } /> //以下同様 ※繰り返しですが「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。 さてkeyPressed関数の中では何をすべきでしょうか。設計を思い出してみましょう。 まず数字のボタンが押されたときは、leftまたはrightの値が変わってほしいです。leftかrightどちらの値を書き換えるべきかはopeによって変わります。 もしopeがnull(まだ入力されていない)なら leftを更新 もしopeがnullじゃない(何か入力されている)なら rightを更新 なのでkeyPressed関数の中では条件によってleft,rightを更新する処理を書きます。 src/App.js //..... if(ope === null){ //leftを更新 }else{ //rightを更新 } //..... ではそれぞれどのような値に更新すべきでしょうか?まずleftから考えてみます。 これを考えるには①どのような状態の時に、②どのようなボタンが押されたら、③どのような値になってほしいのかの3つを考えると良いでしょう。例えば、 パターン1 leftが ①「0」の時に ②「3」が押されたら ③leftは「3」になってほしい ではこんなパターンではどうでしょうか? パターン1 leftが ①「3」の時に ②「4」が押されたら ③leftは「34」になってほしい 結論から言うと①と②と③の関係は次のようになっています。 ③ = ①*10 + ② --------------- 3 = 0*10 + 3 34 = 3*10 + 4 このような思考は初心者にはすぐには理解できないかもしれませんので今は理解できなくてもいいでしょうが、いずれはできるようになれていくといいでしょう。 上の式をもとに考えるとleftの値は 更新前のleft * 10 + 入力された値 このような値に更新されるといいでしょう。 なのでifのYesの中には src/App.js //..... function keyPresesd(key){ if(ope === null){ setLeft( left*10 + key); }else{ //rightを更新 } } //..... と書くと良いでしょう。また、rightも同様に考えれるので src/App.js //..... function keyPresesd(key){ if(ope === null){ setLeft( left*10 + key); }else{ setRight( right*10 + key); } } //..... とすればいいでしょう。ここまででいったんうまく動くかチェックしてみましょう。 まだopeを書き換える処理を実装していないので左側の数字の入力しかありませんが問題なさそうです。次は演算子を実装していきましょう。 演算子のボタンを実装する 演算子のボタンは{/* ここに「+」、「-」、「*」、「/」ボタン */}のところに実装していきます。(数字のボタンの時とほぼ同じなので説明はほぼ割愛します。)以下のコードをここに書きましょう。 src/App.js //..... <Button text="+" onClick={ ()=> {/*後述*/} } /> <Button text="-" onClick={ ()=> {/*後述*/} } /> <Button text="*" onClick={ ()=> {/*後述*/} } /> <Button text="/" onClick={ ()=> {/*後述*/} } /> //..... 次にonClickプロップスに渡す関数を考えてみましょう。数字の時に作ったkeyPressed関数を使用したくなりますが、keyPressed関数を呼び出して実行されるのはあくまで数字が押されたときの処理です。演算子ボタンが押されたときの処理はそれとは少し違ったコードになるので、関数を分けるべきです。なので演算子ボタンがクリックされたとき用の関数opeKeyPressedを新たに定義してそれを実行するようにしましょう。 src/App.js function opeKeyPressed(key){ //keyのボタンが押されたら... } //..... <Button text="+" onClick={ ()=> {opeKeyPressed("+")} } /> <Button text="-" onClick={ ()=> {opeKeyPressed("-")} } /> <Button text="*" onClick={ ()=> {opeKeyPressed("*")} } /> <Button text="/" onClick={ ()=> {opeKeyPressed("/")} } /> //..... 次にopeKeyPressed(key)が実行されたときの処理を考えます。 速い話が、演算子ボタンが押されたらopeStateを更新するべきです。それが更新されることで、「opeやrightはopeがnullじゃない時だけ表示する」の動きを実装できるからです。またどの演算子で更新するかはopeKeyPressed関数の引数として"+"や"-"などの演算子が与えられるのでそれをそのまま設定してしまえばOKです。 src/App.js //..... function opeKeyPressed(key){ setOpe(key); } //..... ここまで実装できれば演算子ボタンを押すことで演算子が表示され、2つ目の数字が入力できるようになっているはずなので確認してみましょう。 ここまででApp.jsは以下のようになっているはずです。 src/App.js import {useState} from "react" ; import Button from "./components/Button" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); function keyPressed(key){ if(ope === null){ setLeft( left*10 + key); }else{ setRight( right*10 + key); } } function opeKeyPressed(key){ setOpe(key); } return ( <div className="calc"> <header>電卓</header> <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} </div> <div className="input"> <div className="numbers"> <Button text="0" onClick={ ()=> {keyPressed(0)} } /> <Button text="1" onClick={ ()=> {keyPressed(1)} } /> <Button text="2" onClick={ ()=> {keyPressed(2)} } /> <Button text="3" onClick={ ()=> {keyPressed(3)} } /> <Button text="4" onClick={ ()=> {keyPressed(4)} } /> <Button text="5" onClick={ ()=> {keyPressed(5)} } /> <Button text="6" onClick={ ()=> {keyPressed(6)} } /> <Button text="7" onClick={ ()=> {keyPressed(7)} } /> <Button text="8" onClick={ ()=> {keyPressed(8)} } /> <Button text="9" onClick={ ()=> {keyPressed(9)} } /> </div> <div className="operators"> <Button text="+" onClick={ ()=> {opeKeyPressed("+")} } /> <Button text="-" onClick={ ()=> {opeKeyPressed("-")} } /> <Button text="*" onClick={ ()=> {opeKeyPressed("*")} } /> <Button text="/" onClick={ ()=> {opeKeyPressed("/")} } /> </div> </div> </div> ); } export default App; ※Button.jsxは変更なし 「=」ボタンを押すと計算結果が表示される さていよいよこの記事も大詰めです。最後に「=」を押すと計算結果を表示してくれるようにしましょう。まず=ボタンを作らないことには始まらないので、数字のボタンの最後にtext="="であるボタンを追加しましょう。 src/App.js //..... <Button text="8" onClick={ ()=> {keyPressed(8)} } /> <Button text="9" onClick={ ()=> {keyPressed(9)} } /> <Button text="=" onClick={ ()=> {/*後述*/} } /> //←ここ //..... そしてまたまたonClickですが、=ボタンが押されたときの処理はこれまた他のボタンとは違った動きをします。なので新しくequalKeyPressed関数を用意してそれを実行するようにしましょう。ただし今回の=ボタンは1つしかないのでわざわざequalKeyPressed("=")としなくてもequalKeyPressed()だけで十分です。 src/App.js function equalKeyPressed(){ //=ボタンが押されたとき } //..... <Button text="=" onClick={ ()=> {equalKeyPressed()} } /> //←ここ //..... あとはequalKeyPressed()が実行されたときにおこる処理を考えるだけです。 ここで記事の初めの方で紹介した設計を思い出してください。 State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 「=」ボタンが押されたときは計算結果を表示しなければいけません。Stateの設計を見る限りその計算結果はansに入ってくると良さそうです。なのでansを更新しましょう。 src/App.js function equalKeyPressed(){ setAns(/*後述*/); } ではsetAnsの中には何を書くべきでしょうか? ここにはleft,ope,rightをもとに計算した結果を入れると良いです。 例えば、 leftに5,opeに"+",rightに7が入っていた場合,ansには12 を入れると良いです。 また、 leftに30,opeに"/",rightに5が入っていた場合,ansには6 を入れると良いです。 つまり、 もし ope が"+" なら setAns(left + right) もし ope が"-" なら setAns(left - right) //以下同様 としていけばいい訳です。これをコードにおこすと、 src/App.js function equalKeyPressed(){ if(ope === "+"){ setAns(left+right); }else if(ope === "-"){ setAns(left-right); }else if(ope === "*"){ setAns(left*right); }else if(ope === "/"){ setAns(left/right); } } となります。 これでボタンを押したときの処理が実装できました。 ですがansはAppコンポーネントのreturn ( ... ) の中にはどこにも書かれていないため、 表示されません。なので right の後に表示するよう追加してみましょう。 src/App.js <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} 答え:{ans} //ここを追加 </div> ここまで来たら計算結果が表示されるはずです。チェックしてみましょう。 うーん、これじゃない感が否めませんが、とりあえず=を押したら計算結果の表示が出来ました! これじゃない感を打破するために、「答え:」を=を押した後だけ表示するようにしてみましょう。これは三項演算子を使うことで手軽に実装できます。 src/App.js { ans === null ? "" : " 答え:"+ans } 三項演算子でansがnullじゃない(=ボタンが押された)ときだけ「 答え:〇〇」というように表示しています。 これで電卓の開発が出来ました!お疲れ様でした! おまけ ところどころに掲載したGithubのページのソースコードとこの記事で完成できるソースコードは多少異なっています。これはあえてです。何が違うのか、どちらの方が適切かぜひご自身で目を通して考えてみてください!(職務放棄)(ボソッ この記事では実装していない機能(完成イメージとちょっと違うところ)がいくつかあるので紹介します。ぜひチャレンジしてみてください。(職務放棄)(ボソッ 計算結果をクリアしてもう一度計算しなおす機能 小数点が入力できる機能 様々な演算子(^, sin, cos, tanなどなど)や特別な数(πやeなど) ボタンの並び方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む