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

React Navigationについて�かなりザックリ解説する

React Navigationとは

React Nativeで画面遷移(スマホで画面シュッ)ってするために使うコンポーネントのことです。なにやら3種類くらいあり、それらは以下の通り。

  • StackNavigator
  • TabNavigator
  • DrawerNavigator

違いについては省略しますが、ザックリと言えば画面遷移の作り方が異なってきます。
これらの違いについてはこちらの記事が参考になります。

試しに使ってみたいなら

この記事が物凄く良さげに解説してくれています。有難や。

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

React.jsについてかなりザックリ解説する

React.js(通称リアクト)とは

様々な記事でReactはライブラリだよとか、フレームワークだよとかって解説されててごっちゃになります。なので自分用に整理しておくこととします。

React.jsはFacebook製のJavaScriptライブラリです。フレームワークではありません。フレームワークはちゃんと別で名前があり、それがReact Nativeです。

Reactの特徴

Reactと言えばコンポーネントと言う単語がよく出てきます。コンポーネントとは部品のことで、「UIを独立した再利用可能な部分に分割したもの」のことを指します。

なんのこっちゃと思いますが、ボタン、フォーム、画面、ダイアログなどのそれぞれのUIを部品ごとに分けて開発することで、保守性に優れた開発が出来るようになるんだとか。

Reactの概要を学ぶなら

この記事がめちゃくちゃ参考になりそうです。

ちょっとずつ理解を深めていきましょう。

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

ReactでマークダウンをHTMLに変換する

ReactでマークダウンをHTMLに変換する方法について記載

1.react-markdownをインストール

npm install --save react-markdown

2.以下のコードを記載

import React,{Component} from 'react';
import ReactMarkdown from 'react-markdown/with-html';

export default class MarkdownSample extends React.Component {

    constructor(props) {
        super(props)
    }

    getHTMLfromMarkdown(){
        let markdown =
            '# 見出し 1\n' +
            '## 見出し 2\n' +
            '### 見出し 3\n' +
            '#### 見出し 4\n' +
            '---\n' +
            '- リスト 1\n' +
            '- リスト 2\n' +
            '- リスト 2-1\n' +
            '1. 番号付きリスト 1\n' +
            '2. 番号付きリスト 2\n' +
            '3. 番号付きリスト 3\n' +
            '[リンク](http://...)\n' +
            '**強調**\n' +
            '```ruby:filename.rb\n' +
            'コード\n' +
            '```';
        return markdown;
    }

    render() {
        return (
            <div>
                <ReactMarkdown
                    source={this.getHTMLfromMarkdown()}
                    escapeHtml={false}
                />
            </div>
        )
    }
}

以上

参考

https://github.com/rexxars/react-markdown

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

【react-native】setStateで値の反映が待てないあなた向け

はじめに

setStateをした後にその中の値を使った計算をしたいのに、反映が遅くて使えない・・・
なんて思ったことありませんか。私はありません。ということで対策案を考えました。
まずは事象から見てください。

事象

Mar-05-2020 07-54-36.gif

こちらデモですが、「No1add」「No2add」ボタンを押下することでそれぞれの数字に値を1足す。
その後、合計値にという流れを実装したいのですが、うまくいきません。
合計値がボタンを押す前のNo1とNo2の合計値になってしまいます。

ソースは以下です。

App.js
import React from 'react';
import { StyleSheet, View, Button, Text } from 'react-native';

export default class App extends React.Component {
  state = {
    num1: 0,
    num2: 0,
    sum: 0,
  }
  render() {
    const onPressNo1 = () => {
      this.setState({ num1: this.state.num1 + 1 })
      // 合計点を随時更新する
      calcSum();
    }
    const onPressNo2 = () => {
      this.setState({ num2: this.state.num2 + 1 })
      // 合計点を随時更新する
      calcSum();
    }

    const calcSum = () => {
      this.setState({ sum: this.state.num1 + this.state.num2 })
    }
    return (
      <View style={styles.container}>
        <View style={styles.row}>
          <Button title="No1add" onPress={() => onPressNo1()} />
          <Text style={styles.textStyle}>No1: {this.state.num1}</Text>
        </View>

        <View style={styles.row}>
          <Button title="No2add" onPress={() => onPressNo2()} />
          <Text style={styles.textStyle}>No2: {this.state.num2}</Text>
        </View>

        <Text style={styles.textStyle}>No1 + No2 =  {this.state.sum}</Text>

      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  row: {
    flexDirection: "row",
    alignItems: "center"
  },
  textStyle: {
    fontSize: 20,
  }
});

原因はsetStateで値が反映されるのは画面が再描画されるタイミングのため、合計値の計算のタイミングではまだnum1とnum2の値が更新されていないため。
以下、対策案です。
他にもあれば教えてください。

対策案①:stateに値を直接いれる

これはあまりいけてないですが、パワープレイしたいならこちらが良いでしょう。

App.js
import React from 'react';
import { StyleSheet, View, Button, Text } from 'react-native';

export default class App extends React.Component {
  state = {
    num1: 0,
    num2: 0,
    sum: 0,
  }
  render() {
    var varNum1 = this.state.num1;
    var varNum2 = this.state.num2;
    var varSum = this.state.sum;

    const onPressNo1 = () => {
      varNum1 = this.state.num1 + 1
      this.setState({ num1: varNum1 })
      // 合計点を随時更新する
      calcSum();
    }
    const onPressNo2 = () => {
      varNum2 = this.state.num2 + 1
      this.setState({ num2: varNum2 })
      // 合計点を随時更新する
      calcSum();
    }

    const calcSum = () => {
      varSum = varNum1 + varNum2
      this.setState({ sum: varSum })
    }
    return (
      <View style={styles.container}>
        <View style={styles.row}>
          <Button title="No1add" onPress={() => onPressNo1()} />
          <Text style={styles.textStyle}>No1: {this.state.num1}</Text>
        </View>

        <View style={styles.row}>
          <Button title="No2add" onPress={() => onPressNo2()} />
          <Text style={styles.textStyle}>No2: {this.state.num2}</Text>
        </View>

        <Text style={styles.textStyle}>No1 + No2 =  {this.state.sum}</Text>

      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  row: {
    flexDirection: "row",
    alignItems: "center"
  },
  textStyle: {
    fontSize: 20,
  }
});

変数を新たに用意し、そこに値を入れ即時反映させていくやり方です。
いけてないですが、確実に理想的な実装ができます

対策案②:再描画タイミングで計算しちゃおう

sumをstateでもつことなんてやめましょうw

App.js
import React from 'react';
import { StyleSheet, View, Button, Text } from 'react-native';

export default class App extends React.Component {
  state = {
    num1: 0,
    num2: 0,
  }
  render() {
    const onPressNo1 = () => {
      this.setState({ num1: this.state.num1 + 1 })
    }
    const onPressNo2 = () => {
      this.setState({ num2: this.state.num2 + 1 })
    }

    return (
      <View style={styles.container}>
        <View style={styles.row}>
          <Button title="No1add" onPress={() => onPressNo1()} />
          <Text style={styles.textStyle}>No1: {this.state.num1}</Text>
        </View>

        <View style={styles.row}>
          <Button title="No2add" onPress={() => onPressNo2()} />
          <Text style={styles.textStyle}>No2: {this.state.num2}</Text>
        </View>

        <Text style={styles.textStyle}>No1 + No2 =  {this.state.num1 + this.state.num2}</Text>

      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  row: {
    flexDirection: "row",
    alignItems: "center"
  },
  textStyle: {
    fontSize: 20,
  }
});

まとめ

独学しているとsetStateの正しい使い方これであってるのかな・・・と不安になります。
一旦まとめて見ました。

他に案がある人募集しています。
教えて欲しいです・・・

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

第4回 2020年版 既存のウェブサイトに React を追加する

1. 概要

Reactを大規模に利用することが難しい場合、少しだけReactを取り入れてみて、徐々に適用範囲を広げていくのが良いです。
Reactでは既存のページに部分的に利用することが可能であり、今回はその方法について説明します。

2. 前提条件

作業日時

