20210118のReactに関する記事は15件です。

React17 Babel7.12.12で、HTML CDN を使ってReactアプリ試作環境を作る

はじめに

タイトルの通りです。

React16 CDN でHelloWorld JSXありなし両方 - Qiita
https://qiita.com/standard-software/items/0cdbcdb2a6d6355c946b

上記記事の最新バージョンです。

先の記事では、HTMLファイル単独でした。今度はjsファイルを別にして開発します。

あとで書きますが、HTML ファイルと Jsファイルを別々にするので、Webサーバーを使って開発する必要があります。

ファイル

ファイルです。

<!DOCTYPE html>
<html>
  <head>

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="./Button.js" type="text/babel"></script>
    <script src="./Application.js" type="text/babel"></script>

  </head>
  <body>
    <div id="root"></div>

  </body>
</html>

Application.js
class Application extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
    }

    this.onClick = this.onClick.bind(this);
  }

  onClick() {
    let { counter } = this.state;
    counter += 1;
    this.setState({ counter });
  }

  render() {
    return (
      <div>
        <span>{this.state.counter}</span>
        <Button
          onClick={this.onClick}
          text={this.state.counter}
        />
      </div>
    );
  }
}

ReactDOM.render(<Application />,
  document.getElementById('root'));
Button.js
class Button extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <button
        onClick={this.props.onClick}
      >
        {this.props.text}
      </button>
    );
  }
}

これらを同一フォルダに配置して、Webサーバー経由で起動してください。

すごく簡単ですが次のようなボタンを押すと、テキストとボタンテキストがカウントアップされるページが表示されます。

image.png

Webサーバーを起動するには次の記事などご参考ください。

ライブリロード(ファイル更新監視)してくれるWebサーバー browser-sync のはじめの一歩 - Qiita
https://qiita.com/standard-software/items/7a1d237133f0fe7961fd

node.js http-serverコマンドでwebサーバーを起動する - Qiita
https://qiita.com/standard-software/items/1afe7b64c4c644fdd9e4

ローカルファイルでブラウザ起動はできない

HTMLをローカルファイルとしてブラウザ起動だと次のようなエラーが出てしまうのでご注意ください。
jsファイルが他のサイトにあるから動かないよ、というエラーです。

画面はWindowsです。

Access to XMLHttpRequest at 'file:///C:/.../Button.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
babel.min.js:1 GET file:///C:/.../Button.js net::ERR_FAILED
(anonymous) @ babel.min.js:1
yq @ babel.min.js:1
(anonymous) @ babel.min.js:1
Cq @ babel.min.js:1
kq @ babel.min.js:1
index.html:1 Access to XMLHttpRequest at 'file:///C:/.../Application.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
babel.min.js:1 GET file:///C:/.../Application.js net::ERR_FAILED

JavaScriptコードがHTMLファイル内にかかれていれば、ローカルファイル起動でも動くのですが、jsファイルがHTMLファイルと別になっていると、ローカルファイル起動では動きません。

以上です。

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

React/TypeScript: useStateのコールバック関数をpropsで受け取る時のtypeの定義の仕方備忘録

DispatchとsetStateActionを使う。

コード

子コンポーネント
import React, { Dispatch, SetStateAction } from "react";

type Hoge = {
  fuga: string;
  setFuga: Dispatch<SetStateAction<string>>;
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] Reactの学習をします(1-3)式の埋め込み

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

Reactの学習をしてみることにしました。
前回の記事 : [React] Reactの学習をします(1-2)コンポーネントの分割

教材

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

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

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

学習日記

本日は「式の埋め込み」~「繰り返し」までやりました!

式の埋め込み

※ 教材から箇条書き的に抜粋させて頂きます。

・JavaScript の式を JSX に埋め込むためには {式}のように書きます。

この {式} の使い方はとても重要ですね。

コンポーネントと props

※ 教材から箇条書き的に抜粋させて頂きます。

・次は親コンポーネントから子コンポーネントへプロパティを渡す方法を扱います。

・呼び出し側の関数である親コンポーネントから、呼び出される側の関数である子コンポーネントへ props を通じてプロパティを渡すことができます。
props は子コンポーネントの関数の引数となります。

・JavaScript の式を渡したい場合は{}を使ってください。

・子コンポーネントでは、関数の仮引数の props を通じて渡されたプロパティにアクセスすることができます。

繰り返し

※ 教材から箇条書き的に抜粋させて頂きます。

・これまでは 1 枚の画像のみを表示していましたが、Dog API から複数の画像の URL を取得してそれを一度に表示してみることを考えてみましょう。

・URL の配列を JSX 式の配列に変換すると考えると良いでしょう。
このような処理は配列の map メソッドでできました。

・map メソッドで作られる JSX 式は、最も外側の要素に key 属性を付けなければいけません。

