20201114のReactに関する記事は8件です。

react-image-cropで画像のトリミング機能を実装する方法

react-image-cropを用いたトリミング機能を追加するところで躓いたので、自分へのメモとして書きました。

state

index.tsx
const [src, setSrc] = useState<any>(null);
const [crop, setCrop] = useState<Crop>({
    unit: "%",
    x: 0,
    y: 0,
    width: 50,
    height: 50,
    aspect: 1
});
const [imageRef, setImageRef] = useState<HTMLImageElement | null>(null);

画像を読み込む関数

index.tsx
const onSelectFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files !== null) {
        const reader = new FileReader();
        reader.addEventListener("load", () =>
            setSrc(reader.result);
        );
        reader.readAsDataURL(event.target.files[0]);
    }
};

const onImageLoaded = (image: HTMLImageElement) => 
    setImageRef(image);
};

画像をトリミングする関数

index.tsx
const onCropChange = (crop: Crop) => {
    setCrop(crop);
};

const onCropComplete = (crop: any) => {
    if(imageRef && crop.width && crop.height){
        const canvas = document.createElement("canvas")
        const scaleX = imageRef.naturalWidth / imageRef.width;
        const scaleY = imageRef.naturalHeight / imageRef.height;
        canvas.width = crop.width
        canvas.height = crop.height
        const ctx = canvas.getContext("2d")
        if (ctx !== null) {
            ctx.drawImage(
                imageRef,
                crop.x * scaleX,
                crop.y * scaleY,
                crop.width * scaleX,
                crop.height * scaleY,
                0,
                0,
                crop.width,
                crop.height
            )
        }
    }
};

コンポーネント

index.tsx
import React, {useState} from "react";
import ReactCrop, {Crop} from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";

export const CropModal: React.FC<Props> = props => {
    ...
    return (
        <div>
            <input type="file" accept="image/*" onChange={onSelectFile} />
            {src && (
                <ReactCrop
                    src={src}
                    crop={crop}
                    ruleOfThirds
                    onImageLoaded={onImageLoaded}
                    onComplete={onCropComplete}
                    onChange={onCropChange}
                />
            )}

        <div/>
    )
}

今回トリミングした画像データをサーバーにアップロードするには、canvasのデータをBlobデータに変換し、axiosを用いてDataFormとして送る。

canvasの画像データをblobに変換し、axiosでpost.する方法

参考にした記事

react-image-crop - npm
react-image-crop dom CodeSandBox

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

React × TypeScript 入門

はじめに

30代未経験からエンジニアを目指して勉強中のYNと申します。
JavaScriptでReactが書ける人向けの、React × TypeScript入門記事です。

新しいReactプロジェクトつくる

npx create-recact-app <your-pjt-name> --typescript

コードを書き始める前に

SnippetからReactコンポーネントのテンプレートを作る

まずはSnippetを作ると楽です。
VScodeであれば、Snippetを使って一瞬でコンポーネントのテンプレートを持って来れます。
output.gif

cmd + shift + p > Preference:Configure User Snippets > typescriptreactでスニペットの設定ができます。 typescriptreact.jsonを下記のように変更します。

typescriptreact.json
{
  "Typescript React Function Component": {
    "prefix": "rh",
    "body": [
      "import React from 'react'",
      "",
      "interface ${TM_FILENAME_BASE}Props {",
      "$1",
      "}",
      "",
      "export const $TM_FILENAME_BASE: React.FC<${TM_FILENAME_BASE}Props> = ({$2}) => {",
      "\t\treturn ($3);",
      "}"
    ],
    "description": "Typescript React Function Component"
  }
}

オートサジェストを有効にする

TypeScriptを使うメリットの一つに、型指定によるオートサジェストにあります。 試しにpropsの場所でCtrl+Spaceを押すと、指定可能なpropsの一覧が表示されます。
output.gif
VScodeであれば、デフォルトでCtrl+Spaceで有効になっているはずです。
スクリーンショット 2020-11-14 11.43.04.png
もし作動しなければ、(Macの場合)システム環境設定 > キーボード > ショートカット で Ctrl+Spaceの既存ショートカットを無効化しましょう。

