20210116のReactに関する記事は9件です。

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を実行すると万事解決しました。
(めでたしめでたし。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】#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も英語読めないと対処しにくいところあるので、
誰かの助けになればいいなと思って作りながらメモしながら書いてます。

2020-15.png

使用環境

  • 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 start

reactのマークがくるくる回ってたらOK

スクリーンショット 2021-01-15 1.06.38.png

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.css
body {
  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とか。

image.png

では、デフォルトページを修正。
初期状態では、コンポーネントが存在しないで、App.jsに全てが書いてあります。
全部をApp.jsに作ることもできますが、
Reactは機能ごとにコンポーネントに分けて作るのが普通です。

では、Reactマークのくるくるさせるための記述と、
デザインが、App.jsとApp.cssに書かれていますので、
Material-UI使うために、白紙のページにします。

白紙ページにする

  1. App.css中身全部消す
  2. App.jsの項目削除して真っ白なページにする
App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      真っ白
    </div>
  );
}

export default App;

Reactマークが消えて、真っ白とだけ表示されてればOK

グリッド、レスポンシブ

Bootstrapと同じですね。
画面を横方向を12分割にしてその要素は、12個ののうち、何個分の横幅を使うの?
ってやつです。

flexbox使うより簡単にレスポンシブ対応できるので、専門でない人には便利です!

スクリーンショット 2021-01-15 17.06.04.png

https://material-ui.com/components/grid/#grid
※いまいちわからない方は↑ページ開いて、ブラウザのウィンドウを小さくしたり、大きくしたりしてみてください。

material-react/src/App.js
import './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}>

すると、こんな感じ。
スクリーンショット 2021-01-15 2.02.49.png

ブラウザの画面を手動で大きくしたり、小さくしたりして変化するか試してください。

ヘッダーを作る

srcのなかにコンポーネントフォルダを作ります。
その中にHeader.js作成

$ mkdir src/components
material-react/src/components/Header.js
import React from 'react'

function Header() {
    return (
        <div>
            ヘッダー
        </div>
    )
}

export default Header

ヘッダーコンポーネントをApp.jsに追加

作ったヘッダーコンポーネントを親玉のApp.jsの中につっこみます。

material-react/src/App.js
import Header from './components/Header';

・・・

    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
...

単純に「ヘッダー」という文字が出るだけですが、
ヘッダーコンポーネント(ヘッダーの部品)が読み込まれてます。

スクリーンショット 2021-01-15 3.05.16.png

これだとしょぼいので、公式サイトから使用例をパクって改造します。

https://material-ui.com/components/app-bar/
https://material-ui.com/ja/api/app-bar/

基本的なものだけで構成してみました。

