20200226のReactに関する記事は12件です。

【DX】Ryzen9 3950xは開発体験をどれほど向上させるか

Ryzen9 3950xは開発体験をどれほど向上させるか

はじめに

AMD Ryzenの登場の登場により、それまでIntel寡占状態だったCPU業界の動きがかなり激しくなっています。僕も先日「AMD Ryzen9 3950x」搭載のデスクトップPCを購入しました。
「AMD Ryzen 9 3950X」はメインストリーム向けCPUとして、世界初の16コア32スレッドCPUです。そんな驚異的な性能でどのくらい開発体験が向上するかを検証していきたいと思います。

比較環境

パッと思いつく検証方法がなかったのでReactのbuild時間を計測することにしました。
自作PCにはmacOSを搭載することができないのでUbuntuOSを利用しています。

検証PC

PC CPU OS Memory
iMac 2019 Intel(R) Core(TM) i5-8500 CPU @ 3.00GHz macOS 10.15.3 40GB
MacBook Pro 2017 Intel(R) Core(TM) i7-7660U CPU @ 2.50GHz macOS 10.15.3 16GB
Ryzen9 3950X PC AMD Ryzen™ 9 3950X Ubuntu 18.04 32GB

ビルド対象

項目
言語 JavaScript
フレームワーク React
bundler webpack
Node Version 10.16.3
ファイル数 1404
行数 133363

結果

Development Build

まずはDevelopment Buildで検証します。
以下のコマンドを実行したときにブラウザでロードされるまでの時間をキャッシュ有(1回目)とキャッシュ無(2回目)計測します。

$ yarn start
  • キャッシュ無
PC Time
Macbook Pro 4:01.04
iMac 1:01.53
3950x PC 0:42.63
  • キャッシュ有
PC Time
Macbook Pro 1:44.14
iMac 0:41.78
3950x PC 0:30.82

Production Build

まずはProduction Buildで検証します。
以下のコマンドを実行したときにbundleファイルが生成され終わるまでの時間を計測します。

$ yarn build
PC 1回目
Macbook Pro 4:40.63
iMac 2:32.74
3950x PC 1:42.44

まとめ

じつはこの3台、購入時期は差があるものの値段的にはあまり差がありません。
OSは違えどRyzenのコストパフォーマンの高さがうかがえますね。

そもそもデスクトップPCとノートパソコンではCPUの設計が異なります。型番の最後に"U"がつくとそれはモバイル用に設計された省電力CPUなのであまりパフォーマンスがでません。そのぶんMacBookProは持ち運ぶことができますが。

1日に2回キャッシュ無、10回キャッシュ有ビルドをした場合、
最速のRyzenとMacBookでは一日あたり20分、一年で120時間の差にになります。
それ以外の差も含めるともっと差がでることになります。

ゲーム業界やデザイナー業界だけでなく、デベロップ環境でもAMDの勢いを感じることができました。
AMD最高です。

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

【DX】Ryzen9 3950Xは開発体験をどれほど向上させるか

Ryzen9 3950Xは開発体験をどれほど向上させるか

はじめに

AMD Ryzenの登場により、それまでIntel寡占状態だったCPU業界の動きがかなり激しくなっています。僕も先日「AMD Ryzen9 3950X」搭載のデスクトップPCを購入しました。
「AMD Ryzen9 3950X」はメインストリーム向けCPUとして、世界初の16コア32スレッドCPUです。そんな驚異的な性能でどのくらい開発体験が向上するかを検証していきたいと思います。

比較環境

パッと思いつく検証方法がなかったのでReactのbuild時間を計測することにしました。
自作PCにはmacOSを搭載することができないのでUbuntuOSを利用しています。

検証PC

PC CPU OS Memory
iMac 2019 Intel(R) Core(TM) i5-8500 CPU @ 3.00GHz macOS 10.15.3 40GB
MacBook Pro 2017 Intel(R) Core(TM) i7-7660U CPU @ 2.50GHz macOS 10.15.3 16GB
Ryzen9 3950X PC AMD Ryzen™ 9 3950X Ubuntu 18.04 32GB