App.js
function Gallery(props) {
  const { urls } = props;
  return (
    <div className="columns is-vcentered is-multiline">
      {urls.map((url) => {
        return (
          <div key={url} className="column is-3">
            <Image src={url} />
          </div>
        );
      })}
    </div>
  );
}

(Galleryコンポーネントの部分だけ抜粋させて頂きました。)

※ 下図のように表示させることができました!ありがとうございます!

024a.png

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

React-Calendarで超簡単にカレンダーを作ってみた

会社のイベントとして、2020年のアドベントカレンダーで記事を書いたのですが、個人のQiitaでも同じものを公開したいな〜と思ったので、ちょっとだけ変えて公開します!

去年の案件で急にReactでカレンダーを作ることになり、有識者もいない中、四苦八苦したのですが、ライブラリって便利だな〜と感じたので、Reactのreact-calendarを使ってカレンダーを作成したときのことを書きます。

※環境構築とかは端折ります。

react-calendarとは

カレンダー実装だともっとメジャーなライブラリがあるかも知れないんのですが、今回、私はreact-calendarを使ってみたのですが、簡単にReactでカレンダーを実装できちゃいました。

条件を絞って、日付ごとに任意の内容を表示させたり、スタイルのカスタマイズなど 柔軟に対応できます。

公式:react-calendar

react-calendarで実装してみる

react-calendarをインストール

yarnで「react-calendar」をインストールします。

yarn add react-calendar

react-calendarをimportする

react-calendarをimportしてCalendarコンポーネントを呼び出します。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    render() {
        return(
            <Calendar />
        )
    } 
}

これだとまだ動きません!

カレンダーに日付を表示する

カレンダーのタイルに日付(日本時間)を表示します。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    render() {
        return(
            <div>
                <Calendar
                    locale="ja-JP"
                    value={this.state.date}
                 />
          </div>
        )
    } 
}

それぞれのPropsについては公式だと以下のように説明されています。(英語のドキュメントのみ、、、)

Description Description Default value Example values
locale Locale that should be used by the calendar. Can be any IETF language tag. User's browser settings "hu-HU"
value Calendar value. Can be either one value or an array of two values. If you wish to use React-Calendar in an uncontrolled way, use defaultValue instead. "n/a" ・Date: new Date()
・An array of dates: [new Date(2017, 0, 1), new Date(2017, 7, 1)]

これで、カレンダーの作成は完了です。とても簡単!!

カレンダーに任意のアイテムを表示してみる

これだけだと寂しいので、日付タイルに任意のアイテムを表示させてみたいと思います。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    constructor(props) {
        super(props);
        this.state = {
            date: new Date(),
            //テストデータ
            month_item: {
                2020-12-01: { text: 'work' },
                2020-12-10: { text: 'hangout' },
                2020-12-24: { text: 'Christmas Eve' },
                2020-12-25: { text: 'Christmas' },
            }
        }
    };


     //日付の内容を出力
      getTileContent({ date, view }) {
          if (view === 'month') {
              const targetDate = moment(date).format('YYYY-MM-DD')

             return   month_item[targetDate] && month_item[targetDate].text ?
                 <div>
                        <p>{month_item[targetDate].text}</p>
                 </div>
               : null

          }
      }


    render() {
        return(
            <div>
                <Calendar
                    locale="ja-JP"
                    value={this.state.date}
                    tileContent={this.getTileContent.bind(this)} //ここを追加
                 />
    </div>
        )
    } 
}

tileContentの詳細の説明は以下です。functionを呼び出して、returnされたものを渡せば任意の日付に内容を表示させることができます。

Description Description Default value Example values
tileContent Allows to render custom content within a given calendar item (day on month view, month on year view and so on). n/a ・String: "Sample"
・React element:
・Function: ({ activeStartDate, date, view }) => view === 'month' && date.getDay() === 0 ? It's Sunday! : null

まとめ

Reactって便利〜! 今回の記事では説明していませんが、Reduxと合わせてDBからデータを引っ張ってきてstateで制御すれば、スケジュールを登録したり、表示させたりすることも可能でした!

今回記載している以外にもたくさんPropsの種類もあるので、気になる方はぜひ公式URLから見てみてください!

参考

React-Calendar が便利 | バシャログ。
Reactでカレンダー作成(React-Calendarライブラリ)

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

初回renderを無視するuseEffect

はじめに

useEffectは特定の変数が更新された時だけ、中身を実行してくれる良いやつです。
ただ、初回のレンダー時はどんなことがあっても処理が走ってしまいます。
これを回避するのはなかなか面倒で、useRefを使ったりといろいろあるんですが、めっちゃ便利なライブラリ見つけたので紹介します。

react-use

react-useは様々な便利系hooksが実装されていて、スター数も多くおすすめです。
https://github.com/streamich/react-use
この中で実装されているuseUpdateEffectが今回の目的のhooksです!