Reactコンポーネントの基本

Function Component

interfaceで型を定義し、React.FCにgenericsとして渡します。

TextField.tsx
import React from "react";

interface Props {
  text: string;
  ok?: boolean;
  i?: number;
}

export const TextField: React.FC<Props> = ({text ="hello", i=1, ok=true }) => {
  return (
   ...
  );
};

onClick

マウスホバーすれば、定義すべき型がわかります。

OnClick.tsx
import React from 'react';

interface OnclickProps {
  onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; 
  // divタグのonClickにカーソルを合わせると、この型を指定すればいいことがわかる
  text?: string;
}

const Onclick: React.FC<OnclickProps> = ({ onClick, text }) => {
  return <div onClick={onClick}>{text}</div>;
};

export const Click: React.FC<OnclickProps> = () => {
  const logText = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
    console.log(e.target);
  return <Onclick onClick={logText} text={'hello!!'} />;
};

Hooks

useState

Hooksもgenericsで型を定義します。

Counter.tsx
import React, { useState } from 'react';

interface CounterProps {
  initialCount: number;
}

export const Counter: React.FC<CounterProps> = ({ initialCount }) => {
  const [count, setCount] = useState<number>(initialCount); // countの型をgenericsで指定
  return (
    <>
      <button onClick={() => setCount(count + 1)}>+</button>
      <div>{count}</div>
    </>
  );
};

useReducer

ActionやStateもtypeやinterfaceを使って型を定義します。

Reducer.tsx
import React, { useReducer } from "react";

type Actions =
  | { type: "add"; text: string }
  | {
      type: "remove";
      idx: number;
    };

interface Todo {
  text: string;
  complete: boolean;
}

type State = Todo[];

const TodoReducer = (state: State, action: Actions) => {
  switch (action.type) {
    case "add":
      return [...state, { text: action.text, complete: false }];
    case "remove":
      return state.filter((_, i) => action.idx !== i);
    default:
      return state;
  }
};

export const ReducerExample: React.FC = () => {
  const [todos, dispatch] = useReducer(TodoReducer, []);

  return (
    <div>
      {JSON.stringify(todos)}
      <button
        onClick={() => {
          dispatch({ type: "add", text: "..." });
        }}
      >
        +
      </button>
    </div>
  );
};

参考

Ben Awadさんの動画
Snippet
Gif動画を記事に貼るやりかた

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

React やってみたら超簡単だった話(2020年11月版)

React / Vue.js / Angular / jQuery

Angularjsが出た時に、jQueryでもういいじゃん新しい言語覚えるのめんどくせーよと思いながらやってみたら、超便利で新しい言語ちゃんと覚えないとダメだなと改心したのが遠い分昔。

その後、Angularの頻繁なバージョンアップに嫌気がさしVue.jsを使ってみたら、Angularより随分整理されてて簡単でびっくりしたのが少し昔。
Vuetify とセットで利用するとほとんどコードを書かずにやりたいことができるし、typescript を使うと記述もシンプルで安全だし、もうこれで新しい言語覚えなくてもいいかと油断したのが少し前。

とは言え、Reactも触っておいた方がいいかなと触ったら、他のフレームワークとは概念が違い戸惑ったけれども超簡単でびっくりしたのが今日。

触ってみたいけど難しそうだなと思ってる方は是非触ってみてください。

特に便利と思ったところ

jsx/tsx

js/ts に html を直接埋められる。

以下のコードは等価。

createElement.js
const header = React.createElement("header", null,
  React.createElement("h1", null, "Hello")
);
createElement.jsx
const header = <header>
    <h1>Hello</h1>
</header>

React.createElementの様な書き方しかできないならVue.js圧勝かなと思ったけれども、そんなことはなかった。Flutterもこの辺融通効くようにならないかな…。

ベタのhtmlに近い形で記述できるVue.jsの方がデザイナとの協業に強いと思っていたのだけど、Storybookを併用して開発すれば問題ないようにも思えた。

Function Component

状態を持たないコンポーネントは class ではなく、function で定義できる。
Flutter の、StatefulWidget / StatelessWidget をすごくシンプルにした感じ。

以下のコードは等価。