  • 2020/3/5

ソフトウェアのバージョン

ソフトウェア バージョン
webpack 4.42.0
babel 7.8.6
ts-loader 6.2.1
react 16.13.0
react-dom 16.13.0
typescript 3.8.3

3. シンプルな利用方法

3.1. 環境の準備

create-react-appは利用せず、自分でWebpack等の設定ファイルを準備する。
最初に yarn initpackage.json を作成し、その後、必要なパッケージをインストールする。

webpack、babel、typescriptの他にhtml確認用のwebpack-dev-serverをインストールする。

$ yarn init
$ yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin
$ yarn add -D @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/register
$ yarn add -D typescript ts-loader

React関連もインストールする。

$ yarn add react react-dom
$ yarn add -D @types/react @types/react-dom
$ yarn add @material-ui/core @material-ui/icons

3.2.設定ファイルの作成

3.2.1.Webpackの設定ファイル

webpackのコンフィグファイルを作成します。

$ touch webpack.config.js

設定ファイルには以下のように記述します。
エントリポイントはindex.jsxで、出力ファイルはbundle.jsにしています。

webpack.config.js
require('@babel/register'); // development.jsでES6を使えるようにする

const path = require('path')

const src  = path.resolve(__dirname, 'src');
const dist = path.resolve(__dirname, 'dist');

module.exports = {
  mode: 'development',
//  mode: "production",

  entry: src + '/index.jsx',
  output: {
    path: dist,
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        use: [
            {
              loader: "babel-loader", // Babel を利用する
              options: { // Babel のオプションを指定する
                presets: [
                  "@babel/preset-env", // プリセットを指定することで、ES2020 を ES5 に変換
                  "@babel/react" // React の JSX を解釈
                ]
              }
            }
          ]        
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  },
  plugins: []
}

3.2.2. Typescriptの設定

Typescriptの設定ファイルも作成します。

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",  /* tsを書くときにどのversionのESが対象か: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "es2015" /* どのversionのESを生成するか: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */

    /* Strict Type-Checking Options */
    "strict": true,                        /* すべての strict タイプ・オプション(noImplicitAny, strictNullChecks, noImplicitThis(thisの型チェック), alwaysStrict(strictモードのjs出力)) を 有効化する */
    "noImplicitAny": true,                 /* any型の使用不可 */
    "strictNullChecks": true,              /* nullable型以外でnullを許容しない */

    /* Additional Checks */
    "noUnusedLocals": true,                /* 未使用の変数を許容しない */
    "noUnusedParameters": true,            /* 未使用の変数を許容しない */
    "noImplicitReturns": true,             /* メソッド内で返り値の型があっているかをチェック */