使い方

使い方は猛烈に簡単!
useEffectと全く同じです。hooks名をuseUpdateEffectに変えればおしまい。
これだけで、初回のレンダー時に中身は実行されません。

useUpdateEffect.ts
useUpdateEffect(() => {
  console.log(hoge) //初回は実行されない
}, [hoge]);

めっちゃ便利なのでぜひ使ってみてください〜。

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

【React初心者】 #3 ルーティング・ページ遷移を作る! react-router-dom

Reactでreact-router-domのルーティング設定

3回目の今回は、Reactでreact-router-dom
を使用したルーティングを使います。
URLを変えることで、表示するページを変える(レンダリングする)ことができます。

前回までで、

  • Material-UIで最低限のデザインを一から作成
  • データをAPIから取得して表示

しました。コードは前回の続きとなります。

シリーズ記事

やること

  • react-router-domでのReactでのルーティング
  • ルーティングするためのリンクの付け方

react-router-dom導入

まず、インストールが必要です。
今回はreact-router-dom: 5.2.0を使用しています。

$ npm install react-router-dom

TypeScriptの場合はnpm install react-router-dom @types/react-router-domみたいです。

とりあえず、aboutページを作成します。
ただ、文字を表示するだけにします。

ということで、Aboutコンポーネントを作成。

$ touch src/components/About.js

文字を表示するだけになります。

src/components/About.js
import React from 'react'

function About() {
    return (
        <div>
            aboutページ
        </div>
    )
}

export default About

前回までのコードにRouterとSwitch、Routeを足して、
URLによって、レンダリングするコンポーネントを変えています。

material-react/src/App.js
import './App.css';
import { Grid } from '@material-ui/core';
import Header from './components/Header';
import Content from './components/Content';
import About from './components/About';

import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";

function App() {
  return (
    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
      <Grid item container>
        <Grid sm={2} />
        <Grid xs={12} sm={8}>
        <Router>
          <Switch>
            <Route exact path="/">
              <Content />
            </Route>
            <Route path="/about">
              <About />
            </Route>
          </Switch>
        </Router>
        </Grid>
        <Grid sm={2} />
      </Grid>
    </Grid>
  );
}

export default App;

http://localhost:3000/about

とURLに入れると、

スクリーンショット 2021-01-16 5.25.38.png

文字だけのAboutコンポーネントが表示されています。

説明と注意

<Route exact path="/">exactは完全に一致しないとダメという意味です。

一致するURLを上から探すのだけど、このexactをつけないと、http://localhost:3000/jasdklfsとか意味ないものやでもContent`コンポーネントを使うことになります。

というか、http://localhost:3000/移行にURLに、何があろうがContentコンポーネントを使います。

試しに、exactを消して/aboutにアクセスしてみれば、Contentコンポーネントが出るので、試してみてもいいと思います。
<Route path="/"> 変更後、
http://localhost:3000/jasdklfshttp://localhost:3000/about
と書いてみましょう。

どちらも、Contentコンポーネントの内容が表示されます。
「書く順番」を考えましょう。

URLのパラメータを取得して個別記事表示

前回、JSON Placeholderからデータを取得しました。

APIをみると、個別記事の取得もできるので、
記事についている「詳細を見る」ボタンを押したら遷移させます。

個別記事のコンポーネントを作成

前回までで、左側は作りました。
今回は、右側を作ります。
個別記事IDにアクセスしたときにPostContentコンポーネントをレンダリングすることになります。
image.png

$ touch src/components/PostContent.js

/post/1だったら記事1を表示するようにしてみます。とりあえず
idを受け取れるかテスト。
/post/1にアクセスしたらuseParams(){id: 1}のようなデータが入るのでidだけをとるためconst { id }にしています。

そして、個別記事の後に受け取ったIDをくっつけて表示します。

material-react/src/components/PostContent.js
import React from 'react'
import { useParams } from 'react-router-dom'

function PostContent() {
    const { id } = useParams();
    return (
        <div>
            <h1>個別記事 {id}</h1>
        </div>
    )
}

export default PostContent

スクリーンショット 2021-01-16 6.02.07.png

http://localhost:3000/post/1にアクセスしたら1が表示されているのでOK
http://localhost:3000/post/10とかにしたら10に変わりますので試してみてください。

個別記事データをAPIで取得

Getで取得しましょう。URLの最後に記事のIDを入れればデータを取得できます。

では、JSON PlaceholderでAPIのURLは

https://jsonplaceholder.typicode.com/posts/1

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

axiosを使ったAPIのデータ取得は前回やったので簡単に書きます。

material-react/src/components/PostContent.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom'

import { Grid } from '@material-ui/core'
import BodyCard from './BodyCard'


const cardContent = 
    {
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    }