class-component.jsx
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}
function-component.jsx
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

記述がシンプルなだけでなく、このコンポーネントは状態を持たないことが読み手に一瞬で伝わるのが素晴らしいと思った。

チュートリアル

React公式のチュートリアルが、ステップバイステップでサンプルコードを修正していくものとなっており、Reactの素晴らしさを簡単に理解できる作りとなっていた。

チュートリアル:React の導入

準備

チュートリアルのソースコードは code pen にあるため、ローカルの開発環境作成は必須ではない。
ローカルの開発環境を作成する場合、node と create-react-app のインストールを行う。

choco install nodist
nodist + 10.23.0
nodist 10.23.0
npm i -g create-react-app

2020/11/14時点では、node 8, 10, 12 で動作すると記載があったが、8.17.0, 12.19.0 では動作しなかった。

プロジェクト作成と実行

# js版作成
npx create-react-app my-app
# ts版作成
npx create-react-app my-app --typescript
# 実行
yarn start

以後は、src以下のファイルを全部消して、チュートリアルの指示通りファイルを作成し、一つずつ修正をしていけば完了します。

コンポーネント単位でファイルを分けるなども簡単にできました。

おわりに

とにかく超簡単でした。
vue-cli もそうであるが、webpack.config を手で書かなくて良くなったので誰でも気軽に始められるのでぜひお試しください。

以下の記事もあわせて読んでおくとよいと思います。

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

Firestoreでidのわからないdocumentを取得したいときはどうすればいいのか

firestoreはフロントエンドエンジニアが個人でwebサービスを作る際に、わざわざデータベースを一から作っていく必要がなく、数行のコードでデータを保存したり、取得したりととても便利なサービスです。

ただ、使いはじめの頃、必ずと言っていいほどidのわからない(または自動で付与されたidの)documentをどうやって取得するのかという壁が立ちはだかります。

公式ドキュメントではfirebase.firestore.collection("cities").doc("SF").get()で取得できると書いてあるけど、この例でのSFと言うidがそれ以外、もしくは複数のドキュメントをgetしたい場合にはどうすればいいのかと言う話です。

結論:docを使わなくてもdocumentの取得は可能

公式リファレンスをみていただければわかると思いますが、データの取得に使うget()メソッドは返り値としてQuerySnapshotを返します。

このQuerySnapshotはdocumentのidを持っていますし、このsnapshotからdocumentを取り出すことが可能です。

documentの取り出し方の一例を紹介

ここでは僕のパターン(React)を紹介します。

Example.jsx
const getPosts = () => {
    //空の配列postsを準備します
    let posts = []
    //firebaseお決まりのメソッドでコレクションからデータを取ってきます
    //ここでのポイントは「doc()がいらないこと」です
    db.collection('posts').get()
    //getしたデータに対し、
    .then(snapshot => {
        //docsプロパティ(※)を指定しforEachで各データを取り出します。
        snapshot.docs.forEach(doc => {
            const data = doc.data()
            //準備しておいた配列に取り出したデータをpushします
            posts.push({
                authorName: data.authorName,
                content: data.content,
                createdAt: data.createdAt,
                title: data.title,
                id: doc.id
            })
        })
        //ここはhooksなので気にしなくてOK
        setCurrentPost(posts)
    })
}

※マークをつけたdocsプロパティは、get()メソッドの返り値QuerySnapshotが持つ、プロパティで指定したcollectionのデータを配列として持っています。複雑ですね...w
この辺の返り値や持っているプロパティなどに関しては英語しかありませんがfirebaseの公式リファレンスに細かく説明が載っています。

番外編:documentのidを自動的に付与する方法について

ここも少し迷ったところなので、一応紹介します。

今度は逆にデータを保存する方法についてなのですが、firestoreでは自動idが推奨されていますが、この自動のdocument idをどのようにつけるのかというと、add()を使います。

Form.jsx
    db.collection('posts').add({
      title: title,
      content: content,
      authorName: authorName
    })

このようにaddを使えばデータを保存する時に自動idを生成してくれます。

ちなみにデータを追加する時にset()と言うメソッドも使いますが、これはdocumentのidを指定する必要がありますし、idを自動で生成してくれません。