ビルド対象

項目
言語 JavaScript
フレームワーク React
bundler webpack
Node Version 10.16.3
ファイル数 1404
行数 133363

結果

Development Build

まずはDevelopment Buildで検証します。
以下のコマンドを実行したときにブラウザでロードされるまでの時間をキャッシュ有(1回目)とキャッシュ無(2回目)計測します。

$ yarn start
  • キャッシュ無
PC Time
Macbook Pro 4:01.04
iMac 1:01.53
3950x PC 0:42.63
  • キャッシュ有
PC Time
Macbook Pro 1:44.14
iMac 0:41.78
3950x PC 0:30.82

Production Build

つぎにProduction Buildで検証します。
以下のコマンドを実行したときにbundleファイルが生成され終わるまでの時間を計測します。

$ yarn build
PC 1回目
Macbook Pro 4:40.63
iMac 2:32.74
3950x PC 1:42.44

まとめ

じつはこの3台、購入時期は差があるものの値段的にはあまり差がありません。
OSは違えどRyzenのコストパフォーマンの高さがうかがえますね。

そもそもデスクトップPCとノートパソコンではCPUの設計が異なります。型番の最後に"U"がつくとそれはモバイル用に設計された省電力CPUなのであまりパフォーマンスがでません。そのぶんMacBookProは持ち運ぶことができますが。

1日に2回キャッシュ無、10回キャッシュ有ビルドをした場合、
最速のRyzenとMacBookでは一日あたり20分、一年で120時間の差にになります。
それ以外の差も含めるともっと差がでることになります。

ゲーム業界やデザイン業界だけでなく、デベロップ環境でもAMDの勢いを感じることができました。
AMD最高です。

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

Top 50+ React Native Interview Questions and Answers 2020

Have a look at these mostly asked best 50+ React Native Interview Questions and their answers in 2020. Get ready to face any question on React Native thrown at you in the interview.

click here to read more
https://www.positronx.io/top-react-native-interview-questions-and-answers/

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

Top 75+ JSP Interview Questions and Answers In 2020

In this article, I am going to provide you with a lot of JSP questions that are often asked in Interviews along with their answers. Let’s move on to the Q and A tour.

click here to read more
https://www.positronx.io/top-jsp-interview-questions-and-answers/

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

Create React Native Firebase CRUD App with Firestore

This is a step by step React Native Firebase tutorial. In this tutorial, we will learn to create CRUD (Create, Read, Update, Delete) app using Firestore for iOS and Android platforms.

click here to read more
https://www.positronx.io/create-react-native-firebase-crud-app-with-firestore/

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

React Native Firebase - Firebase Integration in React Native

React Native is a well known open-source framework created by Facebook. It allows you to build a native mobile application with the help of JavaScript.

clic here to read more
https://www.positronx.io/react-native-firebase-firebase-integration-in-react-native/

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

React Native Alert Example – Show Alert in React Native App

This tutorial shows you how to easily show an alert message in React Native application using the React Native Alert API. We will also learn to create custom alert using React Native Awesome Alerts module.

click here to read more
https://www.positronx.io/react-native-alert-example-show-alert-in-react-native-app/

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

HTML6 is Coming – Here is a Sneak Peek

HTML, the language of the web, is one of the most well-known web technology. HTML has been in use continually for building the internet since the time it was introduced.

click here to read more
https://www.positronx.io/html6-is-coming-here-is-a-sneak-peek/

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

Create Radio Button Component in React Native

This tutorial shows you how you can create a radio button component in React Native application pretty smoothly. We will learn the easiest way to deal with React Native Radio Buttons.

Read more to click here
https://www.positronx.io/create-radio-button-component-in-react-native/

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

Preactの始め方&Reactとの違い