function PostContent() {
    const { id } = useParams();
    const [post, setPosts] = useState([])

    useEffect(() => {
        axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
        .then(res => {
            setPosts(res.data)
        })
    }, [])

    return (
        <Grid container spacing={2}>
            <Grid item xs={12} key={post.id}>
                <BodyCard {...{...post, ...cardContent}} />
            </Grid>
        </Grid>
    )
}

export default PostContent
  • https://jsonplaceholder.typicode.com/posts/${id}でURLから取得したパラメータを埋め込んでGET、APIでの取得
  • useStateを使って、ページを開いた時一度だけ、APIで取得
  • 前回作った、BodyCardコンポーネントに渡してMaterial-UIのカードの形式で作成
    • {...{...post, ...cardContent}}で二つのオブジェクトを結合して渡してます

とりあえず、ページを作り込むより、ルーティングの使い方説明メインなので、

  • 詳細をみるというボタンがあったり、
  • 画像がAPIでランダム取得するので変わってしまう、
  • 画像がhight: 150pxだと大きさおかしい

とかツッコミありますができました。
スクリーンショット 2021-01-16 16.44.21.png

一応、前回の記事から変更はないですが、BodyCardコンポーネントも載せておきます。

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';
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';
import { CardMedia } from '@material-ui/core';

const useStyles = makeStyles({
    bullet: {
      display: 'inline-block',
      margin: '0 2px',
      transform: 'scale(0.8)',
    },
    title: {
      fontSize: 14,
    },
    pos: {
      marginBottom: 12,
    },
    cHeader: {
        height: '50px',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        "& .MuiCardHeader-content": {
            overflow: 'hidden'
        }
    },
    cContent: {
        height: '200px',
        overflow: 'hidden'
    }
});