「じゃあ使わなくていいじゃん」と思うかもしれませんが、これはこれで、データの編集の際に{ merge: true }と引数に指定することで、値が変わっているところだけ更新するので、無駄な書き込みが無いなどのいいところもあります。

このようにしてデータの追加の際はaddにすれば自動でidが生成されるのです。

firebaseは公式ドキュメントを読めば結構理解できる

firebaseはかなり公式のドキュメントが充実しています。

今回のようなわからないメソッドなどがあった場合はリファレンスを見れば、大切なところが全て書いてありますし、導入部分の簡単な使い方については日本語のドキュメントがあります。

ですので、firebase関連でわからないことがあったら、とりあえず公式ドキュメントをみましょうと言うお話でした?

参考

Firebase公式ドキュメント

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

Reactを学ぶIX〜importとexport〜

■ はじめに

タイトルについて記事にしました。
この記事で得る内容は以下の通りです。

・ Reactの基礎知識が増える
・ importとexportの理解とモジュールについて

■ モジュール化とは

→ 大きなものをいくつかにモジュール毎に分割して管理する

・ 他のJavaScript以外の言語では昔からある概念

・ JavaScriptではES2015(ES6)から採用

・ 基本的に1ファイルに1モジュール

・ モジュールを作ったら、任意の場所でモジュールを読み込んで使用できる

■ モジュール化の効果

スクリーンショット 2020-11-13 15.05.34.png

Reactは、このモジュールの概念を強く意識して作られています。
Reactのコンポーネントを作ったらそれが1個のモジュールになっており、それを必要な所で読み込んだり・・・
コンポーネントを分ける事で管理をしやすくしたり・・・
同じUIの所であれば、それを何度も何度も使い回して使ったり・・・
Reactとモジュールの関係はお互いに強く結びついていると言えます

■ モジュールを作る

モジュールを作る時は、exportを使います。exportは名前付きexportと名前なしexportの2種類あります

・ 名前付きexport

→ 1つのモジュールから複数の関数をexportできるが、クラスはexportできない

src/index.js
// 関数を用いる
export function foo() {
    return (<h1>Foooo</h1>)
}

// 或いはアロー関数を使う
export const Bar = () => {
    return (<h1>Baaaar</h1>)
}

exportしたfooという関数を別の所で使ったり、Barという関数をimportして別の所で使うことができます

・ 名前なし(default)export

→ 1ファイル(1モジュール)1つだけexportし、ES6で推奨されているexport方法です

src/Foo.js
export default function Foo() {
    return (<h1>Fooooo</h1>)
}

アロー関数の場合は、export default const hogeの様な書き方はできませんので
先にアロー関数で宣言してからexportします

src/Bar.js
const Bar = () => {
    return (<h1>Baaaar</h1>)
}
export default Bar

そして、名前なしexportであれば、クラスもexportすることができます

src/Hoge.js
// Fugaというクラスを継承したHogeというクラスを作ってexportする

export default class Hoge extends Fuga {
    render() {
        return (<h1>Hoge</h1>)
    }
}

■ import

importする方法は3つあり、モジュール全体をimport・モジュールの一部をimport・別名でimportする方法があります

・ モジュール全体のimport

→ 名前なし(default)exportしたモジュールをimportする時に使う

src/Blog.js
import React from `react`;
import Article from "./Article";

// npmで管理しているreactパッケージの中のReactというモジュールをimportしている
// ArticleファイルからArticleというモジュールをipmortしている
src/Article.js
const Article = (props) => {
    return (<div>Articleです!</div>)
};
export default Article

・ 関数ごとのimport

→ 名前付きexportされたモジュールをimportする時に使う

src/Hoge.js
import { Foo, Bar } from "./FooBar";

// {}内にimportした関数名を記述
// FooBarというファイルから、FooとBarをimportする
src/FooBar.js
export function Foo() {
    return (<h1>Foooo</h1>)
}
export const Bar = () => {
    return (<h1>Baaaar</h1>)
}

// 名前付きexportなので、1ファイルで2つのモジュールがexportされている

・ 別名import

