- 投稿日:2020-02-26T23:07:01+09:00
【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最高です。
- 投稿日:2020-02-26T23:07:01+09:00
【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最高です。
- 投稿日:2020-02-26T21:27:29+09:00
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/
- 投稿日:2020-02-26T21:26:30+09:00
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/
- 投稿日:2020-02-26T21:23:21+09:00
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/
- 投稿日:2020-02-26T21:10:09+09:00
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/
- 投稿日:2020-02-26T21:05:16+09:00
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/
- 投稿日:2020-02-26T21:02:49+09:00
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/
- 投稿日:2020-02-26T21:01:33+09:00
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/
- 投稿日:2020-02-26T13:20:03+09:00
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.jsconst 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.jsximport { 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でよくない?と思えるのですが、他ライブラリとの互換性などの問題もあるのかもしれません。まずは小さいプロジェクトで試してみて、実際に使った場合にどんな問題が起こるのかを把握してから適切に使いたいですね。
- 投稿日:2020-02-26T10:24:46+09:00
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
に入力した値が入っていることを確認できます。配列に対応する
配列データは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.questions
をmap
でループさせています。
入力エリアは別コンポーネントで管理するようにし、子コンポーネントには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> ) }ただ、これだと入力エリアの切り替えは上手くいきましたが、カテゴリを何回か切り替えてタイプ別ドロップダウンも変更を続けた時、
切り替える前の値が残ってしまっています。
正しくは、選択中のカテゴリだけの動物タイプの値を取得し、選択外は取得させたくないので、一手間加える必要があります。ぐぬぬ、、そうすると…
私はこう実装しました。
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になってくれました。追加、削除
<FieldArray />
のpush
やremove
を使うと、配列データに追加・削除などの操作ができます。
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> )} /> ) }
render
でremove
、push
を渡し、削除・追加ボタンのonClick
で使います。バリデーションを適用する
各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)
を指定し、特定のフィールドのデータ(FieldInputProps
、FieldMetaProps
、FieldHelperProps
)が取得できます。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> ) }
- 投稿日:2020-02-26T01:07:11+09:00
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に影響しないはずです。