    /* Module Resolution Options */
    "moduleResolution": "node",            /* http://js.studio-kingdom.com/typescript/handbook/module_resolution 参照 */
    "esModuleInterop": true                /* ESModuleと同じ動作をする. */
  }
}

3.3. サンプルの作成

3.3.1. HTML に DOM コンテナを追加する

Reactを追加したい HTML ファイルを用意します。

React で描画したい箇所に空の <div> 要素を追加し、ユニークな id 属性を指定します。また、 <script src="bundle.js" charset="utf-8"></script> を追加し、React コンポーネントのJavascriptを読み込みます。

これで、後から <div> 要素にReact コンポーネントを描画する準備ができました。

index.html
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Web site created using create-react-app" />
    <title>React App</title>
</head>

<body><noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="app" />
</body>
<script src="bundle.js" charset="utf-8"></script>
</html>

3.3.2. Reactコンポーネントを作成する

次にReactコンポーネントを作成します。

src/index.tsx
import React from 'react';
import {render} from 'react-dom';

function App() {
    return (
      <div>
      <h1>Hello React!</h1>
      </div>);
}

render(<App/>, document.getElementById('app'));

3.3.3. ビルド

Webpackを使って上記のコードをビルドします。
成功すれば、 dist/bundle.js が生成されます。

$ ./node_modules/.bin/webpack

webpack-dev-serverを起動して、ブラウザからlocalhost:8080にアクセスしてみます。
Hello React! と表示されれば成功です。

$ ./node_modules/.bin/webpack-dev-server

4. 複数のコンポーネントを利用する方法

先程のサンプルでは一つのRreactコンポーネントを利用しただけですが、次はReactコンポーネントを利用する方法について説明します。

4.1. 設定ファイルの修正

複数のファイルを一度に生成できるように、 webpack.config.js を修正します。
entry に複数のコンポーネントを列挙します。また、outputfilename[name].bundle.jsとすることで、それぞれ別のファイルが出力されるようにします。

webpack.config.js
require('@babel/register'); // development.jsでES6を使えるようにする

const path = require('path')

const src = path.resolve(__dirname, 'src');
const dist = path.resolve(__dirname, 'dist');

module.exports = {
    mode: 'development',
    //  mode: "production",

    entry: {
        appbar: src + '/index.tsx',
       content: src + '/content.tsx'
      },

    output: {
        path: dist,
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
            {
                // 拡張子 .ts の場合
                test: /\.tsx?$/,
                exclude: /node_modules/,
                // TypeScript をコンパイルする
                use: [
                    // 下から順に処理される
                    {
                        loader: "babel-loader",
                        // Babel のオプションを指定する
                        options: {
                            presets: [
                                // プリセットを指定することで、ES2020 を ES5 に変換
                                "@babel/preset-env",
                                // React の JSX を解釈
                                "@babel/react"
                            ]
                        }
                    },
                    { loader: "ts-loader" }
                ],
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx']
    },
    plugins: []
};

4.2. サンプルアプリの作成

htmlファイルと、ヘッダとコンテンツのReactコンポーネントの3つのファイルを作成します。

最初にヘッダ部分の描画用のReactコンポーネントを作成する。
<StylesProvider> はMaterial-UIで自動生成するクラス名が重複しないようにするため、 createGenerateClassName での衝突を避けるためのプリフィックス等の指定を行う。

index.tsx
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles';


import {
  AppBar,
  Toolbar,
  Typography,
  IconButton,
  Avatar
} from '@material-ui/core';

import red from '@material-ui/core/colors/red';

// Material-UIアイコン取得
import NotificationImportantIcon from '@material-ui/icons/NotificationImportant';
import MenuIcon from "@material-ui/icons/Menu";

const generateClassName = createGenerateClassName({
  productionPrefix: 'a',
  seed: 'appbar', // classNameが重複しないようにするために設定
});

// スタイルを適用する
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    headerLogo: {
      color: "inherit",
      marginRight: 20,
    },
    headerTitleStyle: {
      flexGrow: 1,
      color: "inherit",
    },
    menuButton: {
      color: "inherit",
      padding: '8px',
    },
    avatar: {
      margin: '8px',
      backgroundColor: red[500],
    }
  }),
);


function MyAppBar() {
  // CSSを適用する。
  const classes = useStyles();

  return (
    <StylesProvider generateClassName={generateClassName}>  
    <div>
      <AppBar position='static' aria-label="Global Navi">
        <Toolbar>
          <Typography className={classes.headerLogo} variant="subtitle1">My Sample App</Typography>
          <Typography className={classes.headerTitleStyle} variant="subtitle1" >Material UI test</Typography>
          <NotificationImportantIcon></NotificationImportantIcon>
          <IconButton className={classes.menuButton} aria-label="Menu">
            <Avatar className={classes.avatar}></Avatar>
          </IconButton>
          <IconButton aria-label="SideMenu">
            <MenuIcon />
          </IconButton>
        </Toolbar>
      </AppBar>
    </div>
    </StylesProvider>  
  )
}

ReactDOM.render(<MyAppBar />, document.getElementById('appbar'));

次にコンテンツ部分の描画用のReactコンポーネントを作成する。

content.tsx
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles';

import {
  Typography,
  IconButton,
  Avatar,
  Paper,
  Tab,
  Tabs,
  Box,
  Card,
  CardHeader,
  CardMedia,
  CardContent
} from '@material-ui/core';

import red from '@material-ui/core/colors/red';

// Material-UIアイコン取得
import MoreVertIcon from '@material-ui/icons/MoreVert';

const generateClassName = createGenerateClassName({
  productionPrefix: 'b',
  seed: 'content', // classNameが重複しないようにするために設定
});

// スタイルを適用する
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    avatar: {
      margin: '8px',
      backgroundColor: red[500],
    },
    card: {
      textAlign: 'center',
      maxWidth: 400,
    },
    media: {
      height: 0,
      paddingTop: '56.25%', // 16:9
    },
    actions: {
      display: 'flex',
    },
    expand: {
      transform: 'rotate(0deg)',
      marginLeft: 'auto',
      transition: theme.transitions.create('transform', {
        duration: theme.transitions.duration.shortest,
      }),
    },
    expandOpen: {
      transform: 'rotate(180deg)',
    },
  }),
);


interface TabPanelProps {
  children?: React.ReactNode;
  index: any;
  value: any;
}

function TabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <Typography
      component="div"
      role="tabpanel"
      hidden={value !== index}
      id={`simple-tabpanel-${index}`}
      aria-labelledby={`simple-tab-${index}`}
      {...other}
    >
      {value === index && <Box p={3}>{children}</Box>}
    </Typography>
  );
}

function Content() {
  // CSSを適用する。
  const classes = useStyles();
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
    setValue(newValue);
  };

  return (
    <StylesProvider generateClassName={generateClassName}>  
    <div>
      <Paper square>
        <Tabs
          value={value}
          indicatorColor="primary"
          textColor="primary"
          onChange={handleChange}
          variant="scrollable"
          scrollButtons="auto"
          aria-label="scrollable auto tabs example"
        >
          <Tab label="TOP" />
          <Tab label="お買い物" />
          <Tab label="ファッション" />
          <Tab label="グルメ" />
          <Tab label="おでかけ" />
          <Tab label="スポーツ" />
          <Tab label="映画・ドラマ" />
        </Tabs>

        <TabPanel value={value} index={0}>
          <Card className={classes.card}>
            <CardHeader
              avatar={<Avatar aria-label="Recipe" className={classes.avatar}>R</Avatar>}
              action={
                <IconButton>
                  <MoreVertIcon />
                </IconButton>
              }
              title="キットカット4種セット(毎日のナッツ&クランベリー[パウチ36g/ルビー パウチ31g]"
              subheader="2020年3月5日"
            />
            <CardMedia className={classes.media} image="/img/paella.jpg" title="Paella dish"/>
            <CardContent><Typography component="p">カードの説明</Typography></CardContent>
            </Card>
        </TabPanel>
          <TabPanel value={value} index={1}>
            Item Two
        </TabPanel>
          <TabPanel value={value} index={2}>
            Item Three
        </TabPanel>        
        <TabPanel value={value} index={3}>
            Item Four
        </TabPanel>        
        <TabPanel value={value} index={4}>
            Item Five
        </TabPanel>        
        <TabPanel value={value} index={5}>
            Item Six
        </TabPanel>        
        <TabPanel value={value} index={6}>
            Item Seven
        </TabPanel>        
      </Paper>
    </div>
    </StylesProvider>      
      )
    }