Reactのサブセットライブラリとして、Preactというものがあります。より軽量なReactという位置付けで、ダウンロードするjsファイルのサイズが肥大しがちなSPAサイトにおいて、パフォーマンスを向上させるための有効な選択肢の一つとして捉えられているようです。

使い方などを勉強してみたので、メモ代わりとしてPreactプロジェクトの始め方、Reactとの違いなどについてまとめておきます。

Reactとのサイズ差

PreactはReactより軽量ということですが、具体的にどのくらい違うのでしょうか?公式ドキュメントには3kB!!と書かれています。それではReactのライブラリサイズは...?ということで、「BUNDLE PHOBIA」というサイトで調べてみると、

  • React: 6.4kB(minified), 2.6kB(minified & gzipped)
  • Preact: 9.5kB(minified), 3.7kB(minified & gzipped)

あら...?Reactのほうが軽くない...?と一瞬思ってしまうのですが、ReactをWebで使うには、react-domも必要です。こいつのサイズを見てみると、

  • react-dom: 114.6kB(minified), 36.2kB(minified & gzipped)

デカい!!Preactではreact-domは必要ないので、その分軽い、ということでしょうか。

Preactの始め方

Preactの公式ドキュメントに行くと、「Getting Started」というページがあり、その通りに進めればcreate-react-appに近い感じで簡単にプロジェクトをセットアップできます。

ただ、今回はその方法を使わずに手作業で必要なセットアップを行なっていきます。シンプルにするため、TypeScriptは導入しません。
まずは適当なディレクトリを作成し、npm initを入力。

mkdir preact-sample
npm init -y

なにはともあれ、Preactをインストールします。

npm i --save preact

js, jsxファイルをバンドルするため、Webpack系のライブラリをインストール。

npm i --save-dev webpack webpack-cli webpack-dev-server

続けて、jsxファイルをトランスパイルするためのライブラリをインストール。

npm i --save-dev @babel/core @babel/preset-env babel-core babel-loader babel-preset-preact

.babelrcを作成します。

.babelrc
{
  "presets": [
    "@babel/preset-env",
    "preact"
  ]
}

次に、webpack.config.jsを作成して設定を記述していきます。エントリポイントはsrc/index.jsx、バンドルファイルの出力先はdist/js/bundle.jsとします。

webpack.config.js
const path = require("path");
const webpack = require("webpack");

module.exports = env => {
  return {
    entry: "./src/index.jsx",
    output: {
      filename: "./js/bundle.js"
    },

    resolve: {
      extensions: [".js", ".jsx"]
    },

    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: ["babel-loader"]
        }
      ]
    },

    devServer: {
      contentBase: path.join(__dirname, "./dist"),
      watchContentBase: true,
    }
  }
}

次にdistディレクトリを作って、その直下にindex.htmlを作成。

index.html
<html>
<head>
  <title>Preact sample</title>
</head>
<body>
  <div id="app"></div>
  <script src="./js/bundle.js"></script>
</body>
</html>

最後にsrc/index.jsxに表示したいコンテンツを書けばOKです。

index.jsx
import { h, render } from "preact";

const App = () => {
  return <div>Hello World!</div>
};

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

これでwebpackのdevServerを立ち上げれば、Hello World!が表示されるはずです。あとは、普段Reactを書いているのとほぼ同じ感覚で進めていけます。関数コンポーネントやhooksも使えます。

ReactとPreactの違い