function BodyCard(props) {
    const { userId, id, title, body, avatarUrl, 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>
                }
                className={classes.cHeader}
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent className={classes.cContent}>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard

ボタンにリンクをつける

Material-UIにはButtonにhrefつければ遷移できるようになります。
以下のようにすれば大丈夫。

BodyCard.js
<Button size="small" href={`/post/${id}`}>詳細をみる</Button>

今回、Buttonにリンク機能がついていたので使いましたが、
デザインを自分でやりたい、文字にリンクつけるなどの場合は、

<Link to="/post/1">記事1の詳細</Link>

<Link to=`/post/${id}`>記事{$id}の詳細</Link>

などにすればいいと思います。
react-routerの公式サイトは英語ですが、
コードがみやすいのでみながら試してみてください。

https://reactrouter.com/web/example/basic

まとめ

  • react-router-domでのReactでのルーティング
  • ルーティングするためのリンクの付け方

をやりました。次回はAPIサーバーをPythonのDjangoで自作します。

補足

これまで、RouterをApp.jsに書きました。
ただ、ヘッダーがいらない、デザインを変えたいなどの場合(ログインページ、LP、認証のためのURLとか使う場合など)、

index.jsにrouterを書くこともできるので補足として。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import About from './components/About';
import reportWebVitals from './reportWebVitals';
import { Route, BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <div>
        <Route exact path="/" component={App} />
        <Route exact path="/about" component={About} />
      </div>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React hooks】噛み砕いて解説してみた~useContext編~

前書き

16.8vで追加された機能であるreact hooksを理解を深めるために体系的にまとめました。

以下、本題です

そもそもContextとは

別コンポーネントに値を動的に渡す方法として、Propsがあると思います。Contextも同じように値を動的に渡すことができます。

Contextの存在意義

多重ネストされたコンポーネントに値を渡すときに、下記のようにバケツリレーになってしまうことが多々あると思います。(コードは公式ドキュメントからコピペしました)

<Page user={user} avatarSize={avatarSize} />
// ... Page コンポーネントは以下をレンダー ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... PageLayout コンポーネントは以下をレンダー ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... NavigationBar コンポーネントは以下をレンダー ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

Contextを使うことで、コンポーネントを超えて動的に値を渡すことができます。

実際の使い方(hookなし)

まずはuseContextを使わないパターンを解説していきます。そのほうが理解が深まるからです。(知っているよ!という人は飛ばしてください。)

登場人物

  • createContext()
  • Proveider
  • Consumer

それぞれ軽く説明しておきます。

createContext()

const Context = React.createContext()

この関数でコンテキストオブジェクトが作成されます。このコンテキストオブジェクトは下記のようになっています。

{
  Provider: <>...<>,
  Consumer: <>...<>,
  ...
}

Context.ProviderContext.Consumerのように使われます。実際に見ていきましょう。

Provider

 return (
    <Context.Provider value={resource}>
      <Hoge />
    </Context.Provider>
  )

上記のようにProviderpropsvalueに渡したい値を指定してあげます。

Consumer

const Hoge = () => (
  <Context.Consumer>
    {(resource)=> (
      <h1>{resource.title}</h1>
    )}
  </Context.Consumer>
)

上記のように実際に使いたいコンポーネント(今回はProviderラップしたHogeコンポーネント)でprops.childに対して関数の引数で渡したい値(ここではresource)を入れています。

以上でuseContextを使わないパターンの説明は終了です!

以降からuseContextを使ったパターンの解説をしていきます。

useContextを使う場合

簡潔にお伝えするとConsumerつまり実際に値を呼び出すコンポーネントで使うことができます。useContextを使わない場合は下記のような形ですね。

const Hoge = () => (
  <Context.Consumer>
    {(resource)=> (
      <h1>{resource.title}</h1>
    )}
  </Context.Consumer>
)

useContextを使う場合は下記のようにuseContext()関数の引数にコンテキストオブジェクトを含めてください。

const Hoge = () => {
  const { resource } = useContext(Context)

  return (
    <h1>{resource.title}</h1>
  )
}

props.childが不要になったのでスッキリしましたね!

解説は以上です。お疲れ様でした?‍♂️

参考記事

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

React+reduxで数字をカウントしてみる

index.jsを用意します

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { PersistGate } from 'redux-persist/integration/react'
import './index.css';
import App from './App';


// ステートの値
let state_value = {
    counter:0,
    message:"COUNTER"
}


// レデューサー
function counter(state = state_value, action) {
    switch (action.type) {
        case 'INCREMENT':
        return {
            counter:state.counter + 1,
            message:"INCREMENT"
        };
        case 'DECREMENT':
        return {
            counter:state.counter - 1,
            message:"DECREMENT"
        };
        case 'RESET':
        return {
            counter:0,
            message:"RESET"
        };
        default:
        return state;
    }
}


// Redux Persistの設定
const persistConfig = {
    key: 'root',
    storage,
}


// パーシストレデューサーの作成
const persistedReducer = persistReducer(persistConfig, counter)


// ストア、パーシスターの作成
let store = createStore(persistedReducer)
let pstore = persistStore(store)


// 表示をレンダリング
ReactDOM.render(
    <Provider store={store}>
        <PersistGate loading={<p>loading...</p>}
                persistor={pstore}>
            <App />
        </PersistGate>
    </Provider>,
    document.getElementById('root')
);

ここではパーシストレデューサーをもとに、ストアとパーシスターを作成しました。
次にパーシスターとAPPコンポーネントをJSXで表示します。

APP.jsを作成

import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';


// Appコンポーネント
class App extends Component {


  constructor(props){
    super(props);
  }

  render() {
    return (
      <div>
        <h1>Redux</h1>
        <Message />
        <Button />
      </div>
    );
  }
}
// ストアのコネクト
App = connect()(App);


// メッセージ表示のコンポーネント
class Message extends Component {
  style = {
    fontSize:"20pt",
    padding:"20px 5px"
  }

  render(){
    return (
      <p style={this.style}>
        {this.props.message}: {this.props.counter}
      </p>
    );
  }
}
// ストアのコネクト
Message = connect((state)=>state)(Message);


// ボタンのコンポーネント
class Button extends Component {
  style = {
    fontSize:"16pt",
    padding:"5px 10px"
  }

  constructor(props){
    super(props);
    this.doAction = this.doAction.bind(this);
  }

  // ボタンクリックでディスパッチを実行
  doAction(e){
    if (e.shiftKey){
      this.props.dispatch({ type:'DECREMENT' });
    } else if (e.ctrlKey){
      this.props.dispatch({ type:'RESET' });
    } else {
      this.props.dispatch({ type:'INCREMENT' });
    }
  }


  render(){
    return (
      <button style={this.style}
          onClick={this.doAction}>
        click
      </button>
    );
  }
}
// ストアのコネクト
Button = connect()(Button);


export default App;

APP.js内に画面に表示するMessageコンポーネントとプッシュボタンのButtonコンポーネントを作成します。
Buttonコンポーネント内には、ボタンを押すINCREMENT、Shiftキー押しながらボタンを押すとDECREMENT、ctrlキーを押しながらボタンを押すとRESETのタイプを作りました。ボタンを押すとタイプごとのアクションをRedux送りそのタイプにあった処理が行われます。

image.png

読んでくださりありがとうございます。
初学者なので至らないところがあると思います。
おかしい所があればご指摘願います。

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

【TypeScript】.ts拡張子と.tsx拡張子

.ts拡張子と.tsx拡張子の違い

.ts拡張子

  • 純粋なTypeScriptファイル
  • JSX要素の追加をサポートしない

.tsx拡張子

  • JSXを含むファイル
  • 型アサーションの記法として value as type<type>value の2通りあるが、後者は .tsx には書けない
    <> はJSXタグのマーカーであるため)

Reactを使うプロジェクト内のTypeScriptファイルは全て.tsxではダメなのか

結論

  • .ts.tsx は明示的に分けるべき

理由

  • 拡張子で明示的にしておくことで、「このファイルにJSXを書くべきではない」ことを表すことができる
    (UIコンポーネントのファイルか、ロジックを書くファイルか)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Emotion v11とtypescriptが喧嘩する ~jsx設定方法~

emotionの環境構築をおこなったところ、
TypeScript絡みの設定が案外手強かったので備忘録とさせていただきます。

ざっくり環境

React v17.0.1
typescript v4.1.3
@emotion/react v11.1.4
@babel/core v7.12.10

jsx属性認識しない問題

import { jsx } from '@emotion/react'
import { FooterContainer } from '../../../style/components/block/Footer'

export default function Footer() {
  return (
    <div css={FooterContainer}>
      <p>Footer</p>
    </div>
  )
}


emotion v11においては、
上記のように jsxをimportして、reactからやってくるjsxを使わない(reactを直でimportしない)記述となります。
しかし、早速問題が...

スクリーンショット 2021-01-18 11.42.34.png

なんとか型定義をimportしているjsxの方に向かせたいですね。
そもそもreactのJSXの型定義はどこからきているのでしょうか?

答えはTypeScriptのコンパイルのオプションである jsxFactoryからきています。
https://www.typescriptlang.org/tsconfig/#jsxFactory
こちらはデフォルトでは React.createElementになっています。

jsxFactoryをemotionのjsxで呼べるよう変更しておきます。
ちなみにトランスパイルをbabelに任せている場合、 "jsx": "preserve" でOKです。

tsconfig
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxFactory": "jsx",
...
}