ReactDOM.render(<Content />, document.getElementById('content'));

最後にhtmlファイルを作成する。
ヘッダとコンテンツ用のReactコンポーネントを読み込むため、Reactを描画する

にidを指定し、appbar.bundle.jscontent.bundle.jsのJavascriptを読み込む。
index.html
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Web site created using create-react-app" />
    <title>React App</title>
</head>

<body><noscript>You need to enable JavaScript to run this app.</noscript>
    <header>
        <h1>ページタイトル<h1>
    </header>
    <div id="appbar"></div>
    <div id="content"></div>
    <script src="appbar.bundle.js" charset="utf-8"></script>
    <script src="content.bundle.js" charset="utf-8"></script>
    <footer>
        <nav>
            <a href="index.html">トップページ</a>
            <a href="about.html">このサイトについて</a>
        </nav>
        <p>Copyright 2020</p>
    </footer>
</body>

</html>

4.3. ビルドする

ビルドして、サーバーで表示します。

$ ./node_modules/.bin/webpack
$ ./node_modules/.bin/webpack-dev-server

以下の画面が表示されたら成功です。

既存hamlにReact.gif

5. 最後に

今回は既存のhtmlにReactを部分的に利用する方法について説明しました。
既に運用しているサービスがある場合、全面的なSPA化は困難ですが、部分的に置換えていくことで、徐々にReactに移行していくことが可能だと思います。

6. 過去の記事

過去のReactに関する記事です。

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

個人サービスの運用コストがどのぐらいかかるか検証した

はじめに

個人サービスをリリースして1年半、ようやく利益が出てきたので、運用した知見と運用コストについて公開したいと思います。

運用しているサービスについて

洋楽にまつわる最新情報を更新、配信、投稿出来る音楽総合メディアです。

みんなの洋楽ランキング

流入

PV

スクリーンショット 2020-03-05 12.32.21.png

おかげさまで10万PVほどありました。特にバズったということもなく、毎日同じようなアクセス数がありました。

流入元

スクリーンショット 2020-03-05 12.36.57.png