Reactとは何が違うのか、どういった機能が使えないのかが気になるところですが、それらは公式ドキュメントにまとめられています。(Differences to React

そこまで分量も多くないので、自身で確認してみるのが一番良いと思います。Reactにあって、Preactにない機能の部分だけを抜粋します。

PropTypesによるバリデーションチェック

こういうのですね(以下のコードはReact公式ドキュメントからの抜粋です)。

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

型チェックをするなら今ならTypeScriptを使うことが多いはずなので、なくてもとくに困らないような気はします。ちなみにPropTypesが必要なら、preact-compatというライブラリを入れることで補完できるようです。

React.Children

React.Childrenについての解説はこちら

props.childrenはよく使いますが、React.Childrenは使ったことがないですね。。。こちらもpreact-compatで補完できるとのことです。

Synthetic Events

Reactではイベントハンドラに渡されるイベントは全てラップされたものになっています。ラップされたイベントインスタンスは同じAPIインターフェースを持つので、ブラウザ間の差異を意識することなく、イベント処理を行えます。また、Synthetic Eventsインスタンスはプールされ、同じインスタンスが使い回されます。そのために、イベント処理が終了するとインスタンスの各フィールドが全てnullで初期化されるという特徴も持っています。

この仕様を知らずにハマった記憶もありますが、PreactではSynthetic Events機能は提供せず、ブラウザネイティブのイベントインスタンスがイベントハンドラに渡されるようです。これは知っておくべき違いな気がしますね。

まとめ

パッと調べた感じだと、Preactでよくない?と思えるのですが、他ライブラリとの互換性などの問題もあるのかもしれません。まずは小さいプロジェクトで試してみて、実際に使った場合にどんな問題が起こるのかを把握してから適切に使いたいですね。

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

Formikを導入する

Formikの使い勝手が良すぎるので、備忘録がてらQiitaに残します。
(↓ 分かりやすく説明しているYoutubeもたくさんアップされているので、参考にぜひ
https://youtu.be/FD50LPJ6bjE)

目次

Formikの導入

まずはライブラリをインストールしていきます。
(公式サイトはこちら → https://jaredpalmer.com/formik/)

$ npm i formik

続いて、<form> タグを <Formik> へ変更します。
<Formik>タグに関する公式サイトはこちら → <Formik />

import { Form, Formik } from "formik"

export const App: React.FC<Props> = () => {
  return (
    // 変更前
    <form>
    ...
    </form>

    // 変更後
    <Formik
      initialValues={}
      onSubmit={(values, actions) => {
        // Submit した時の記述
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
      ...
      </Form>
    </Formik>
  )
}

また、初期値と型の定義をして下準備を整えておきます。
今回は下のようなデータを扱うことにしていきます。

  • カテゴリ「犬」、「猫」、「うさぎ」から選ぶ
  • 「ユーザー名」、「メールアドレス」
  • questions は何個でも入力することができ、追加・削除ができる
  • カテゴリを選択直後、question 内の動物タイプがカテゴリごとに描画される
  • 動物カテゴリ「dogA」「catA」「rabbitA」を選択した時だけ、questionBを描画する
type Category = "dog" | "cat" | "rabbit"

// Formで使用する型
export type BaseValues = {
  userName: string
  email: string
  category: Category
  questions: Question[]
}

export type Question = {
  dogType?: "dogA" | "dogB" | "dogC" | ""
  catType?: "catA" | "catB" | "catC" | ""
  rabbitType?: "rabbitA" | "rabbitB" | "rabbitC" | ""
  questionA: string
  questionB?: string
}

初期値を定義し、<Formik>initialValuesにセットする

const initialValues = {
  category: "dog" as Category,
  userName: "",
  email: "",
  questions: [
    {
      questionA: "",
    },
  ],
}

...

    <Formik
      // 変更後
      initialValues={initialValues}
      onSubmit={(values, actions) => {}}
    >
      ...
    </Formik>

各入力エリアにFormikを適用する

テキストエリア、セレクトボックスをそれぞれ、Formikの<Field>タグに差し替えます。
バリデーション用のエラーメッセージは、一旦後回しにします。
<Field />タグに関する公式サイトはこちら → <Field />
nameを正しくセットすることを忘れずに!

import { Form, Formik, Field } from "formik"

export const App: React.FC<Props> = () => {
  return (
    ...
    // 変更前
    <input type="text" placeholder="名前" />
    ...
    // 変更後
    <Field
      type="text"
      name="userName"
      placeholder="名前"
    />

    ...

    // 変更前
    <select>
      <option selected></option>
      <option></option>
      <option>うさぎ</option>
    </select>
    ...
    // 変更後
    <Field as="select" name="category">
      <option value="dog"></option>
      <option value="cat"></option>
      <option value="rabbit">うさぎ</option>
    </Field>
    ...
  )
}

全て差し替えたところでSubmitすると、onSubmitの引数valuesに入力した値が入っていることを確認できます。

1.png

配列に対応する

配列データはFormikの<FieldArray />を使っていきます。
<FieldArray />タグに関する公式サイトはこちら → <FieldArray />
今回はquestionsに、型Questionのオブジェクトを配列で管理することにしています。

"data": [
  {
    "dogType": "dogA",
    "questionA": "foo",
    "questionB": "hoo"
  },
  {
    "dogType": "dogB",
    "questionA": "bar",
    "questionB": "moo"
  },
  ...
],
import { FieldArray, useFormikContext } from "formik"

export const Questions: React.FC<Props> = () => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <FieldArray
      name="questions"
      render={() => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              <PetType fieldName={`questions.${index}`} />
              <Details fieldName={`questions.${index}`} question={question} />
            </div>
          ))}
        </div>
      )}
    />
  )
}