これでエラーが解消されます。

別解:プラグマを使う

公式のドキュメントでは以下のようなコメントが差し込まれています。

/** @jsx jsx */

このコメントはプラグマというTSでサポートされている機能になります。
この機能は、ファイル毎にjsxFactoryを変更できる機能です。
こちらを追加するとエラーが解消されます。

/** @jsx jsx */
import { jsx } from '@emotion/react'
import { FooterContainer } from '../../../style/components/block/Footer'

export default function Footer() {
  return (
    <div css={FooterContainer}>
      <p>Footer</p>
    </div>
  )
}

https://github.com/Microsoft/TypeScript/pull/21218

ただ、ファイル毎にプラグマを記述しないといけないのが微妙ですね。

わがまま

jsxを直で上書きしてしまうのはちょっと不安です。
毎度jsxを読まないといけないのもなんか微妙です。
もっといい方法があれば教えていただけると嬉しいです:runner:

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

Redux超入門【React】

Reduxが難しかったのですが、1つのファイルにまとめてみるとすんなり理解できたので記載。

準備

まずはReactの環境構築。

$ npx create-react-app アプリ名 

次にカレンドディレクトリに移動してreduxの導入。

$ npm install redux react-redux   // npm→yarnでも可

コード

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import {createStore} from 'redux';

// Action
const increment = () => {
    return {
        type: 'INCREMENT'
    }
}
const decrement = () => {
    return {
        type: 'DECREMENT'
    }
}

// Reducer
const counter = (state = 0, action) => {
    switch(action.type){
        case 'INCREMENT':
            return state + 1
        case 'DECREMENT':
            return state - 1
    }
}

let store = createStore(counter)

// Display it in the console
store.subscribe(() => console.log(store.getState()))

// Dispatch
store.dispatch(increment())
store.dispatch(decrement())
store.dispatch(increment())

ReactDOM.render(
  <React.StrictMode>
      <App />
  </React.StrictMode>,
  document.getElementById('root')
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Hooks ~ Effect Hookの使い方 ~

Effect Hook とは

レンダリング後の DOM の変更や、状態の更新、他コンポーネントに対して影響を及ぼす処理(effects)を制御するための Hooks API になります。

基本的な書き方は下記、 useState と同様に import して関数コンポーネント内に記述することです。

import React, { useEffect } from "react"

// 関数コンポーネント内で下記を記述する
useEffect(() => {
  // 処理をここに書く

  // 【option】関数を返すようにすると、アンマウントされる前に関数が実行される。
  return () => console.log('クリーンアップを実行');
}, []);

Effect Hook は以下のタイミングで実行される。

  • レンダリング後に必ず実行する。
  • 初回のレンダリング後だけ実行する。
  • 指定したデータに変更があった場合のみ実行する。
    • 例で示した文の[]のところに、何の変更をトリガーにするかを書くことができる。

利点として、State など、Reactの機能をEffect Hookの関数内で利用できること。

Effect の利用ケース

  • APIとの通信
    • State を設定して、レンダリング後に API からデータを取得してセットする。
  • Stateを変更させたときに、他に影響を与える場合
    • 例えばチャットアプリで相手から返信が来た時に、タイトルに未読件数を表記するとか

サンプル

State の変更を受け取って、ページのタイトルを変更する Effect Hook を実装します。
記事投稿サイトみたいなのをイメージしてます。

App.tsx
import React, { useState, useCallback, useEffect } from "react";
import { InputForm } from "./InputForm";
import "./styles.css";

export default function App() {
  const [content, setContent] = useState<string>("test");

  const onChangeContent = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setContent(event.target.value);
    },
    []
  );

  useEffect(() => {
    document.title = `${content} が入力されている。`;
    console.log("content が変更された");
  }, [content]);

  return (
    <div className="App">
      <InputForm content={content} onChange={onChangeContent} />
      <div>
        <span>content の値は、"{content}"</span>
      </div>
    </div>
  );
}
InputForm.tsx
import React from "react"