→ importする時は、export元の名前を使うのが一般的だが、別名(エイリアス)をつけてimportできる
・ モジュール全体なら*as name
・ モジュールの一部なら{ A as B }

src/Blog.js
import React from 'react';
import * as AnotherName from './Article' // ① AnotherNameという名前でArticleのモジュールをimportする
import { Foo as MyFoo } from './FooBar'  // ② FooBarファイルからFooという関数をMyFooという名前でimportする

■ 例

① 以前のプロジェクトを流用します。まず、src内に新規ディレクトリを作ります

スクリーンショット 2020-11-14 15.30.42.png

スクリーンショット 2020-11-14 15.33.13.png

② 作成した新規ディレクトリの中に、FooBar.jsxというファイルを作成して、名前付き関数を2つ記述します

FooBar.jsx
import React from 'react'

export function Foo() {
    return(<h2>Foooo</h2>)
}
export const Bar = () => {
    return(<h2>Baaar</h2>)
}

// FooBar.jsxからは、2つ関数をexportしていることになる

③ 作成した新規ディレクトリの中に、Hoge.jsxというファイルを作成して、クラスコンポーネントをexportします

Hoge.jsx
import React from 'react'

export default class Hoge extends React.Component {
    render() {
        return(<h2>Hogeeee</h2>)
    }
}

④ Blog.jsxで先程作ったモジュールをimportしていきます

Blog.jsx
import { Foo, Bar } from './components/FooBar' // 名前付きexport

// 参照先が相対パス(現在変更しているファイル目線)になるので注意

⑤ 名前なしクラスのHoge.jsxもimportしていきます

Blog.jsx
import Hoge from './components/Hoge'

⑥ これらのコンポーネントを並べて実際に使ってみます

Blog.jsx
render () {
  return(
    <>
        <Article title={"Reactの使い方"} isPublished={this.state.isPublished} toggle={() => this.togglePublished()} count={this.state.count} />
        <Foo />
        <Bar />
        <Hoge />
    </>    
  );
}

確認.png

Foo関数とBar関数、hogeクラスを表示することができました

⑦ 別名importでFooBarを読み込むには、以下の書き方をします

Blog.jsx
import * as FooBar from './components/FooBar' // FooBarとい名前でファイル全体をimportする

// 中略

render () {
  return(
    <>
        <Article title={"Reactの使い方"} isPublished={this.state.isPublished} toggle={() => this.togglePublished()} count={this.state.count} />
        <FooBar.Foo />
        <FooBar.Bar />
        <Hoge />
    </>    
  );
}

// FooBarの中のFoo関数とBar関数を読み込む

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

「This is the wrong package! Please install eslint-plugin-jsx-a11y」のエラー

エラー内容

りあクト!の【II.React基礎編】で

$ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin \                                                        
eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import \
eslint-plugin-jsx-ally eslint-config-airbnb

と入力したところ

error /.../hello-world/node_modules/eslint-plugin-jsx-ally: Command failed.
Exit code: 1
Command: node ./postinstall.js
Arguments: 
Directory: /.../hello-world/node_modules/eslint-plugin-jsx-ally
Output:
/.../hello-world/node_modules/eslint-plugin-jsx-ally/postinstall.js:1
throw new Error('This is the wrong package! Please install eslint-plugin-jsx-a11y')

のエラーが出ました

結論

タイポでした!
「eslint-plugin-jsx-a11y」の「a11y」は英字のLの小文字ではなく数字の11でした!

感想

恥ずかしいです。。。

他にも、りあクト!の【II.React基礎編】の少し前のページで

$ yarn eslint --init

でエラーが出て、解決のためにいろいろ調べてなんとか解決したところで、次の会話で柴崎さんが「なおコマンドがエラーで終わっても、とりあえずは気にしなくてもいいから」とおっしゃられていました。
恥ずかしいです。。。

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

Next.js + MaterialUIでfontを指定する

手順

  • 使用したいfontファイル(woff/otfなど)をpublic/fonts配下に設置
  • @font-faceを記載したcssファイルも同様にpublic/fonts配下に設置
  • _document.tsxのheadタグにlinkタグ設置<link rel="stylesheet" href="fonts/fonts.css" />
  • _app.tsx内でProviderに渡すthemeを作成(styles/theme.tsに作成)
  • 作成したtheme.tsを_app.tsxでimportする