<FieldArray />nameをセットし、renderの中で values.questionsmapでループさせています。
入力エリアは別コンポーネントで管理するようにし、子コンポーネントにはnameを渡すようにしました。
1レコードごとのそれぞれの値は、questions.${index}.XXX で参照できます。

子コンポーネントの方もお見せします。

import { Field } from "formik"

type Props = {
  fieldName: string
  children?: never
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  return (
    <div>
      ...
      <div>タイプ</div>
      <Field as="select" name={`${fieldName}.dogType`}>
        <option value="">選択してください</option>
        <option value="dogA">dogA</option>
        <option value="dogB">dogB</option>
        <option value="dogC">dogC</option>
      </Field>
      ...
    </div>
  )
}

特定の入力エリアの値が変わったことを検知し、表示を変更する

今回下の用件で作成していきます。

カテゴリを選択した直後、各カテゴリに対応する入力エリアを描画させる

別コンポーネントで変更された値を検知するのに、useFormikContext()を使って、フォームの値を取得します。

import { Field, useFormikContext } from "formik"
import { BaseValues } from "./App"

type Props = {
  children?: never
  fieldName: string
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  const { values } = useFormikContext<BaseValues>()

  return (
    ...
    <div>タイプ</div>
    <div>
      {values.category === "dog" && (
        <Field as="select" name={`${fieldName}.dogType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
      {values.category === "cat" && (
        <Field as="select" name={`${fieldName}.catType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
      {values.category === "rabbit" && (
        <Field as="select" name={`${fieldName}.rabbitType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
    </div>
  )
}

ただ、これだと入力エリアの切り替えは上手くいきましたが、カテゴリを何回か切り替えてタイプ別ドロップダウンも変更を続けた時、
切り替える前の値が残ってしまっています。
正しくは、選択中のカテゴリだけの動物タイプの値を取得し、選択外は取得させたくないので、一手間加える必要があります。

3.gif

ぐぬぬ、、そうすると…

私はこう実装しました。

import { useField, useFormikContext } from "formik"
import { BaseValues, Category } from "./App"

type Props = {
  children?: never
}

export const AnimalCategory: React.FC<Props> = () => {
  const { values, setFieldValue } = useFormikContext<BaseValues>()
  const [field] = useField<Category>("category")

  const handleChange = (value: Category): void => {
    setFieldValue("category", value)

    values.questions.forEach((_q, index) => {
      switch (value) {
        case "dog":
          setFieldValue(`questions.${index}.dogType`, "")
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "cat":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, "")
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "rabbit":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, "")
          break
      }
    })
  }

  return (
    <div>
      <div>カテゴリ</div>
      <select
        {...field}
        onChange={(event) => {
          handleChange(event.target.value as Category)
        }}
        onBlur={(event) => {
          handleChange(event.target.value as Category)
        }}
      >
        <option value="dog"></option>
        <option value="cat"></option>
        <option value="rabbit">うさぎ</option>
      </select>
    </div>
  )
}

setFieldValueを使うと手動で値の変更などができます。カテゴリが変更された時点、つまり、onChange()が走った時点で、
該当の動物タイプの値をセットし、且つ、除外するものはundefinedをセットします。
undefinedに指定するとvaluesに含まれないので、Submitすると除外された状態でPOSTなりPUTなりできます。

上記ではswitchで値をジャッジし、都度setFieldValueさせるようにしています。
(ベストプラクティスご存知の方はぜひアドバイスください…!)
すると、期待通りのvaluesになってくれました。

4.gif

追加、削除

<FieldArray />pushremoveを使うと、配列データに追加・削除などの操作ができます。
FieldArray Helpersに関する公式サイトはこちら → FieldArray Helpers

export const Questions: React.FC<Props> = () => {
  ...
  return (
    <FieldArray
      name="questions"
      render={({ remove, push }) => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              // 変更後
              <button onClick={() => { remove(index) }}>削除</button>
              ...
            </div>
          ))}
          // 変更後
          <button
            onClick={(e) => {
              e.preventDefault()
              push({ questionA: "" })
            }}
          >
            追加
          </button>
        </div>
      )}
    />
  )
}

renderremovepushを渡し、削除・追加ボタンのonClickで使います。

2.gif

バリデーションを適用する

各questionの questionAだけ入力必須にします。
Submitした時、questionAが空だったら該当の入力エリア下にエラーメッセージを表示させます。
バリデーションはyupを使おうと思うので、ライブラリインストールから始めることにします。

$ npm i yup

$ npm i @types/yup

バリデーションルールは別ファイルにまとめ、yupの書き方に則って追加していきます。
(yupの詳細は省きます)

import { array, object, string } from "yup"

export const validationSchema = object({
  questions: array().of(
    object().shape({
      questionA: string()
        .required("必須です")
        .typeError("必須です"),
    })
  ),
})

<Formik />タグに validationSchemaを追加していきます。

import { validationSchema } from "./validationSchema"

export const App: React.FC<Props> = () => {
  return (
    <Formik<BaseValues>
      initialValues={initialValues}
      // 変更後
      validationSchema={validationSchema}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
        ...
        <button type="submit">
          登録する
        </button>
      </Form>
    </Formik>
  )
}

入力エリア下にエラーメッセージが表示されるよう、コンポーネントの方も変えていきます。
このまま<Field />タグを使ってerrorを取得するのも良いですが、今回はuseField()を使ってエラーを取得するように実装していこうと思います。
useField()に関する公式サイトはこちら → useField()
useFieldの()に useField(該当フィールドのname)を指定し、特定のフィールドのデータ(FieldInputPropsFieldMetaPropsFieldHelperProps)が取得できます。

import { Field, useField } from "formik"

export const Details: React.FC<Props> = ({ fieldName }) => {
  const [field, meta] = useField(`${fieldName}.questionA`)

  return (
    <div>
      <div>Q1.</div>
      // 変更前
      <Field
        as="textarea"
        name={`${fieldName}.questionA`}
        className="textarea"
      />

      // 変更後
      <textarea {...field} />
      {meta.touched && meta.error ? (
        <div className="error">{meta.error}</div>
      ) : null}
    </div>
  )
}

感想 & まとめ

Formikは複雑な処理に向いていると思ってます。useFormikContext()は非常に有用です。
ですが、「ここまでのフォームじゃないんだけど」って時には、別にFormikでなくても良いと思ってます。
react-hook-formや、別のライブラリ有用だと思っています。
「Formikはかゆいところに手が届きすぎる」「こんなことも出来るんだ!?」そんな感覚です。
ぜひFormikで感動を味わってください。

コード共有

ここまでのコードは以下の通りです。
(参考例として作ったので、あくまでも本記事のサンプル用として見てください!)

App.tsx

import { Form, Formik } from "formik"
import React from "react"
import { AnimalCategory } from "./AnimalCategory"
import { Questions } from "./Questions"
import { User } from "./User"
import { validationSchema } from "./validationSchema"

export type Category = "dog" | "cat" | "rabbit"
// Formで使用する型
export type BaseValues = {
  category: Category
  email: string
  questions: Question[]
  userName: string
}

export type Question = {
  catType?: "catA" | "catB" | "catC" | ""
  dogType?: "dogA" | "dogB" | "dogC" | ""
  questionA: string
  questionB?: string
  rabbitType?: "rabbitA" | "rabbitB" | "rabbitC" | ""
}

type Props = {
  children?: never
}

const initialValues = {
  category: "dog" as Category,
  userName: "",
  email: "",
  questions: [
    {
      questionA: "",
    },
  ],
}

export const App: React.FC<Props> = () => {
  return (
    <Formik<BaseValues>
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
        <AnimalCategory />
        <User />
        <Questions />
        <button type="submit">
          登録する
        </button>
      </Form>
    </Formik>
  )
}

AnimalCategory.tsx

import { useField, useFormikContext } from "formik"
import React from "react"
import { BaseValues, Category } from "./App"

type Props = {
  children?: never
}

export const AnimalCategory: React.FC<Props> = () => {
  const { values, setFieldValue } = useFormikContext<BaseValues>()
  const [field] = useField<Category>("category")

  const handleChange = (value: Category): void => {
    setFieldValue("category", value)
    values.questions.forEach((_q, index) => {
      switch (value) {
        case "dog":
          setFieldValue(`questions.${index}.dogType`, "")
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "cat":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, "")
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "rabbit":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, "")
          break
      }
    })
  }

  return (
    <div>
      <div>カテゴリ</div>
      <select
        {...field}
        onChange={(event) => {
          handleChange(event.target.value as Category)
        }}
        onBlur={(event) => {
          handleChange(event.target.value as Category)
        }}
      >
        <option value="dog"></option>
        <option value="cat"></option>
        <option value="rabbit">うさぎ</option>
      </select>
    </div>
  )
}

User.tsx

import React from "react"
import { Field } from "formik"

type Props = {
  children?: never
}

export const User: React.FC<Props> = () => {
  return (
    <div>
      <div>ユーザー</div>
      <Field
        type="text"
        name="userName"
        placeholder="名前"
      />
       <Field
        type="text"
        name="email"
        placeholder="Email"
      />
    </div>
  )
}

Questions.tsx

import { FieldArray, useFormikContext } from "formik"
import React from "react"
import { BaseValues } from "./App"
import { Details } from "./Details"
import { PetType } from "./PetType"

type Props = {
  children?: never
}

export const Questions: React.FC<Props> = () => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <FieldArray
      name="questions"
      render={({ remove, push }) => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              <button
                onClick={() => { remove(index) }}
              >
                削除
              </button>
              <PetType fieldName={`questions.${index}`} />
              <Details fieldName={`questions.${index}`} question={question} />
            </div>
          ))}
          <button
            onClick={(e) => {
              e.preventDefault()
              push({ questionA: "" })
            }}
          >
            追加
          </button>
        </div>
      )}
    />
  )
}

PetType.tsx

import { Field, useFormikContext } from "formik"
import React from "react"
import { BaseValues } from "./App"

type Props = {
  children?: never
  fieldName: string
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <div className="field is-horizontal">
      <div>タイプ</div>
      <div className="field-body">
        <div className="field control">
          {values.category === "dog" && (
            <Field as="select" name={`${fieldName}.dogType`}>
              <option value="">選択してください</option>
              <option value="dogA">dogA</option>
              <option value="dogB">dogB</option>
              <option value="dogC">dogC</option>
            </Field>
          )}
          {values.category === "cat" && (
            <Field as="select" name={`${fieldName}.catType`}>
              <option value="">選択してください</option>
              <option value="catA">catA</option>
              <option value="catB">catB</option>
              <option value="catC">catC</option>
            </Field>
          )}
          {values.category === "rabbit" && (
            <Field as="select" name={`${fieldName}.rabbitType`}>
              <option value="">選択してください</option>
              <option value="rabbitA">rabbitA</option>
              <option value="rabbitB">rabbitB</option>
              <option value="rabbitC">rabbitC</option>
            </Field>
          )}
        </div>
      </div>
    </div>
  )
}

Details.tsx

import { Field, useField } from "formik"
import React, { Fragment } from "react"
import { Question } from "./App"

type Props = {
  children?: never
  fieldName: string
  question: Question
}

export const Details: React.FC<Props> = ({ fieldName, question }) => {
  const [field, meta] = useField(`${fieldName}.questionA`)

  return (
    <Fragment>
      <div>
        <div>Q1.</div>
        <textarea
          {...field}
          className={`textarea
          ${meta.touched && meta.error && "is-danger"}`}
        />
        {meta.touched && meta.error ? (
          <div className="help is-danger">{meta.error}</div>
        ) : null}
      {(question.dogType === "dogA" ||
        question.catType === "catA" ||
        question.rabbitType === "rabbitA") && (
        <div>
          <div>Q2.</div>
          <Field
            as="textarea"
            name={`${fieldName}.questionB`}
            placeholder="DogA, CatA, RabbitA のみ表示する入力エリア"
          />
        </div>
      )}
    </Fragment>
  )
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

emotion (emotion.js) のStyled Componentsで作成した要素にカスタム属性を渡したくなったら (shouldForwardProp)

今日ハマったのでメモ。

Reactはバージョン16から要素に好きに属性を生やせます

Reactはバージョン16からカスタム属性の利用を認めています。

たとえば、下記のような属性指定ですね。

<a mycustomattr="customvalue">foobar</a>

上記の場合だときちんと mycustomattr="customvalue" が実際のDOMにレンダリングされます。

もともとReactでは、HTML公認のカスタム属性である data-** や、幅広い語彙を持つ aria-** は利用を認められていたのですが、その他の自作の属性、あるいはHTML5でいなくなった古い属性(e.g. <td> 要素の align 属性)は、カスタム属性が使えないと表現できません。

emotionのStyled Componentsはそのままだとカスタム属性をはたき落とします

そして本題ですが、タイトルの通りemotionのStyled Componentsは、デフォルトでカスタム属性をはたき落とします。

たとえば、

const CustomElem = styled.div`
  color: red;
`

// @ts-ignore
return <CustomElem mycustomattr="customvalue">Hello</CustomElem>

(@ts-ignore はTypeScriptさんが mycustomattr なんてないよ!とエラーを出すのを抑制するために入れています。JSの人は要りません。)

上記のコードだと mycustomattr がはたき落とされます。基本的には、is-prop-validという実装に従って、HTMLに無さそうな属性は自動的に消してくれるようです。

今までのReactであれば、存在しない属性を渡すと警告を出していたので、特に自前propsが無意識に渡されて警告が出がちなStyled Componentsにおいては重要な機能と言えるでしょう。

emotionでカスタム属性を通す方法

ただ、当然このままだと困るケースもあるわけで、じゃあそういう場合はどうするかというと、ドキュメントに書いてあるとおり(←最初からちゃんとドキュメントを読めよ!)、 shouldForwardProp を指定して、自分のカスタム属性を通してあげましょう。

const CustomElem = styled("div", {
  // mycustomattr と children(子要素) のみ許可する
  shouldForwardProp: (props) => ["mycustomattr", "children"].includes(props)
})`
  color: red;
`

// @ts-ignore
return <CustomElem mycustomattr="customvalue">Hello</CustomElem>

ちゃんと結果のDOMに mycustomattr がいるはずです。逆に、他の属性を渡しても何もDOMに影響しないはずです。

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