恥ずかしながらSEOの影響でorganic searchがほぼ数を占めています。自分のブログTwitterでアウトプットはしているものの、やり方が悪いのか、そもそもセンスがないのか、あまり効果が出ていません。逆に流入の見込みが立てやすいので計画が立てやすいかもしれません(後付け

諸経費

サーバー代 + データベース諸々

スクリーンショット 2020-03-05 12.42.34.png

2月は¥2,000ちょっとですみました。AzureのApp Service on Linuxというやつを使っていて、現在値下げ中なのでこの価格になります。余談ですが、昨年の12月末に値下げ終了告知をしていたのですが、今年の1月末まで延び、さらには今年の7月末までと値下げ期間がどんどん延びています。このまま続いてくれれば良いのですが。。Azureは若干高い印象があるので値下げが終わったらDocker化して他のサービスに移行してやろうかと思ってます。

ドメイン代

スクリーンショット 2020-03-05 12.52.45.png

お名前.comを利用しています。大体¥1,500ぐらいです。年々高くなっているのはなんでですかね?

SSL

Let's Encryptを利用しています。無料なのでお金掛かってません。

開発費

全て自分でやっているので0円ですが、全て外注したらいくらかかるのだろう。。

キャンペーン費

サービスの性質上、ユーザーが投稿してくれる形で成り立ってるので毎月数名にamazonギフト券を還元しています。楽しんで記事を書いてくれる方がほとんどなのですが、運営している本人が「このサービス使ってもあまり利益ないな」と感じているのでその懺悔としてお気持ち程度に。。

負荷

平均応答時間

スクリーンショット 2020-03-05 13.01.55.png

PageSpeed Insightsでは200ミリ秒以下は遅いと判断されるので、まぁ許容範囲かと思います。ところどころ負荷が高いところがみられますが、1日1回定期タスクが動いている影響です。

サーバーの応答時間は 200 ミリ秒以下に抑える必要があります。

CPU

スクリーンショット 2020-03-05 13.05.46.png

10%以下に抑えられているので大丈夫でしょう。ところどころ負荷が高いところがみられますが、1日1回定期タスクが動いている影響です。

メモリ

スクリーンショット 2020-03-05 13.07.07.png

64%とまぁまぁ使っている状況です。まだプランを変えずに踏ん張れるレベルかと思います。

データベース

スクリーンショット 2020-03-05 13.09.59.png

DTUというAzure特有の単位なのですが、1%程度なのでほぼほぼ問題ないかと思います。

DTU(Database Transaction Unit)という単位で定義され、CPU、メモリ、I/Oの組み合わせからなります。
DTUが大きくなるほど、コンピューティングリソースが多く使えるようになり、標準で付属するストレージ、追加できるストレージも大きくなります。後述するように、ダウンタイムを発生させずにスケールの変更ができます。

収益

GoogleAdSenseのみの収益となります。金額は

6,188円

でした。

※ キャプチャ載せて違反とか食らったら困るので載せらせません?‍♀️

まとめ

今は利益出ていますが、これまでは収益がほぼなかったので諸経費がそのまま掛かってました。やっと利益が出たので定期的にユーザーさんに大して還元出来る状況になったのはよかったと思います。私は「サービスを使うユーザーの利益がなければ陳腐化する」というポリシーを持ってサービスを運用しているので、ユーザーの利益が「お金」である以上は儲かるまでの敷居が高くなります。なので、利益をどこに持っていくかが鍵となりますが、そんなこと気にせずにこのぐらいのお金を投資出来るよーという方に個人サービスを作って欲しいと思います!

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

Rails + React + AjaxでCRUDのサンプルプロジェクト [Hello World]

React初心者が公式サイトで基礎を学んだ後に作るReact + AjaxによるCRUD(作成/読み込み/更新/削除)のサンプルプロジェクトです。
react_crud_1.png
Twitterのように「つぶやき」を投稿可能で編集/削除もできます。

動作確認はChrome、FireFox、Microsoft Edge、IE11です。恐らくマックさんのブラウザでも動作するはずです。

DEMO

https://www.petitmonte.com/rails-demo/react_crud

ソース一式

https://github.com/TakeshiOkamoto/mpp_react_crud

※学習用の為、ライセンスはパブリックドメイン

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

Reactのプロキシを通すとSSE (Server-sent events)のレスポンスを受け取れない

React、というかreact-scriptsはプロキシ機能を備えています。

このプロキシ、基本的にはウェブAPIのために用います。React自体はlocalhost:3000で実行し、ウェブAPIはlocalhost:8080で実行する場合、localhost:3000にウェブAPIのリクエストを投げるとlocalhost:8080にプロキシする、という具合ですね1

使用方法も簡単で、 package.json に下記のようなフィールドを付け加えるだけで動作します。

{
  // ...
  "proxy": "http://localhost:8080",
  // ...
}

ただこのプロキシを通してSSE (Server-sent events)なウェブAPIにリクエストを投げると、レスポンスのヘッダーなどは受け取れるものの、本文が受け取れません。悲しいね……。

という事実を述べて終わりたいわけじゃなく、なぜそうなるのか、どうすれば解決できるのかまとめてみました。

手っ取り早く解決したい方へ

「理由はどうでもいいから一刻も早く解決したい!」という方はここ読むだけで大丈夫です。

Reactアプリケーションのソースコードが置いてあるルートに以下の内容を setupProxy.js というファイル名で保存してください。ちなみに/apiへのリクエストをlocalhost:8080にプロキシする、という内容ですので、その辺は適宜変更してください。

const proxy = require('http-proxy-middleware');

module.exports = (app) => {
  app.use('/api', proxy({
    target: 'http://localhost:8080',
    onProxyReq: (proxyReq, req) => {
      if (req.headers['accept'] === 'text/event-stream') {
        req.headers['accept-encoding'] = 'identity';
      }
    }
  }));
};

このファイルを置いた後、react-scripts restartを行えば解決すると思います。

原因

react-scriptsは内部でwebpack-dev-serverを利用しています。webpack-dev-serverはさらにExpress.jsを内包しており、そのミドルウェアで様々な機能を実現しています。プロキシもそのひとつです2

そして、デフォルトでレスポンスの圧縮も行っています3。圧縮もExpress.jsのミドルウェアで実現されており、compressionがその実体です。

で、原因はずばりこの圧縮にあります。この件はすでにOption to disable the compression for the webpack dev server · Issue #7847 · facebook/create-react-appなどで言及されています。

対処

さきほどのIssueにもコメントとして残されていますが、SSEなウェブAPIのレスポンスヘッダーに Cache-Control: no-transform を付与すればこの問題は解決します。

なぜ解決するかはcompressionミドルウェアのソースコードを眺めると簡単に分かります。

compressionミドルウェアは以下のいずれかに該当するときに圧縮を行いません

となっているので、 Cache-Control: no-transform とすると、2番目の条件に該当して圧縮が行われない、というわけです。

もう一歩踏み込んだ対処

Cache-Contro: no-transform が妥当なケースならそれで良いはずです。

ただ、 Cache-Control: no-transformCache-Control - HTTP | MDNでも触れられている通り、プロキシにおけるあらゆる変換の禁止を宣言するものです。それにより圧縮も行われなくなるのですが、圧縮は許容しないが、他の変換は許容するという場合には不適切です。

まーーーーーーーいろいろ考慮することはあると思うのですが、今回に限れば圧縮が悪さしているので、圧縮にのみアプローチできるのが最も影響範囲が小さいと思います。そうなると Cache-Control: no-transform はやや影響範囲が大きいかな……、と思う。ちょっと強引な気もするけど。SSEであれば実際はあらゆる変換を禁止するのが妥当な気もするけど。

とりあえず圧縮にのみアプローチしたい、となった場合、やはり着目すべきはAccept-Encodingヘッダーでしょう。実際compressionミドルウェアもAccept-Encodingヘッダーにidentityが含まれていれば圧縮を行わないので、対処自体は妥当だと思います。

というわけでAccept-Encodingヘッダーを付けよう!と思ったけど、SSEのクライアントとして使うであろうEventSourceはリクエストヘッダーを指定できない!ていうかそもそもAccept-Encodingヘッダーはプログラムによる指定を禁止されている

となるので、先にも挙げたこのコードになるのです。

const proxy = require('http-proxy-middleware');

module.exports = (app) => {
  app.use('/api', proxy({
    target: 'http://localhost:8080',
    onProxyReq: (proxyReq, req) => {
      if (req.headers['accept'] === 'text/event-stream') {
        req.headers['accept-encoding'] = 'identity';
      }
    }
  }));
};

react-scriptsはsetupProxy.jsを読み込むようになっており、そこで詳細なプロキシの設定を行えます

そのとき、http-proxy-middleware(実際は内包しているhttp-proxy)にてプロキシ時のイベントを捉えることが出来ます。そこで、EventSourceからのリクエスト、つまりAccept: text/event-streamのときに限りAccept-Encoding: identityとしてあげます。

するとcompressionミドルウェアはそれを考慮し、圧縮を行いません。

すごくめでたしな感じしません?な?なーーー!?とりあえず私はこうしています。

また別の対処

CORSを使え!

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

React+Express+WebpackでSSRするときのキャッシュ対策

SPAの場合

webpackでbuildするとき、SPAであればキャッシュ対策は簡単です。

webpack.client.js
module.exports = {
  entry: ["@babel/polyfill", "./src/index.jsx"],
  output: {
    path: path.resolve("dist"),
    filename: "[name]-[hash].js"
  },
}

と、outputに[hash]をつけるだけ。
あとはwebpackでほぼ必須なHtmlWebPackPluginが参照できるようHTMLをビルドしてくれる。

以下のようなHTMLがビルドされます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <base href="/">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="favicon.ico"></head>
<body>
   <div id="index"></div>
   <script type="text/javascript" src="main-09fede0d434d3225ac66.js"></script>
</body>
</html>

hash値がbundleファイルに付くので、これがキャッシュバスターになります。

SSRするときは

SSRでキャッシュ対策するとき、hashをつける方法でやるなら、サーバ側でレンダリングするHTMLでhash付きのbundleファイルを正しく参照できないといけないです。

例えばclientはwebpack.client.jsで、server(express)はwebpack.server.jsでビルドするとして、hashを共有する必要があります。

webpack.client.jsの設定は上記のままで、サーバサイドのエントリーポイントを以下のようにしてみました。

server.js
import express from 'express';
import { JSDOM } from 'jsdom';
import path from 'path';

...

const app = express();

...

// webpack.common.jsでビルドされた、main.jsのハッシュ付きパスを取得する(キャッシュバスター)
let bundlePath;
JSDOM.fromFile(path.resolve("dist", "index.html")).then(dom => {
  const document = dom.window.document;
  const script = document.querySelector('script[type="text/javascript"]');
  bundlePath = script.src.replace("file:///", "");
});
app.all("*", (req, res, next) => {
  req.bundlePath = bundlePath;
  next();
});

...

jsdomを利用して、dom操作で、先にwebpack.client.jsでビルドされているdist/index.htmlの中からscriptタグ内のsrcを、強引ですが持ってきてreqオブジェクトに入れておきます。

script.srcfile://がついて取得されるので.replaceします。

そして、SSRしている部分で、

render.js
...

  const html = `
    <!doctype html>
    <html>
      <head>
        <base href="/" />
      </head>
      <body>
        <div id="index">${app}</div>
        <script type="text/javascript" src="${req.bundlePath}"></script>
        </body>
    </html>
  `;

などとし、client -> serverの順でビルドすれば、SSRでもclientとserverでhash値の共有ができます。

curlしてみてdist内のjsファイルと見比べると、正しくできていることがわかります。

curl -H 'Cache-Control: no-cache' http://localhost/path/to/app

    <!doctype html>
    <html>
      <head>
        <base href="/" />
      </head>
      <body>
        <div id="index">

        ...

        </div>
        <script type="text/javascript" src="main-09fede0d434d3225ac66.js"></script>
        </body>
    </html>

補足1

サーバサイドのエントリーポイントをビルドするときに、canvasがどうのってエラー出るかもしれないですが、

webpack.server.js
module.exports = {
  target: "node",
  entry: "./src/server.js",
  output: {
    path: path.resolve("dist"),
    filename: "server.js"
  },
  externals: {
    canvas: "commonjs canvas"
  },
...
}

のようにexternalsにcanvas入れたらビルド通りました。

補足2

依存関係を入れずに対応することもできます。
正規表現で取得します。

server.js
import fs from "fs";

...

const html = fs.readFileSync(path.resolve("dist", "index.html"), {encoding: "utf-8"});
const scriptTag = html.match(/<script(.|\s)*?><\/script>/i)[0];
app.all("*", (req, res, next) => {
  req.scriptTag = scriptTag;
  next();
});

このscriptTagをhtmlに挿入するだけ。
こっちのほうがいいかも。

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

React Native で画面遷移やタブメニュー【react-navigation 5.x】

react-navigation とは

react-navigation とは、React Native アプリのルーティングで使える便利なライブラリです。
画面遷移や、タブバー、そしてドローワー(横からすっとでてくるメニュー)などを利用できるので非常に便利。
4.x->5.x のメジャーバージョンアップに伴い、コンポーネントベースになりました。

今回は、5.x の使い方を紹介します。

インストール

# ライブラリのインストール
yarn add @react-navigation/native
# dependencies のインストール
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

App.tsx

import { NavigationContainer } from '@react-navigation/native';
import React, { useEffect } from 'react';
import HomeNavigator from './Home';
import NavigationService from './navigation-service';

const App: React.FunctionComponent = () => {
  return (
    <NavigationContainer>
      <HomeNavigator />
    </NavigationContainer>
  );
};

export default App;

これで準備は完了です。

画面遷移の実装

ライブラリをインストールします。

yarn add @react-navigation/stack

サンプルコードは以下のようになります。

import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { Alert, Button } from 'react-native';

const Stack = createStackNavigator();

const HomeNavigator = () => (
  <Stack.Navigator
    screenOptions={{
      headerTintColor: ...,
      headerStyle: {
        backgroundColor: ...,
      },
    }}
  >
    <Stack.Screen
      name="Home"
      component={Home}
      options={{
        headerRight: () => (
          <Button
            onPress={() => Alert.alert('This is a button!')}
            title="Info"
            color="#fff"
          />
        ),
      }}
    />
    <Stack.Screen name="Details" component={Details} />
  </Stack.Navigator>
);

...

export default HomeNavigator;

最初に createStackNavigator でスタックを定義しています。
作成したスタックは Stack.Navigator, Stack.Screen の形で使用します。

screenOptions には、このナビゲーター全体に適用するレイアウトを定義することができます。
個々のスクリーンに適用するレイアウトは options に記載します。

component で画面の要素を見ていきます。
二種類の書き方ができます。

const Home: React.FunctionComponent = () => {
  const navigation = useNavigation();
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
};
const Home: React.FunctionComponent = ({ navigation }) => {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
};

navigation を渡す方法は、 react-navigation 4.x までと変わりませんね。
props として渡す方法の型定義が面倒になってしまったため、私は Hooks の方を使ってます。

ボタンをクリックすると、 Details に遷移します。

タブメニューの実装

ライブラリをインストールします。

yarn add @react-navigation/bottom-tabs

サンプルコードは以下のようになります。

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React, { useLayoutEffect } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';

const Tab = createBottomTabNavigator();

const TabNavigator = ({ navigation, route }) => {
  useLayoutEffect(() => {
    navigation.setOptions({
      headerTitle: route.state
        ? route.state.routes[route.state.index].name
        : route.params?.screen || 'Home',
    });
  }, [navigation, route]);

  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;

          if (route.name === 'Home') {
            iconName = focused
              ? 'ios-information-circle'
              : 'ios-information-circle-outline';
          } else if (route.name === 'Settings') {
            iconName = focused ? 'ios-list-box' : 'ios-list';
          }

          return <Ionicons name={iconName} size={size} color={color} />;
        },
      })}
      tabBarOptions={{
        activeTintColor: 'tomato',
        inactiveTintColor: 'gray',
      }}
    >
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
  );
};