material-react/src/components/Header.js
import { 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"にしてます。
これ指定しないと、ヘッダーに要素が重なってしまいます。
スクリーンショット 2021-01-15 3.14.36.png

アイコンをつける

https://material-ui.com/components/material-icons/

検索して良いアイコンを探す。
好きなアイコンをクリックすると下の画像みたいな表示が出る。

今回はこのシルエットみたいなアイコンを試しに貼り付けてみます。
スクリーンショット 2021-01-15 3.17.25.png

importのところをコピーしてHeader.jsに貼り付け。iconを使えるようにします。

Header.js
import AccountCircleOutlinedIcon from '@material-ui/icons/AccountCircleOutlined';

        <AppBar position="static">
            <Toolbar>
                <Typography>ヘッダー</Typography>
                <AccountCircleOutlinedIcon />
            </Toolbar>
        </AppBar>

スクリーンショット 2021-01-15 3.19.26.png
アイコンが出ました。

ただ、普通、こういうのって、左端にありますよね。
ということで、CSSをいじって、調整します。

Material-UIでCSSを追加

makeStyleを使ってみましょう。
Material-UIで作るときにCSSを書くとき使うもののようです。
ファイル一つにたくさんのCSS書くより、コンポーネントに分かれてるし、そこにCSS書けばよくない?
ってことで使われるらしいです。(?)
当然、JSで書くので計算とか、条件とか、Scssっぽく?便利にデザインを適用もできますね。

Header.js
import 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を適用するということです。

スクリーンショット 2021-01-15 3.35.59.png

コンテンツ部分のコンポーネントを作る

$ touch src/components/Content.js

Content.js作成。App.js
ヘッダーの時と同じようにインポートと、<Content />とを入れる。

App.js
import 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.js

Contentコンポーネントの中にBodyCardコンポーネントが入れ子になってます。
App.jsからみるとBodyCardコンポーネントは孫に当たります。

では、Outlined Cardというのを公式ページからパクってきて、

https://material-ui.com/ja/components/cards/

src/components/BodyCard.js
import 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.js
import React from 'react'
import BodyCard from './BodyCard'

function Content() {
    return (
        <BodyCard />
    )
}

export default Content

一つだけは表示できてますね。
スクリーンショット 2021-01-15 4.08.39.png

3個表示すると

Content.js
import { 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

スクリーンショット 2021-01-15 4.12.40.png

スペースを入れて見た目調整

くっついていて見た目良くないので、調整します。

Material-UI公式ページのgridの項目で
spacingの項目で確認してからやるといいかもしれません。

スクリーンショット 2021-01-15 4.21.16.png

spacing={2}足す。もっと開けたいときは数字を大きくするといいです。

Content.js
return (
        <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個並べるようにしました。

スクリーンショット 2021-01-15 4.27.29.png

↓xsサイズにすると

スクリーンショット 2021-01-15 4.31.30.png

カードの見た目改造 カードのヘッダー、クリックできるアイコン

カードの中のヘッダーとクリックできるアイコンを用意します。
今回は⭐️アイコンつけます。

Card要素の一番初めにCardHeaderを追加して、
⭐️アイコンを調べてインポート(StarBorderOutlinedIcon)。
該当箇所に貼り付けます。

BodyCard.js
import 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があるとホバーした時に違い出て、押せる感じになってます。
スクリーンショット 2021-01-15 15.16.25.png

アバターと画像は適当な画像をランダムで取得できるサービスあったのでテストで使います。

https://joeschmoe.io/
https://picsum.photos

BodyCard.js
import { 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を一個めのカードにしか与えてないので、このようになってます。

スクリーンショット 2021-01-15 15.48.56.png

では、残りのカードにも表示されるようにして、

Content.js
import { 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で取得して表示する方法について触れます。

> 次回: 【初心者】#2 React axiosでAPI データ取得

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] Reactの学習をします(1-1)

Reactの学習をします(1)

Reactの学習をしてみることにしました。

教材

Reactチュートリアル1:犬画像ギャラリーを作ろう

likr さんが公開している「Reactチュートリアル1:犬画像ギャラリーを作ろう」という記事を教材に学習させて頂きます。

素晴らしい教材をありがとうございます。

学習日記

本日は「React とは」~「Hello, World!」までやりました!

きちんと Hello, World! が表示されました!

補足

ところで、本題とは全然関係ないのですが、

npm start

を実行しますと「--scripts-prepend-node-path」云々というエラーメッセージが出ていました。

何だろうと検索しますと、検索結果にQiitaの記事が出てきました。

npm runコマンドを実行したら警告が出るようになった

Nodist を使用していると出る警告のようです。

npm config set scripts-prepend-node-path true

記事の内容の通り、上記コマンドを実行すると警告は消えました。

ありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React + TypeScript ] HTMLSelectElementのつもりなのに、Property 'options' does not exist on type 'HTMLElement'.

課題

今日もTypeScriptから怒られた。

something.tsx
const months = document.getElementById('datetime_2i')
months ? (months.options[1].selected = true) : null
エラー
> Property 'options' does not exist on type 'HTMLElement'.

HTMLSelectElementのつもりなんだけどな。

結論

つもりなら書けと言うことらしい。
地味に時間取られる。

something.tsx
const months = document.getElementById('datetime_2i') as HTMLSelectElement
months ? (months.options[1].selected = true) : null

TypeScript素人故怒られまくる。
エラー解決するたびに只管メモします。

参考情報

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React初学者が Function Component + Hooks から学ぶなら

Reactを使い始めた際に、筆者が最初のうち戸惑って調べてみたことをまとめてみました。

この記事の対象の読者

Reactを使い始めるにあたって、散乱している記事のうち、とりあえず何にフォーカスして考えはじめれば良いか迷っている人。
VueやAngularなどを触ったことはあるが、Reactは初めて触る人。
JSの基礎を理解していて、Reactの詳細よりも前に、考え方などを大まかに知りたい人が対象です。

Reactを始めるにあたって

本当に基本的な部分については、Vueのような他のフレームワークと共通しています。
例えば、コンポーネントベースであること、DOM操作を隠蔽してくれるような部分です。

ただ、やはり異なるところも多くあります。
当然、アプリケーションを作成していくことで覚えられる部分も多いですが、事前に知っておいた方が良いことも結構たくさんあります。

全体的な概要を知るためには、まず [React公式のドキュメント] を読むことをおすすめします。
読んでから書き始めてなお、つまづきどころはありますし、振り返って "そういえば公式ドキュメントにも書かれていた"と思うことも結構あります。
ですが、Reactがどういうものなのかを理解するのには重要です。

コンポーネントの書き方について

Function Component と Class Component

Reactのコンポーネントの記述方法は二つあり、検索すると両方が無秩序にヒットします。
そのため、そのコードがどちらで書かれたものなのかをちゃんと理解できるようにしましょう。

二つというのは、Function ComponentClass 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 / 信頼できる唯一の情報源 を活用できるからです。

また、今では ReduxHooks を組みあせて使うこともできます。
その場合は、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の原則

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を使っている最中に直面しがちな問題についての一般的な解決策などを学ぶことができます。
    • 一つ一つのステップは非常にシンプルに記述されており、徐々により具体的な活用方法を示すように作られているので、とてもわかりやすいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React + Typescript] Property 'type' is missing in type but required in type 'AnyAction'

課題

今日もTypeScriptから怒られた。

something.tsx
export default lifecycle({
  async componentDidMount() {
    store.dispatch(getPackages)
  }
})
エラー
> Property 'type' is missing in type '(dispatch: Dispatch) => Promise<void>' but required in type 'AnyAction'.

結論

今日も型だった

something.tsx
export 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.tsx
import React from 'react';

function App() {
  return (
    <div>
      {/* 省略 */}
    </div>
  );
}

export default App;

これをアロー関数で書き換えます。(アロー関数にする諸々のメリットは, this の取り回しやら簡潔化やらと聞いてますが詳細は他の方の記事をご覧ください)

アロー関数版App.tsx
import 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.tsx
const 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> にたどり着きます。定義は以下です。

PropsWithChildren
type 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 } のように ? を使えば後は同じ感じです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
import {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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 にしてしまっている。勝手になんとかしてくれる方法は?

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む