実際のコード

public/fonts/fonts.css
@font-face {
  font-family: 'fontname';
  font-weight: normal;
  font-style: normal;
  src: url('./fontname.woff');
}
src/pages/_document.tsx
import React from 'react'
import NextDocument, { Head } from 'next/document'

return (
// 略
 <Head>
   <link rel="stylesheet" href="fonts/fonts.css" />
 </Head>
// 略
)
src/styles/theme.ts
import { createMuiTheme } from '@material-ui/core'

let theme = createMuiTheme({
  typography: {
    fontSize: 16,
    fontFamily: ['fontname'].join(',')
  }
})
export default theme
src/pages/_app.tsx
import React, { useEffect } from 'react'
import {
  ThemeProvider as MaterialUIThemeProvider,
  StylesProvider
} from '@material-ui/styles'

import theme from '../../styles/theme'

const MyApp = ({ Component, pageProps }: any): JSX.Element => {

  return (
      <StylesProvider>
        <MaterialUIThemeProvider theme={theme}>
          <CssBaseline />
          <div>{pageProps.appData}</div>
          <Component {...pageProps} />
        </MaterialUIThemeProvider>
      </StylesProvider>
  )
}

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

React入門 未経験から1週間でReactをマスターする #03. Class ComponentsとFunction Componentsの書き方

目次

1. Reactの新規プロジェクトの立ち上げ
2. コンポーネントのプロパティ(props)とステート(state)
3. Class Components と Function Components←今ここ
4. 条件分岐 (if) と繰り返し (loop) (準備中)
5. フォームと親子間のデータのやり取り (準備中)
6. コンポーネントのライフサイクル (準備中)
7. スタイル (準備中)
8. Higher-Order Component (準備中)
9. Portalを利用したモーダル (準備中)
10. refによるエレメントの取得 (準備中)
11. Contextを利用したテーマの変更 (準備中)

今回の学習内容

今回は、

  • Class Components , Function Components の書き方についての理解
  • Class Componentsでのコンポーネントの書き方

をやっていきます。

YouTubeでの解説動画

YouTubeでも解説しています。
動画で確認したい方はこちらもどうぞ。
【YouTube動画】 未経験から1週間でをマスターするReact入門 #03. Class ComponetnsとFunction Componentsの書き方
【YouTube動画】 未経験から1週間でをマスターするReact入門

この記事のソースコード

ソースコードはGitHubで公開しています。

https://github.com/yassun-youtube/ReactTutorial

ブランチが違うので注意してください↓
スクリーンショット 2020-11-14 1.32.52.png

今回のコミット一覧↓
スクリーンショット 2020-11-14 1.32.45.png

Class Components

少しずつReactの書き方がわかってきたところで、 Class Components と Function Components の話をします。

Reactでのコンポーネントの書き方に、 Class Components と Function Components があります。
Function Components は、本入門で紹介してきた書き方で、 function を定義するとそれがコンポーネントになりました。
Class Components は、 JavaScript のクラスを記載することで、コンポーネントを定義する書き方です。

今は Function Components の書き方が使われることが多くなってきていると思いますが、 Class Components の書き方もまだまだ使われているため、 Class Components の書き方を学んでおく必要があります。

今回は、 App.js を Class Components で書き直してみます。

ブランチの作成

本入門では、すべてのコードを Class Components で記載したものを別ブランチで管理します。

class-components ブランチを作成しましょう。

git checkout -b class-components

App.js の Class Components 化

まず、一番単純なコンポーネントにしてみます。

src/App.js
// 全体的に書き直し
import React from 'react';

class App extends React.Component {
  render() {
    return (
      <div>クラスコンポーネントにしてみました。</div>
    )
  }
}

export default App;

もともと function だったものが、 class に変わっていることがわかると思います。
function だったときは、その return で <div> を返していましたが、 Class Components では
render 関数の返り値が表示するものになります。

画面表示を確認してみましょう。

スクリーンショット 2020-10-31 22.12.29.png