interface Props {
  content: string;
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

export const InputForm = (props: Props) => {
  return <input defaultValue={props.content} onChange={props.onChange}></input>
}

簡単ではありますが、codesandboxにサンプルのコードを上げてます。
余分な関数の実行や、知らぬ間に DOM が書き換わってしまっている、を防ぐために、第二引数はなるべく入れとく方が良いのかなと思ったりしました。

その他

  • useEffect は1つのコンポーネント内で複数設定が可能
    • 異なる変数を第二引数に設定するなどもできる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsでSSRを簡単にするには 其の壱「旅立ち」

Next.jsでSSRを簡単にするには 其の壱「旅立ち」

こちらに同じ内容を書いています

1. Next.jsでのSSRはApolloが優秀

 検索Botの性能が微妙な頃、SPAのWebシステムではSSRが重要な位置を占めていました。みんな必死にSSRを行うための取り組みを行いました。しかしクライアントで動くべき物をサーバで実行し、その結果を返すというのは、それなりの無理が生じます。無理を通しているが故に、開発コストに跳ね返ってしまうのです。

 コストを抑えつつSSRを導入しようとした場合、GraphQLとApollo導入が無難な流れとなります。Apolloのキャッシュ機能を利用すれば、サーバ側で生成したデータを短いステップでクライアントに引き継ぐことが出来るからです。一回手続を書いておけば、その他の部分ではクライアントとサーバのコードを別々に書く必要はありません。

 ではGraphQLを使わなかった場合はどうでしょう?選択肢を調べてみましたが、Apolloのようにサーバ側で生成したキャッシュをまとめて引き渡すような便利なライブラリが見つかりませんでした。大抵の場合、地道に部分部分で引き渡しコードを書いている形です。

2. Next.js上でSSRをする時に使う物

  • _app.tsx上でApp.getInitialProps
  • 各Page上でgetInitialProps
  • 各Page上でgetServerSideProps

 現時点で推奨されているのはgetServerSidePropsです。サーバでしか実行されず、その実行結果はクライアント側に送られます。ただこれ、デフォルトだとSPAのページ切り替えごとにサーバにデータを取りに行きます。一応、shallowパラメータを設定すれば再ロードを回避することは可能なのですが、propsには古いデータが残ってしまい、キャッシュの更新判定が面倒なことになります。ぶっちゃけて言うと、キャッシュのことを考えずに、とにかくデータが受け取れれば良いという場合にしか使い道がありません。

 各Page上でgetInitialPropsの方も、ページを作るごとに個別に受け渡し処理を作成するのも面倒です。

 やはり理想はApolloのApp.getInitialPropsを使ったキャッシュ引き渡しです。Next.jsでのビルド時に、「推奨していないApp.getInitialPropsをまだ使ってるの?getServerSideProps使いなよ」と文句を言われますが、まともな選択肢が他にないのです。

3. useSWRを試してみる

 とりあえず上手いやり方は無いかと使い勝手が良いと話題のuseSWRを試してみました。fetchのURLをキーにして、取得データをキャッシュすることが出来ます。しかも取得したキャッシュに自由にアクセスが可能です。これはと思ったのもつかの間、以下の問題により断念しました。

  • useSWRは適度なタイミングでデータをとりにいく事が目的で、必要ないときはデータをとらないという機能が無い
  • dedupingIntervalに大きな値を設定すれば一応データを取りに行かなくなるけど、とらないフラグ付けて
  • タイミングが色々指定できるし便利だけど、今欲しいのはそれじゃない
  • そして最も重要なポイント、cacheで自由にキャッシュを書き換えることは出来た、確かに出来た。しかしfetchの戻り値を個別管理していて、次のターンで復元される

 useSWRのソースを眺めてみた結果、cacheにはアクセスできるけど、データの流れ的にfetchの戻り値をがっちり持っていて、根本を書き換えないと理想的な動作にならないことが判明。

4. useSWRのような使い勝手でApolloみたいなことをするには

 結論「自分で作るしかない」

