- 投稿日:2021-01-16T21:53:07+09:00
Reactで作成したアプリを起動させようと"npm start"したらエラーが出た
はじめに
個人の忘備録として記載しております。
※右も左も分からない 初学者が記載した内容となります。経緯
参考書をもとにReactのアプリを実行しようとしたら
エラーが発生しました。いままでエラーが発生したことがなかったので
「むむむ?」と思い調べた次第でございます。事象
Reactアプリをwebブラウザ画面で確認するため
$ npm startを実行したところ
npm ERR! code ENOENT npm ERR! syscall open npm ERR! path /var/www/html/~省略~/package.json npm ERR! errno -2 npm ERR! enoent ENOENT: no such file or directory, open '/var/www/html/~省略~/package.json' npm ERR! enoent This is related to npm not being able to find a file. npm ERR! enoent npm ERR! A complete log of this run can be found in: npm ERR! /home/[ユーザー名]/.npm/_logs/~debug.logといったエラーが発生しました。
どうやら
package.json
さんが不在とおっしゃっているようです。「package.jsonさんどこ?」
ちょっと探してみたらいました。
Reactのプロジェクトフォルダの中にいました。
(ったく…、あまり心配させんなよな。)※ちなみにReactのプロジェクトフォルダは
npx create-react-app [プロジェクトフォルダの名前]で作成したやつです。
原因と対策
ということでなんでnpmが"package.json"さんを
見つけられなかったかというと
$ npm start
を実行した際のカレントディレクトリに
package.json
さんがいなかったからです。
package.json
さんがいるディレクトリにて
$ npm start
を実行すると万事解決しました。
(めでたしめでたし。)
- 投稿日:2021-01-16T18:42:00+09:00
【初心者】#1 Reactの基礎とMaterial-UI使って綺麗に作ってみる
Reactでかっこいいページを作りたい!
- 国内ではVue.jsの人気も高いですが、Reactの方が海外では流行っている
React Server Components
なるものが出てきた- 下火気味?だけどReact Nativeをもしやる事になっても、少しは対応しやすくなるかも
ということで、
バックエンドが専門の私ですが、JavaScriptのスキルアップかねてReactをはじめました。シリーズ
随時、ルーティング、ReactでAPI取得、バックエンドでAPIサーバー作ってReactで取得する方法をシリーズで書いていきます?
デザインはMaterial UIに任せよう
デザイナーでもフロントエンジニアでもない私は、CSS Frameworkに頼ります!
情報量の多さから、Material UIを使うのが良いと判断して勉強開始。ReactもMaterial UIも英語読めないと対処しにくいところあるので、
誰かの助けになればいいなと思って作りながらメモしながら書いてます。使用環境
- react 17.0.1
- material-ui 4.11.2
トピック
対象:
- react初心者
- CSS苦手な人へ
- Reactやってみたいけど、デザインが…
- とりあえず見た目が及第点欲しい人の基礎の基礎
やること
- インストール、
react-create-app
から始める- ヘッダー作成
- グリッド(レスポンシブ)
- Reactでのアイコン付け方
- カード、ボタン、画像を使って見た目を整える
環境作る
前提:nodejs入れてない人はインストールしてください。nodejsと調べて、LTSという方をダウンロードしてインストール進めればOKなはずです。
Reactのファイル作りたいところに移動してから、以下でアプリ作ります。
名前はmaterial-react
で作ってみます。$ npx create-react-app material-react $ cd material-react/node_modulesサーバーを起動。
$ npm startreactのマークがくるくる回ってたらOK
Material-UI使えるようにする
公式サイトの通りにインストールしていく。
$ npm install @material-ui/core必須ではないけど、以下をやっていきます。
index.htmlにフォントを追記。titleタグ付近にでも。index.html<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />'Roboto'つかうと書いてあるので、フォントの優先度をあげます。
index.cssbody { margin: 0; font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }アイコンを使うので、インストール。
$ npm install @material-ui/iconsデフォルトのページいらないので変更
軽く説明すると、
- index.htmlの
<div id="root"></div>
この要素をJSでいじりまくってタグとか生成して作っていく感じ- App.jsがJSファイルの親玉?先祖?みたいな感じ
- componentsフォルダ作って、その中に部品となるもの作り、App.jsにコンポーネントを埋め込んで作っていくが一般的。例えば、サイドバー用のJS、ヘッダー用のJSとか。
では、デフォルトページを修正。
初期状態では、コンポーネントが存在しないで、App.jsに全てが書いてあります。
全部をApp.jsに作ることもできますが、
Reactは機能ごとにコンポーネントに分けて作るのが普通です。では、Reactマークのくるくるさせるための記述と、
デザインが、App.jsとApp.cssに書かれていますので、
Material-UI使うために、白紙のページにします。白紙ページにする
- App.css中身全部消す
- App.jsの項目削除して真っ白なページにする
App.jsimport logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> 真っ白 </div> ); } export default App;Reactマークが消えて、真っ白とだけ表示されてればOK
グリッド、レスポンシブ
Bootstrapと同じですね。
画面を横方向を12分割にしてその要素は、12個ののうち、何個分の横幅を使うの?
ってやつです。flexbox使うより簡単にレスポンシブ対応できるので、専門でない人には便利です!
https://material-ui.com/components/grid/#grid
※いまいちわからない方は↑ページ開いて、ブラウザのウィンドウを小さくしたり、大きくしたりしてみてください。material-react/src/App.jsimport './App.css'; import { Grid } from '@material-ui/core'; function App() { return ( <Grid container direction="column"> <Grid item> item1item1item1item1item1item1item1item1item1item1item1item1 </Grid> <Grid item container> <Grid sm={2} /> <Grid xs={12} sm={8}> item2item2item2item2item2item2item2item2item2item2item2item2 item2item2item2item2item2item2item2item2item2item2item2item2 item2item2item2item2item2item2item2item2item2item2item2item2 item2item2item2item2item2item2item2item2item2item2item2item2 item2item2item2item2item2item2item2item2item2item2item2item2 </Grid> <Grid sm={2} /> </Grid> </Grid> ); } export default App;
<Grid container>
という親の中に<Grid item>
という子を入れていきます。
<Grid item container>
分割された子の要素の中をさらに分割するため、container入れてます。
<Grid sm={2} />
これが二つありますが、これが左右の余白です。
xsが超小さい画面の時には余白なし。小さい画面以上の時は、 2/12の空白を入れてます。
2/12が二つなので、本体は残りの8/12。なので
<Grid xs={8}>
ブラウザの画面を手動で大きくしたり、小さくしたりして変化するか試してください。
ヘッダーを作る
srcのなかにコンポーネントフォルダを作ります。
その中にHeader.js
作成$ mkdir src/componentsmaterial-react/src/components/Header.jsimport React from 'react' function Header() { return ( <div> ヘッダー </div> ) } export default HeaderヘッダーコンポーネントをApp.jsに追加
作ったヘッダーコンポーネントを親玉のApp.jsの中につっこみます。
material-react/src/App.jsimport Header from './components/Header'; ・・・ <Grid container direction="column"> <Grid item> <Header /> </Grid> ...単純に「ヘッダー」という文字が出るだけですが、
ヘッダーコンポーネント(ヘッダーの部品)が読み込まれてます。これだとしょぼいので、公式サイトから使用例をパクって改造します。
https://material-ui.com/components/app-bar/
https://material-ui.com/ja/api/app-bar/基本的なものだけで構成してみました。
material-react/src/components/Header.jsimport { AppBar, Toolbar, Typography } from '@material-ui/core' import React from 'react' function Header() { return ( <AppBar position="static"> <Toolbar> <Typography>ヘッダー</Typography> </Toolbar> </AppBar> ) } export default Headerヘッダーなので
position="static"
にしてます。
これ指定しないと、ヘッダーに要素が重なってしまいます。
アイコンをつける
検索して良いアイコンを探す。
好きなアイコンをクリックすると下の画像みたいな表示が出る。今回はこのシルエットみたいなアイコンを試しに貼り付けてみます。
importのところをコピーしてHeader.jsに貼り付け。iconを使えるようにします。
Header.jsimport AccountCircleOutlinedIcon from '@material-ui/icons/AccountCircleOutlined'; <AppBar position="static"> <Toolbar> <Typography>ヘッダー</Typography> <AccountCircleOutlinedIcon /> </Toolbar> </AppBar>ただ、普通、こういうのって、左端にありますよね。
ということで、CSSをいじって、調整します。Material-UIでCSSを追加
makeStyleを使ってみましょう。
Material-UIで作るときにCSSを書くとき使うもののようです。
ファイル一つにたくさんのCSS書くより、コンポーネントに分かれてるし、そこにCSS書けばよくない?
ってことで使われるらしいです。(?)
当然、JSで書くので計算とか、条件とか、Scssっぽく?便利にデザインを適用もできますね。Header.jsimport React from "react"; import { AppBar, Toolbar, makeStyles, Typography } from "@material-ui/core"; import AcUnitRoundedIcon from "@material-ui/icons/AcUnitRounded"; const useStyles = makeStyles(() => ({ typographyStyles: { flex: 1 } })); const Header = () => { const classes = useStyles(); return ( <AppBar position="static"> <Toolbar> <Typography className={classes.typographyStyles}> Anthony sistilli </Typography> <AcUnitRoundedIcon /> </Toolbar> </AppBar> ); }; export default Header;
className
はHTMLのclassのことです。reactの中ではclassNameと書きます。で、クラスをmakeStyleで生成するということで↓追加。
const useStyles = makeStyles(() => ({ typographyStyles: { flex: 1 } }));useStyles作って
const classes = useStyles();
でclasses作って、
classNameにオブジェクトのキー名typographyStyles
を設定しているので
classes.typographyStyles
で、ここの要素にflex: 1
を適用するということです。コンテンツ部分のコンポーネントを作る
$ touch src/components/Content.js
Content.js
作成。App.js
に
ヘッダーの時と同じようにインポートと、<Content />
とを入れる。App.jsimport Content from './components/Content'; ... <Grid container direction="column"> <Grid item> <Header /> </Grid> <Grid item container> <Grid sm={2} /> <Grid xs={12} sm={8}> <Content /> </Grid> <Grid sm={2} /> </Grid> </Grid>コンテンツ部分は
「カード」
を使います。カードの部分もコンポーネントにしたいので
BodyCard.js
を作ります。$ touch src/components/BodyCard.jsContentコンポーネントの中にBodyCardコンポーネントが入れ子になってます。
App.jsからみるとBodyCardコンポーネントは孫に当たります。では、Outlined Cardというのを公式ページからパクってきて、
src/components/BodyCard.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; const useStyles = makeStyles({ bullet: { display: 'inline-block', margin: '0 2px', transform: 'scale(0.8)', }, title: { fontSize: 14, }, pos: { marginBottom: 12, }, }); function BodyCard() { const classes = useStyles(); const bull = <span className={classes.bullet}>•</span>; return ( <Card variant="outlined"> <CardContent> <Typography className={classes.title} color="textSecondary" gutterBottom> Word of the Day </Typography> <Typography variant="h5" component="h2"> be{bull}nev{bull}o{bull}lent </Typography> <Typography className={classes.pos} color="textSecondary"> adjective </Typography> <Typography variant="body2" component="p"> well meaning and kindly. <br /> {'"a benevolent smile"'} </Typography> </CardContent> <CardActions> <Button size="small">Learn More</Button> </CardActions> </Card> ); } export default BodyCardカードを一つだけ表示させてみます。
Content.jsimport React from 'react' import BodyCard from './BodyCard' function Content() { return ( <BodyCard /> ) } export default Content3個表示すると
Content.jsimport { Grid } from '@material-ui/core' import React from 'react' import BodyCard from './BodyCard' function Content() { return ( <Grid container> <Grid item xs={4}> <BodyCard /> </Grid> <Grid item xs={4}> <BodyCard /> </Grid> <Grid item xs={4}> <BodyCard /> </Grid> </Grid> ) } export default Contentスペースを入れて見た目調整
くっついていて見た目良くないので、調整します。
Material-UI公式ページのgridの項目で
spacingの項目で確認してからやるといいかもしれません。
spacing={2}
足す。もっと開けたいときは数字を大きくするといいです。Content.jsreturn ( <Grid container spacing={2}> <Grid item xs={4}> <BodyCard /> </Grid> <Grid item xs={4}> <BodyCard /> </Grid> <Grid item xs={4}> <BodyCard /> </Grid> </Grid> )あとは、好みで調整してみてください。
画面のサイズによってどのような横幅にするかをxs, sm md, lg, xl
で決められます。
Bootstrapと同じ感じ。今回は、スマホなど画面が非常に小さくなると、カードが一つずつ。smサイズで1行に3個並べるようにしました。
↓xsサイズにすると
カードの見た目改造 カードのヘッダー、クリックできるアイコン
カードの中のヘッダーとクリックできるアイコンを用意します。
今回は⭐️アイコンつけます。Card要素の一番初めにCardHeaderを追加して、
⭐️アイコンを調べてインポート(StarBorderOutlinedIcon)。
該当箇所に貼り付けます。BodyCard.jsimport CardHeader from '@material-ui/core/CardHeader'; import Avatar from '@material-ui/core/Avatar'; import IconButton from '@material-ui/core/IconButton'; import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined'; <Card variant="outlined"> <CardHeader avatar={ <Avatar aria-label="recipe" className={classes.avatar}> R </Avatar> } action={ <IconButton aria-label="settings"> <StarBorderOutlinedIcon /> </IconButton> } title="Shrimp and Chorizo Paella" subheader="September 14, 2016" />IconButtonがあるとホバーした時に違い出て、押せる感じになってます。
アバターと画像は適当な画像をランダムで取得できるサービスあったのでテストで使います。
BodyCard.jsimport { CardMedia } from '@material-ui/core'; . . . function BodyCard(props) { const { avatarUrl, title, subheader, text, imageUrl } = props; const classes = useStyles(); const bull = <span className={classes.bullet}>•</span>; return ( <Card variant="outlined"> <CardHeader avatar={<Avatar src={avatarUrl} />} action={ <IconButton aria-label="settings"> <StarBorderOutlinedIcon /> </IconButton> } title={title} subheader={subheader} /> <CardMedia style={{ height: "150px" }} image={imageUrl} /> <CardContent> <Typography variant="body2" component="p"> {text} </Typography> </CardContent> <CardActions> <Button size="small">詳細をみる</Button> </CardActions> </Card> ); }Content.js. . . function Content() { return ( <Grid container spacing={2}> <Grid item xs={12} sm={4}> <BodyCard title="タイトル1" subheader="サブヘッダー1" avatarUrl="https://joeschmoe.io/api/v1/random" imageUrl="https://picsum.photos/150" text="カードの説明1" /> </Grid> <Grid item xs={12} sm={4}> <BodyCard /> </Grid> <Grid item xs={12} sm={4}> <BodyCard /> </Grid> <Grid item xs={12} sm={4}> <BodyCard /> </Grid> </Grid> ) }今は、propsを一個めのカードにしか与えてないので、このようになってます。
では、残りのカードにも表示されるようにして、
Content.jsimport { Grid } from '@material-ui/core' import React from 'react' import BodyCard from './BodyCard' const cardContents = [ { title: "タイトル1", subheader: "サブヘッダー1", avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" }, { title: "タイトル2", subheader: "サブヘッダー2", avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" }, { title: "タイトル3", subheader: "サブヘッダー3", avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" }, { title: "タイトル4", subheader: "サブヘッダー4", avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" }, ] function Content() { const getCardContent = getObj => { return ( <Grid item xs={12} sm={4}> <BodyCard {...getObj} /> </Grid> ); }; return ( <Grid container spacing={2}> {cardContents.map(contentObj => getCardContent(contentObj))} </Grid> ) } export default Content同じ画像になりますができました。
次回はデータをAPIで取得して表示する方法について触れます。
- 投稿日:2021-01-16T18:31:04+09:00
[React] Reactの学習をします(1-1)
Reactの学習をします(1)
Reactの学習をしてみることにしました。
教材
likr さんが公開している「Reactチュートリアル1:犬画像ギャラリーを作ろう」という記事を教材に学習させて頂きます。
素晴らしい教材をありがとうございます。
学習日記
本日は「React とは」~「Hello, World!」までやりました!
きちんと Hello, World! が表示されました!
補足
ところで、本題とは全然関係ないのですが、
npm start
を実行しますと「--scripts-prepend-node-path」云々というエラーメッセージが出ていました。
何だろうと検索しますと、検索結果にQiitaの記事が出てきました。
Nodist を使用していると出る警告のようです。
npm config set scripts-prepend-node-path true
記事の内容の通り、上記コマンドを実行すると警告は消えました。
ありがとうございます。
- 投稿日:2021-01-16T18:09:56+09:00
[React + TypeScript ] HTMLSelectElementのつもりなのに、Property 'options' does not exist on type 'HTMLElement'.
課題
今日もTypeScriptから怒られた。
something.tsxconst months = document.getElementById('datetime_2i') months ? (months.options[1].selected = true) : nullエラー> Property 'options' does not exist on type 'HTMLElement'.HTMLSelectElementのつもりなんだけどな。
結論
つもりなら書けと言うことらしい。
地味に時間取られる。something.tsxconst months = document.getElementById('datetime_2i') as HTMLSelectElement months ? (months.options[1].selected = true) : nullTypeScript素人故怒られまくる。
エラー解決するたびに只管メモします。参考情報
Property 'selectedOptions','selectedIndex','options' does not exist on type 'HTMLElement'
https://sharepoint.stackexchange.com/questions/283252/property-selectedoptions-selectedindex-options-does-not-exist-on-type-htm
- 投稿日:2021-01-16T17:53:42+09:00
React初学者が Function Component + Hooks から学ぶなら
Reactを使い始めた際に、筆者が最初のうち戸惑って調べてみたことをまとめてみました。
この記事の対象の読者
Reactを使い始めるにあたって、散乱している記事のうち、とりあえず何にフォーカスして考えはじめれば良いか迷っている人。
VueやAngularなどを触ったことはあるが、Reactは初めて触る人。
JSの基礎を理解していて、Reactの詳細よりも前に、考え方などを大まかに知りたい人が対象です。Reactを始めるにあたって
本当に基本的な部分については、Vueのような他のフレームワークと共通しています。
例えば、コンポーネントベースであること、DOM操作を隠蔽してくれるような部分です。ただ、やはり異なるところも多くあります。
当然、アプリケーションを作成していくことで覚えられる部分も多いですが、事前に知っておいた方が良いことも結構たくさんあります。全体的な概要を知るためには、まず [React公式のドキュメント] を読むことをおすすめします。
読んでから書き始めてなお、つまづきどころはありますし、振り返って "そういえば公式ドキュメントにも書かれていた"と思うことも結構あります。
ですが、Reactがどういうものなのかを理解するのには重要です。コンポーネントの書き方について
Function Component と Class Component
Reactのコンポーネントの記述方法は二つあり、検索すると両方が無秩序にヒットします。
そのため、そのコードがどちらで書かれたものなのかをちゃんと理解できるようにしましょう。二つというのは、
Function Component
とClass Component
の二つです。
- 現在(執筆時点)のトレンドは
Function Component
の方です。これは、
ReactHooks
がリリースされたことで、Class Component
の冗長な記述が不要になって、シンプルに記述できるようになったから、というのが理由の一つに挙げられるようです。もちろん
Class Component
で書いていけない訳ではありません。
サポートされていますし、多くのOSSがClass Component
の記述方式で書かれています。ただ、もし、全く新しくプロジェクトを始めるのであれば、より書き方が簡潔な
Function Component
の方をおすすめします。また、既に
Class Component
の形式で書いている部分がある場合でも、部分的にFunction Component
を利用できます。
その場合、単にFunction Component
の形式で書けば良いだけです。Function Componentでの開発方法
Function Componentでは、これまでのComponentベースの開発方法に加えて、
React Hooks
を使った開発が推奨されています。
React Hooks
の主だったHooksには以下のようなものがあります。このあたりは全て、オフィシャルのドキュメントから探すことができます。
また、これに加えてReactコミュニティが作成している以下のようなHooksがあります。リンク先を少し見てみるとわかりますが、本当にたくさんのHooksがあります。
そのため、Hooksを作りたくなるような場面に遭遇したら、まず先に誰かが既に作っていないか検索してみることをおすすめします。また、実際の開発では、さらに、
自分たちでアプリケーション用のカスタムフックを作成しながら
、開発していくことになります。Hooksの制限
Function Component
に直接書かれている必要があります
Class Component
やプレーンなJS
に書かないようにしてください条件文やループ文の中、ネストされた関数内に書くことはできません
Hooksは、条件文やループの中、ネストされた関数の中には書かないでください
書いた場合、フックの順序が変更され、バグを引き起こすことがあります
- 上記の制限にひっかかる場合は、一般的にはコンポーネントを分割して、Hooksをコンポーネントの中に条件やループなしで書いた上で、コンポーネントを条件にやループによって呼び出すように変更します
Hooksの命名
- 自身でカスタムフックを作る場合は、接頭辞に
use
をつけるようにしましょう。
- ほとんどのフックには、コミュニティのライブラリまで含めて、
useSome
のように命名されています。- 同様の命名を行うことで、可読性を維持でき、誤解を招かずにすみます。
Reactにおける状態管理
Reactには状態管理の方法がいくつか提供されています。
そのうち、Hooksで提供されているのは
- useState
- useReducer
を使った状態管理です。
StateとReducerの使い分けは、Stateは単純な状態管理を、Reducerはより複雑な状態管理を行うという感じで使います。あるいは、Hooksの登場前からよく使われている [Redux] を使う選択肢もあります。
ReduxはHooksのState,Reducerを使った状態管理よりも、さらに複雑な状態管理を行いたい場合に使用します。
具体的には、"状態を他のコンポーネントとも共有したいようなケース"が増えてきた場合は、Reduxを使う方が開発が楽になります。これはState,Reducerを使った状態管理がコンポーネント内(ローカル)に閉じているのに対し、Reduxの状態管理はグローバルで行うため、他のコンポーネントでも状態を共有できるからです。
つまり、Reduxの原則の一つのSingle source of truth / 信頼できる唯一の情報源
を活用できるからです。また、今では
Redux
とHooks
を組みあせて使うこともできます。
その場合は、Reduxのconnect関数を使うための面倒な手順を簡素化できるため、新しくReduxを使う際には、Hooksと組み合わせて使うと良いと思います。参考: Using Hooks in a React Redux App
参考: Reactのステート管理方法まとめ他のキャッシュ層との兼ね合い
GraphQL
などを使っている場合は、AppoloClient
などを使っていることもあるでしょう。
その場合は、Clientがキャッシュを持っているので、このキャッシュを単純なStore代わり
にすることもあります。APIから、情報を受け取り、それをレンダリングし、ある程度扱いやすいUIを提供するのが主目的のアプリケーションなら、Reduxも使わず、State,Reducerも最低限で済ます構成も充分に考えられます。
構造も単純になり、開発も早いです。状態管理のまとめ
状態管理は、アプリケーションの規模や構成によって、適切なものを選ぶのが良さそうです。
- 小さいアプリケーション、あるいは、構成がシンプルなアプリケーションではState,Reducerを使った最低限の状態管理。
- 中規模以上のアプリケーション、一画面上で様々な表現を一度に行うようなアプリケーションではReduxを使った状態管理。
という感じになるかと思います。
アプリケーション規模が小さいからといって、必ずしもHooksが推奨されるわけではなく、他のコンポーネントとどの程度状態を共有する必要があるか、が指標になるという点については注意が必要かと思います。
幾つかの原則
Reactのコンセプト
Declarative (宣言的な View)
宣言的に書くというのは、やりたいことを手続き的に書くのではなく、
何をしたいのか
を書く、ということです。
ReactはDOMを操作する部分を隠蔽してくれるので、プログラマは何をしたいのか
の部分に集中して書くことができます。本質的には、できるだけ宣言的に書いたほうが良い、というのはReactに限ったことではありません。
通常のプログラミングでも、できるだけ宣言的に書いた方が良いです。Component-Based (コンポーネントベース)
Reactでは(当然のことながら)コンポーネントを中心に開発が行われます。
設計も画面デザインを基に、どのようにコンポーネントを分割するか/あるいは共有するか、を最初に考えるのが基本です。
コンポーネントを共有する場合には、共有するコンポーネントがCSSに直接依存していると使いにくい場合も多いです。また、Hooksが出てきたことによって、Hooksをどのように活用すべきか、という点もちゃんと考えた方が良いと個人的には思います。
Learn Once, Run Anywhere (一度学習すれば、どこでも使える)
ReactはReact Nativeなどネイティブアプリ向けでも同じようなニュアンスで開発を行うことができます。(完全に同じではありませんが)
ネイティブアプリ開発者が社内にいない/少ない場合には、Webアプリ開発者のリソースで開発できる、という点でメリットがあります。Default関数 / Renderメソッド
Function Component
では、Default関数
、Class Component
ではRenderメソッド
にあたります。
この二つの関数/メソッドは出来るだけ副作用がないように作成します。
完全にはできませんが、できるだけ副作用がないように作ることで、コンポーネントの構成をシンプルに保つことができます。1コンポーネント1機能(単一責任の原則)
Reactに限らず、プログラミングでは頻出の原則です。
一つのコンポーネント、あるいは一つの関数、メソッドは、一つの機能を実現するように作る、という原則です。この原則を守ることで、コンポーネントや関数がシンプルに保たれます。
一つの関数内の行数が少なくなりますし、全体的に可読性を維持できます。テストもしやすく、変更による影響の見通しもよくなります。Reduxの原則
- Single source of truth(信頼できる唯一の情報源)
- State in read-only(stateは読み取り専用にする)
- Changes are made with pure functions(変更はすべて純粋関数で行われる)
ReactでのAPIからのデータのフェッチについて
多くのケースでは、ReactとAxiosなどのAPIClientを用いて、バックエンドのAPIサーバからデータを取得してきて、それを画面に表示させる部分があると思います。
データのフェッチをどこで行うか、については基本的には以下のように考えると良いと思います。
- データを使うコンポーネントの親になるコンポーネントでフェッチする
- 自分でデータを使い、他にデータを使うコンポーネントがないなら自身でフェッチする
- データのロード中を示すぐるぐるが自分のコンポーネントより上になるなら、ぐるぐるを表示しているコンポーネントでフェッチする
これには、データをフェッチする回数を減らす意図と、データのローディング表示をシンプルに処理する意図があります。
場合によっては、このケースから外れることもあるかもしれませんが、基本的にはこの考え方で組んでいって問題ないと思います。参考: How to fetch data in React
Componentとスタイルの分離
Componentの再利用性を上げる方法として、Componentとスタイルを分離する方法があります。
Reactの考え方は、そもそもComponent Basedなので、できるだけ適切にComponentを分離するのはとても大切です。
ただ、Componentを綺麗に分離していても、Component自身がデザインに依存していると再利用性は極端に下がってしまいます。そのため、再利用性を上げるためにComponentとスタイルを分離します。
基本的には、Componentをラッピングしてスタイルを与えるだけのComponentを作成して実現します。
これは一つづつ作成するというよりは、複数のComponentをまとめている親のコンポーネントから与える形になることが多いです。重要な点は、Component自身がスタイルを知らなくて良いようにしておくことです。
もちろん全てのComponentをスタイルと分離する必要はありません。
大きめのComponentはそもそもデザインに依存していることも多いです。また、明らかに再利用できる必要がないことも多いです。
逆に小さめのComponentはデザインに依存していると再利用できないので、できるだけスタイルを分離しておくようにします。学習時の参考に
React で作られた OSS
書籍
- React開発 現場の教科書
- 実践的な書き方を伝える本ではありません。考え方を伝える本です。コードも記載されていますが、あくまで考え方を理解するためのものとして読むものだと考えると良いと思います。
- React Hooks
- React Hooksについて、ステップバイステップで丁寧に説明している書籍です(英語です)。Hooksの使い方を初心者向けに解説しています。useStateを簡易的に自身で再実装したり、Hooksを使っている最中に直面しがちな問題についての一般的な解決策などを学ぶことができます。
- 一つ一つのステップは非常にシンプルに記述されており、徐々により具体的な活用方法を示すように作られているので、とてもわかりやすいです。
- 投稿日:2021-01-16T17:46:09+09:00
[React + Typescript] Property 'type' is missing in type but required in type 'AnyAction'
課題
今日もTypeScriptから怒られた。
something.tsxexport default lifecycle({ async componentDidMount() { store.dispatch(getPackages) } })エラー> Property 'type' is missing in type '(dispatch: Dispatch) => Promise<void>' but required in type 'AnyAction'.結論
今日も型だった
something.tsxexport default lifecycle({ async componentDidMount() { store.dispatch<any>(getPackages) } })TypeScript素人故怒られまくる。
エラー解決するたびに只管メモします。参考情報
Typescript error when dispatching a thunk: Argument of type 'ThunkAction>' is not assignable to parameter of type 'AnyAction'.
https://github.com/reduxjs/redux-toolkit/issues/587
- 投稿日:2021-01-16T17:30:05+09:00
React の Functional Component で引数の記載方法を色々試す(TypeScript)
概略
前回の記事1で React アプリ(TypeScript 版)の立ち上げをしてみました。
今回は, Create React App によって初期生成されたアプリをアロー関数に書き換えてみたり, Functional Component の記載方法をいろいろ試した内容をまとめています。(参考)現在の環境の状態
Version 情報 React 17.0.1 Node.js 14.15.4 TypeScript 4.1.3 App.tsx をアロー関数に書き換える
Create React App で生成される
App.tsx
は Functional Component になっています。(function App()
のあたりです)
React Hooks 登場により, Functional Component でも state 管理などできるようになり, 基本的には Functional Component 使っていくのが主流のようです。(以前は, Class Component が主)生成時のApp.tsximport React from 'react'; function App() { return ( <div> {/* 省略 */} </div> ); } export default App;これをアロー関数で書き換えます。(アロー関数にする諸々のメリットは,
this
の取り回しやら簡潔化やらと聞いてますが詳細は他の方の記事をご覧ください)アロー関数版App.tsximport React from 'react'; // React.FC は省略可能 // const App = () => { const App: React.FC = () => { return ( <div> {/* 省略 */} </div> ); } export default App;変わったのは
function App
の行あたりです。コメント記載の通り, App 関数の型定義にあたるReact.FC
は省略可能です。ただ, 指定してあげる方が明瞭で好みです。Functional Component の引数指定をいろいろ試す
以降はすべてアロー関数で記載していきます。
前述のApp.tsx
は, 引数を受けずに処理を実施しています。では, 引数を受ける Functional Component の書き方とは ... ?と思って調べるといろいろ出てきて困ったのでちょっとだけ試したものをまとめてみます。引数を受けなくて良い場合のパターン
これは
App.tsx
と同じです。値を親から子へ受け渡す必要がない場合は, 以下のように書きます。
順に, React.FC の記載なし版と記載あり版です。React.FCなしconst NoArgsComponent1 = () => { return ( <div> NoArgsComponent1 </div> ); }React.FCありconst NoArgsComponent2: React.FC = () => { return ( <div> NoArgsComponent2 </div> ); }Component の使い方
上記 Functional Component を作成したあとに, Component を差し込みたいところで以下のように記載すれば OK。(例えば
App.tsx
)使い方const App: React.FC = () => { return ( <div> <NoArgsComponent1 /> <NoArgsComponent2 /> </div> ); }引数を受けて何かしたいパターン
Component にデータを受け渡して表示させるなどの場合, Functional Component の引数を真面目に設定します。
単純なパターン
アロー関数の引数に指定します。(例は arg1, arg2 を設定)
props の型定義として, arg1/arg2 を宣言する形です。引数内で指定const WithArgsComponent = (props: { arg1: number, arg2: string }) => { return ( <div> WithArgsComponent (arg1 = {props.arg1}, arg2 = {props.arg2}) </div> ); }React.FC を記載するパターン
上記単純なパターンを, React.FC を用いて記載すると以下になります。(React.FC のジェネリクスに, 引数で受けたいものの型定義を指定します)
React.FCを使用するconst WithArgsComponent: React.FC<{ arg1: number, arg2: string }> = (props) => { return ( <div> WithArgsComponent (arg1 = {props.arg1}, arg2 = {props.arg2}) </div> ); }
props
については, オブジェクトの分割代入に置き換えて以下の書き方でも良いかと思います。FCを使用する+分割代入で受け取りconst WithArgsComponent: React.FC<{ arg1: number, arg2: string }> = ({ arg1, arg2 }) => { return ( <div> WithArgsComponent (arg1 = {arg1}, arg2 = {arg2}) </div> ); }また, ジェネリクスに指定した内容を
type
で定義してあげると見通しがよくなります。引数の型定義を外だしするtype Props = { arg1: number; arg2: string; } const WithArgsComponent: React.FC<Props> = ({ arg1, arg2 }) => { return ( <div> WithArgsComponent (arg1 = {arg1}, arg2 = {arg2}) </div> ); }Component の使い方
といろいろ書いたところで, 上記 Component を使用する際には以下のように記載します。(引き続き
App.tsx
)
HTML の属性の記載風に, 引数が受け渡しできる感じですね。使い方const App: React.FC = () => { const num: number = 1; const str: string = 'sample'; return ( <div> <WithArgsComponent arg1={1} arg2='sample' /> {/* 値を直接入れる */} <WithArgsComponent arg1={num} arg2={str} /> {/* 変数値を入れる */} </div> ); }Child Component を受け渡す場合
Component が Child Component を持つ場合, それらを受け渡すこともできます。
例えば, 以下のように対象の Component(WithArgsComponent
) に Child Component が存在する場合,App.tsxconst App: React.FC = () => { return ( <div> <WithArgsComponent arg1={1} arg2='sample'> <div> Child ! </div> </WithArgsComponent> </div> ); }
WithArgsComponent
を以下のように記載すれば, Child Component である<div> Child ! </div>
を描画できるようになります。(React.FC を使わない単純な記載です。)childrenを受け取るconst WithArgsComponent = (props: { arg1: number, arg2: string, children: React.ReactNode }) => { return ( <div> WithArgsComponent (arg1 = {props.arg1}, arg2 = {props.arg2}) {props.children} </div> ); }
WithArgsComponent
箇所の出力は以下な感じになります。出力<div> WithArgsComponent (arg1 = 1, arg2 = sample) <div> Child ! </div> </div>さて, 単純なパターンで記載しましたが, React.FC で記載すると以下のようになります。
さっきの単純な書き方例のchildren: React.ReactNode
の記載がなくても分割代入で受け取れています。React.FCを用いてchildlenを受け取るtype Props = { arg1: number; arg2: string; } const WithArgsComponent: React.FC<Props> = ({ arg1, arg2, children }) => { return ( <div> WithArgsComponent (arg1 = {arg1}, arg2 = {arg2}) {children} </div> ); }この違いは,
React.FC
の定義を追いかけてみると,React.FC<P>
->FunctionComponent<P>
->PropsWithChildren<P>
にたどり着きます。定義は以下です。PropsWithChildrentype PropsWithChildren<P> = P & { children?: ReactNode }React.FC のジェネリクスで指定される
P
と,children?: ReactNode
を&
でつないでいます。なので,React.FC<Props>
の記載のみで,children
も受けることができるようになっています。まとめ
Create React App で作成された
App.tsx
内の Functional Component をアロー関数に書き換えつつ, Functional Component の引数の記載方法をいろいろ試してみました。
ちなみに Optional な引数を入れたい場合は{ arg: number, optionalArg?: number }
のように?
を使えば後は同じ感じです。
- 投稿日:2021-01-16T16:48:40+09:00
JavaScriptのオブジェクト比較をできるだけ短いコードで行う
はじめに
Reactを書いていて、initialState(初期ステート)とstate(現在のステート)の比較を行いたいことがあった。
しかし、意外とJavaScriptではオブジェクトの比較が難しかったので、できるだけ短いヘルパー関数を作ったので共有したい。結論
helper.js/* オブジェクトをソート済み配列に変換する */ const objToSortedArray = obj => Object.entries(obj).sort() /* ソート済み配列を文字列に変換して比較する */ const isEqualOneDimentionalArray = (obj1, obj2) => JSON.stringify(objToSortedArray(obj1)) === JSON.stringify(objToSortedArray(obj2)) /* 再帰処理を行い、ネストされたオブジェクトまで比較する */ export const isEqual = (obj1, obj2) => isEqualOneDimentionalArray(obj1, obj2) && objToSortedArray(obj1).map(([key, val]) => typeof val === "object" ? isEqual(val, obj2[key]) : true)howToUse.jsimport {isEqual} from "./helper.js" const initialState = { tmpConditions: { target: "makeup", personalColor: false, faceType: false, items: [] } } const state = { tmpConditions: { target: "makeup", personalColor: true, faceType: false, items: [1, 23] } } console.log(isEqual(initialState, state)) // false
- 投稿日:2021-01-16T14:28:22+09:00
create-react-app でも WebWorker を使う (TypeScript 対応)
概要
create-react-app は React で開発を始めるのに便利 だが、重い計算をバックグラウンドで行いたくなったので WebWorker を使おうとしたら サポートされていない ことがわかった。WebWorker 自体 onmessage/postMessage のような低レベル API を使用しないといけないようで型の恩恵を受けづらいのが難だなと調べていたら dominique-mueller/create-react-app-typescript-web-worker-setup を見つけた。だいたいこのとおりでうまくいった。
ReferenceError: $RefreshReg$ is not defined
しばらく悩まされたランタイムエラーがこれ。worker 内で起こっていて、シンボルの名前で調べてみると react-refresh に関係している らしい。トランスパイル後の JavaScript なんて読めないよと思いつつ何に対応して reference が生成されているか追っていったら 関数名が大文字で始まっている のを見つけた。まあ関係ないだろうと思いつつ直しておくとエラーがなくなった…。
たしかに React コンポーネントは大文字から始まるし、と react-refresh を見返すと isComponentishName 判定を通ったものは他のシンボルと違う処理が入っているらしい。
手順
詳しくは dominique-mueller/create-react-app-typescript-web-worker-setup を。
準備
- react-app-rewired, worker-loader を入れる:
package.json
- react-app-rewired の設定を行う:
package.json
,react-app-rewired.config.js
- .worker.ts で終わる名前のファイルは worker-loader を使って変換する、というような意味
使用
Worker 側では必要なものを expose する:
import { expose } from 'comlink' import { f } from 'mylibrary' export default {} as typeof Worker & { new (): Worker } expose({f})使う側では wrap すると Promise が返ってくる:
import { wrap } from 'comlink' import MyWorker from 'My.worker' const myWorker = wrap(new MyComlinkWorker()) as { f: (x: number) => Promise<number> } : myWorker.f(x).then(g)TODO
as 以下を書くのが面倒なので any にしてしまっている。勝手になんとかしてくれる方法は?
参考
- Comlink, WebWorker については WebWorkerをenjoyableにするComlinkとは何者か に詳しい。
- create-react-app と TypeScript の組み合わせは create-react-appで React + Typescript な環境を構築する にある。