このようになりました。

List.js の Class Components 化

List.js も同じ様に変更します。

src/List.js
import React from 'react';

export class List extends React.Component {
  render() {
    const { title } = this.props;

    return (
      <div>
        <h4>{ title }</h4>
        <div>リストです</div>
      </div>
    )
  }
}

List コンポーネントでは、 props の title という値を利用していました。
Class Components では、 this.props に props が入っています。
Function Components では、下記のように関数の引数に入っていましたね。

const List = ({ title }) => {}

App.jsから List.js を呼び出す

src/App.js
import React from 'react';
import { List } from "./List";

class App extends React.Component {
  render() {
    return (
      <div>
        クラスコンポーネントにしてみました。
        <List title="取り扱い言語一覧" /> // 追加
      </div>
    )
  }
}

export default App;

これは以前行った変更と同じですね。
画面表示は下図のようになります。

スクリーンショット 2020-10-31 22.22.18.png


Class Components での state の利用

App.js で state を利用するようにコードを修正してみます。

App.js
import React from 'react';
import { List } from "./List";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { description: 'クリック前の表示' }
  }

  changeDescription() {
    this.setState({
      description: 'クリック後の表示です。'
    })
  }

  render() {
    const { description } = this.state;
    return (
      <div>
        { description }
        <List title="取り扱い言語一覧" />
        <button onClick={() => this.changeDescription()}>ボタン</button>
      </div>
    )
  }
}

export default App;

同じことができるコードなのですが、 Function Components より少し長いですね。
画面にはボタンが表示されていて、クリックすると下記のように表示されます。

スクリーンショット 2020-10-31 22.40.12.png

コードの解説

Class Components では、 state の初期値は constructor で定義します。

  constructor(props) {
    super(props);
    this.state = { description: 'クリック前の表示' }
  }

ここですね。 constructor は props を引数にとり、それを super(props) で渡す必要があります。
その後、 state を初期化しています。
初期化のときは、 this.state = {} と = を使います。

次に changeDescription を見ていくと、

  changeDescription() {
    this.setState({
      description: 'クリック後の表示です。'
    })
  }

となっていて、 setState という関数で state を更新していることがわかります。
ここで注意して欲しいのが、これは this.state = では動きません。

Reactでは、ステートの更新を明示的に関数で呼び出してやる必要があります。 setState を呼ばれると、Reactがコンポーネントの表示を更新する必要があるとわかるわけですね。

最後に、 button に対しての onClick イベントを追加している部分を見ていきます。

<button onClick={() => this.changeDescription()}>ボタン</button>

ここでは、 onClick で changeDescription メソッドを呼び出しています。

ここで注意してほしいのが、 onClick で changeDescription 関数の参照だけを渡すとエラーになることです。

<button onClick={this.changeDescription}>ボタン</button>

このように記載を変えると、ボタンを押すとエラーになります。

スクリーンショット 2020-10-31 22.37.20.png

参照だけ渡すと this の罠に捕まってしまうので、ちゃんとこのインスタンスが参照されるように意識しましょう。

ちなみに、 bind で this を固定してもちゃんと動きます。
こちらの書き方のほうが好きという方はこちらの書き方を利用しても良いです。

<button onClick={this.changeDescription.bind(this)}>ボタン</button>

今日やったこと

今日やったことをまとめます。

Class Components と Function Componentsの違い

一番単純なコンポーネント

class ClassComponent extends React.Component {
  render() {
    return (
      <div>クラスです。</div>
    )
  }
}
function FunctionComponent {
  return (
    <div>ファンクションです</div>
  )
}

プロパティの違い

Class Componentsでは、

this.props

Function Components

function FuncCompo(props) {}

ステートの違い

初期化

Class Components

constructor(props) {
  super(props);
  this.state = { test: initialValue };
}

Function Components

const [test, setTest] = useState(initialValue);

ステートの変更

Class Components

this.setState({ test: value })

Function Components

setTest(value);

おわりに

これで入門 その2と同様のコードを Class Components の書き方で書くことができました。
今後の入門は、基本的に Function Components の書き方で書いていきますが、 Class Components での書き方も解説していきます。

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