...

export default TabNavigator;

createBottomTabNavigator でタブを作成。
Tab.Navigator, Tab.Screen で実際の内容を定義します。

screenOptions, tabBarOptions の代わりに、カスタムのタブメニューを利用することも可能です。 tabBar={(props) => <TabBar {...props} />} で定義します。
TabBar は以下のような形で書くことができます。

import React from 'react';
import { Dimensions, Text, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';

const { width: windowWidth } = Dimensions.get('window');

const TabBar = ({ state, descriptors, navigation }) => {
  const tabWidth = windowWidth / state.routes.length;
  return (
    <View style={{ flexDirection: 'row', width: windowWidth, height: 64 }}>
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        const label =
          options.tabBarLabel !== undefined
            ? options.tabBarLabel
            : options.title !== undefined
            ? options.title
            : route.name;

        const isFocused = state.index === index;

        const onPress = () => {
          const event = navigation.emit({
            type: 'tabPress',
            target: route.key,
          });

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name);
          }
        };

        const onLongPress = () => {
          navigation.emit({
            type: 'tabLongPress',
            target: route.key,
          });
        };

        return (
          <TouchableOpacity
            key={route.key}
            accessibilityRole="button"
            accessibilityStates={isFocused ? ['selected'] : []}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            onPress={onPress}
            onLongPress={onLongPress}
            style={{
              flex: 1,
              width: tabWidth,
              paddingTop: 10,
            }}
          >
            <Text
              style={{
                color: isFocused ? '#673ab7' : '#222',
                alignSelf: 'center',
              }}
            >
              {label}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default TabBar;

スライドメニューの実装

ライブラリをインストールします。

yarn add @react-navigation/drawer

サンプルコードは以下のようになります。

import { createDrawerNavigator } from '@react-navigation/drawer';
import React from 'react';

const Drawer = createDrawerNavigator();

const DrawerNavigator = () => {
  return (
    <Drawer.Navigator initialRouteName="Home">
      <Drawer.Screen name="Home" component={Home} />
      <Drawer.Screen name="Notifications" component={Notifications} />
    </Drawer.Navigator>
  );
};

...

export default DrawerNavigator;

おまけ

スクリーンのなかで画面遷移をする場合、propsuseNavigation から navigation をうけとって使用する方法を紹介しました。

では、自前の関数など、スクリーン以外で利用する場合はどうすれば良いでしょうか。

navigation-service.ts

import React from 'react';

export const isMountedRef = React.createRef();
export const navigationRef = React.createRef();

const navigate = (name: string, params?: any) => {
  if (isMountedRef.current && navigationRef.current) {
    navigationRef.current?.navigate(name, params);
  } else {
    console.log('Not mounted.');
  }
};

export default {
  navigate,
};

App.tsx

const App: React.FunctionComponent = () => {
  useEffect(() => {
    // TypeScript error が出る
    isMountedRef.current = true;
    return () => (isMountedRef.current = false);
  }, []);

  return (
    <NavigationContainer ref={navigationRef as any}>
      <HomeNavigator />
    </NavigationContainer>
  );
};

公式ドキュメントでも紹介されている方法ですが、 isMountedRef.current の更新はうまくできないようです。
GitHub にも issue がありました。

コンポーネントがマウントされていれば navigationRef.current?.navigate(...); は動作します。

これで自前の関数から、

const myFunc = () => {
  NavigationService.navigate('...');
}

といった形で利用可能になります。

おわりに

別プロジェクトでは 4.x からバージョンアップを試みており、 TypeScript 周りでかなり苦労しています。
ただし、 Hooks との相性も良さそうですし、新規でプロジェクトを始める場合は 5.x を使ってみても良いかもしれません。

型定義周りはもう少し調査が必要そうです。

記事の内容に誤りなどございましたらご指摘いただけると幸いです。

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

[React]別々に useState で定義した相互依存する state は期待通り動かない

React の useState をいろいろいじってるときに微妙にハマったのでメモ。

useState を複数使って state を定義しているときに、それらが相互依存しているとき期待通りに動かなかった。

なので、相互依存しているものは同じ state に入れることで解消しました。

サンプル

loadingtrue のとき、 submit できないようにしたいとする。
(loading === true のとき、 canSubmit === false にしたい)

import React, { FC, useState, useEffect } from 'react';

const StateDependOnEachOther: FC = () => {
  // 別々の state に定義したバージョン
  const [canSubmit, setCanSubmit] = useState(true);
  const [loading, setLoading] = useState(false);

  // loading の状態に合わせて canSubmit を変える
  const submitting = () => {
    setCanSubmit(() => {
      if (loading) {
        return false;
      }

      return true;
    });
  };

  // 同じ state に定義したバージョン
  const [state, setState] = useState({
    canSubmit2: true,
    loading2: false,
  });

  // loading の状態に合わせて canSubmit を変える
  const submitting2 = () => {
    setState(prevCanSubmit2 => {
      if (prevCanSubmit2.loading2) {
        return {
          ...prevCanSubmit2,
          canSubmit2: false,
        };
      }

      return {
        ...prevCanSubmit2,
        canSubmit2: true,
      };
    });
  };

  useEffect(() => {
    // loading を true に変更
    setLoading(true);
    setState(prevState => {
      return { ...prevState, loading2: true };
    });

    // loading === true のとき、 canSumit は false にしたい
    submitting();
    submitting2();
    // eslint-disable-next-line
  }, []);

  return (
    <>
      <div>別々の useState で定義: canSubmit: {`${canSubmit}`}</div>
      <div>同じ useState で定義 state.canSubmit2: {`${state.canSubmit2}`}</div>
    </>
  );
};

export default StateDependOnEachOther;

結果

  • 別々の useState で定義した場合は loading の変更が反映されず、 canSubmit は true のまま
  • 同じ useState 定義した場合は loading の変更が反映され、 canSubmit は false になる

実際に動かしたものがこちら

とりあえず useState に定義すればいいと思ってたけど、関連するものはまとめる、または1コンポーネントの中で useState は一つにして、必要なものは全部その中に突っ込む方が良さそう。

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

React Native vs. Ionic – A head-to-head Comparison in 2020

In this article, we are going to look at an intense comparison between Ionic and React Native development frameworks. At the end of this article, you will be all clear about what framework you should use for your development projects.

click here to read morehttps://www.positronx.io/react-native-vs-ionic-head-to-head-comparison/

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

React Native StackNavigator – Passing & Getting Params to Screen

In this tutorial, we are going to learn how to easily pass some value from one screen to another screen using React Navigation’s StackNavigator service.

click here to read more
https://www.positronx.io/react-native-stack-navigator-passing-getting-params-to-screen/

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

React Native Navigation v5 Example Tutorial

This is a step by step comprehensive React Native Navigation v5 tutorial. In this tutorial, we will learn how to implement Stack Navigation in React Native app using Stack Navigator.

click here to read more
https://www.positronx.io/react-native-navigation-example-tutorial/

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

React Contextを使う

概要

あるコンポーネントから、子、孫のコンポーネントに値を渡すのに
通常だとpropsを使って順番に橋渡ししていくので手間ですが、
Contextを使うことでそんな面倒はなしにグローバルに値にアクセスできます。
アプリ全体で使う設定をもたせるのに便利だと思います。
Reduxが嫌いな人におすすめです。

下記サンプルではParentView -> MiddleView -> ChildViewの階層になっています。

Contextを作成する

const SettingContext = React.createContext({ xxx, yyy });

コンポーネントの外に定義しておきます。
引数はデフォルト値になります。
親をたどってProviderがなかった場合に適用されるようです。(そんな事あるのか?)

値をセットする

        <SettingContext.Provider value={ xxxx }>
          <MiddleView setting={this.state} />
        </SettingContext.Provider>

作成したContextのProviderをタグに使ってvalueに値を入れます。

値をゲットする

やり方は2通りあるようです。

  • contextTypeを設定する クラスのcontextTypeプロパティにコンテキストを設定します。 そしてthis.contextで値を取り出します。
class ChildView1 extends React.Component {
  render() {
    return (
      <div>
        <h3>Passed by contextType</h3>
        <p>{this.context.color}</p>
        <p>{this.context.size}</p>
      </div>
    );
  }
}
ChildView1.contextType = SettingContext;
  • Consumerを使う 作成したContextのConsumerをタグに使います。 中で関数の引数に値が入るのでそれを使います。
class ChildView2 extends React.Component {
  render() {
    return (
      <div>
        <SettingContext.Consumer>
          {context => {
            return (
              <React.Fragment>
                <h3>Passed by Consumer</h3>
                <p>{context.color}</p>
                <p>{context.size}</p>
              </React.Fragment>
            );
          }}
        </SettingContext.Consumer>
      </div>
    );
  }
}

子のコンポーネントから値を更新する

メソッドを作成してProviderに渡して値と同じように使います。

  this.updateState = () => {
      console.log('update');
      this.setState({
        color: this.state.color + '!',
        size: this.state.size + 1
      });
    };

サンプルコード

import React from 'react';

// Contextを定義
const SettingContext = React.createContext({
  // default values
  color: 'red',
  size: 40,
  updateState: () => {},
  resetState: () => {}
});

// Context使わない場合
class ChildView extends React.Component {
  render() {
    return (
      <div>
        <h3>Passed by props</h3>
        <p>{this.props.setting.color}</p>
        <p>{this.props.setting.size}</p>
      </div>
    );
  }
}

// contextTypeを追加
class ChildView1 extends React.Component {
  render() {
    return (
      <div>
        <h3>Passed by contextType</h3>
        <p>{this.context.color}</p>
        <p>{this.context.size}</p>
        <button onClick={this.context.updateState}>update</button>
      </div>
    );
  }
}
ChildView1.contextType = SettingContext;

// Consumerを追加
class ChildView2 extends React.Component {
  render() {
    return (
      <div>
        <SettingContext.Consumer>
          {context => {
            return (
              <React.Fragment>
                <h3>Passed by Consumer</h3>
                <p>{context.color}</p>
                <p>{context.size}</p>
                <button onClick={context.updateState}>update</button>
              </React.Fragment>
            );
          }}
        </SettingContext.Consumer>
      </div>
    );
  }
}

class MiddleView extends React.Component {
  render() {
    return (
      <div>
        <h2>Parent</h2>
        <ChildView setting={this.props.setting} />
        <ChildView1 />
        <ChildView2 />
      </div>
    );
  }
}

class ParentView extends React.Component {
  constructor(props) {
    super(props);

    this.updateState = () => {
      console.log('update');
      this.setState({
        color: this.state.color + '!',
        size: this.state.size + 1
      });
    };

    this.state = {
      color: 'green',
      size: 50,
      updateState: this.updateState
    };
  }

  render() {
    return (
      <div className="App">
        <SettingContext.Provider value={this.state}>
          <MiddleView setting={this.state} />
        </SettingContext.Provider>
        <div>
          <h2>default</h2>
          <ChildView1 />
          <ChildView2 />
        </div>
      </div>
    );
  }
}

export default ParentView;

まとめ

contextTypeの方がスッキリして見えるがクラス外にコードを書くのが嫌。
単純なものならReduxを使うよりシンプルというのは言えるかな。
初心者の私にはわかりませんが。

https://ja.reactjs.org/docs/context.html#reactcreatecontext

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