 ということで願いを叶えるため、自前のuseSWRのHooksの構造とApolloキャッシュ構築方法をパクり、オレオレuseFetchを作りはじめした

 現在の進行状況(基本的な実装は完了)
 天気予報を取得しSSRしつつキャッシュを保持のサンプル
 https://github.com/SoraKumo001/use-fetch-sample01

次回 Next.jsでSSRを簡単にするには 其の弐「流浪」

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

Material-UI でデフォルトテーマを変更する方法

概要

React とセットで使うことの多い Material-UI のテーマを変更する方法です。
デフォルトテーマは『こちら』を参照してください。

テーマをカスタマイズ

ここではtheme.jsというファイルを作成します。

theme.js
import { createMuiTheme } from '@material-ui/core/styles';

// フォントを設定
const fontFamily = [
  'Noto Sans JP',
  'メイリオ',
  'MS Pゴシック',
  'sans-serif',
  // 使用したいフォントを以降に羅列してください
].join(',');

/*****************
 * テーマを設定
 *****************
 */
const theme = createMuiTheme({
  typography: {
    fontFamily: fontFamily,  // フォント
  },
  palette: {
    // Primaryカラーを設定
    primary: {
      light: '#54C527',
      main: '#ff9800',
      dark: '#b26a00',
      contrastText: '#000000',
      mainGradient: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    },
    // Secondaryカラーを設定
    secondary: {
      light: '#33eb91',
      main: '#00e676',
      dark: '#00a152',
      contrastText: '#ffffff',
    },
    type: 'dark', // ダークモードをON
  },
  mixins: {
    // ツールバーの高さ
    toolbar: {
      minHeight: 64,
    },
  },
  // 各パーツのスタイルをカスタマイズ
  props: {
    MuiCheckbox: {
      color: 'primary',
    },
    MuiList: {
      dense: true,
    },
    MuiTable: {
      size: 'small',
    },
    MuiTextField: {
      variant: 'outlined',
    },
  },
});

export default theme;

マテリアルデザイン仕様のカラーは『こちら』で確認することができます。

テーマをコンポーネントに適用

以下の例は create-react-app で作成されたindex.jsに適用しています。
ポイントはMuiThemeProviderをimportしてコンポーネントに適用してあげることです。
パラメーターに先ほど作成したtheme.jsを渡してあげてください。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// カスタムしたテーマ(スタイル)を定義
import { MuiThemeProvider } from '@material-ui/core/styles';
import theme from './styles/theme';

ReactDOM.render(
  <React.StrictMode>
    <MuiThemeProvider theme={theme}>
      <App />
    </MuiThemeProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

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

Material-UI でデフォルトテーマをカスタマイズする方法

概要

React とセットで使うことの多い Material-UI のテーマをカスタマイズする方法です。
デフォルトテーマは『こちら』を参照してください。

テーマをカスタマイズ

ここではtheme.jsというファイルを作成します。

theme.js
import { createMuiTheme } from '@material-ui/core/styles';

// フォントを設定
const fontFamily = [
  'Noto Sans JP',
  'メイリオ',
  'MS Pゴシック',
  'sans-serif',
  // 使用したいフォントを以降に羅列してください
].join(',');

/*****************
 * テーマを設定
 *****************
 */
const theme = createMuiTheme({
  typography: {
    fontFamily: fontFamily,  // フォント
  },
  palette: {
    // Primaryカラーを設定
    primary: {
      light: '#54C527',
      main: '#ff9800',
      dark: '#b26a00',
      contrastText: '#000000',
      mainGradient: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    },
    // Secondaryカラーを設定
    secondary: {
      light: '#33eb91',
      main: '#00e676',
      dark: '#00a152',
      contrastText: '#ffffff',
    },
    type: 'dark', // ダークモードをON
  },
  mixins: {
    // ツールバーの高さ
    toolbar: {
      minHeight: 64,
    },
  },
  // 各パーツのスタイルをカスタマイズ
  props: {
    MuiCheckbox: {
      color: 'primary',
    },
    MuiList: {
      dense: true,
    },
    MuiTable: {
      size: 'small',
    },
    MuiTextField: {
      variant: 'outlined',
    },
  },
});

export default theme;

マテリアルデザイン仕様のカラーは『こちら』で確認することができます。

テーマをコンポーネントに適用

以下の例は create-react-app で作成されたindex.jsに適用しています。
ポイントはMuiThemeProviderをimportしてコンポーネントに適用してあげることです。
パラメーターに先ほど作成したtheme.jsを渡してあげてください。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// カスタムしたテーマ(スタイル)を定義
import { MuiThemeProvider } from '@material-ui/core/styles';
import theme from './styles/theme';

ReactDOM.render(
  <React.StrictMode>
    <MuiThemeProvider theme={theme}>
      <App />
    </MuiThemeProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

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