20210418のReactに関する記事は14件です。

Gatsby.jsを一週間勉強して分かったこと

皆さんReactのフレームワークGatsby.jsをご存じでしょうか? 恐らくReactのフレームワークと言ったらNext.jsが代表かと思われます。 そこで今回は、Gatsby.jsを一週間勉強してGatsby.js良いところ悪いところ完全な主観でまとめてみたいと思います。 もう一回言います。 主観でまとめます。 悪魔で僕個人の意見なので、鵜呑みにはしないようお願いします。 こんな感じなんだと思って頂ければ十分です。 それでは早速良いところをまとめていきます。 良いところ ルーティングの設定が不要 まず、1つ目はルーティングについてです。 Reactでルーティングを行う時って結構面倒ですよね。 必要なパッケージをインストールしたり、あとはpushメソッドとかをstoreで管理したり色々と慣れるまでは面倒。 Gatsby.jsではそれが不要です! Gatsby.jsはデフォルトでsrc\pagesというフォルダがあります。 ここがルーティングそのものになります。 どういうことか。 例えば、http://localhost:8000/contact/inputというパスはsrc/pages/contact/input.jsを参照するということです! 要するにルーティングを行う場合は、src/pages配下にあるファイル・フォルダそのものがルーティングになっているわけです! わざわざ設定しなくても勝手にやってくれる! 僕にとってはこれが一番良い点でした。 ページ遷移 ページ遷移を行う場合もGatsbyではデフォルトで用意されています。 わざわざreact-routerやreact-router-domをインストールする必要がありません。 以下に例を挙げておきます。 import React from "react" import { Link } from "gatsby" const Example = (props) => { return ( <> <Link to="/contact/input">お問い合わせフォーム</Link> </> ) } export default Example メソッドとしてページ遷移を行う際もnavigate()というメソッドがもともと用意されています。 これはpushメソッドと同じ動きをします。 import React from "react" import { navigate } from 'gatsby-link' const Example = (props) => { return ( <> <Button variant="outlined" onClick={() => navigate('/contact/input')}> お問い合わせフォーム </Button> </> ) } export default Example こんな感じでルーティングに関しての機能がデフォルトで用意されているのが最高でした。 悪い点 ヘッダーやフッターコンポーネントはページごとにレンダリング ReactやVueでヘッダーやフッターなどのコンポーネントを使用する際、メインとなるファイルに<Header />などを記述すことで各ファイルにこれらを書かなくて済むという仕組みですが、Gatsbyではこれができません。 なぜかは知りません。 もしかしたら出来るかもしれないです。 なので、マイページ使うヘッダーやフッターはlayout.jsにまとめてそれを毎回呼び出すというのが定石らしいです。 Layoutの使い方は公式のドキュメントに詳しく書いてあるのでリンクを貼っておきますね。 https://www.gatsbyjs.com/docs/how-to/routing/layout-components/ SPAならこの問題を解決してほしいと願っております。 まとめ 結論、良い点は ルーティングの設定が不要 ページ遷移の機能がデフォルトで備わっている で、悪い点は 毎回使うコンポーネントは毎回レンダリングを行わなければいけない です! まだ1週間しか勉強していないので、もっと良い点、悪い点があると思います。 学習を進めていくにあたってまた記事として書いていこうかなと思います。 引き続き勉強頑張ります。 Thank you for reading
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm run buildとは

はじめに Reactでアプリを作る際、create-react-appと同様にターミナルで行っている、Node.jsのbuild toolである「npm run build」 なんとな〜くの理解で機械的に行っていたので、その概要を整理する npm run buildは何をしているのか ずばり「JSファイルやCSSファイルをマージ(合併)している」 ではなぜマージするのか? 開発段階では以下のように1つのアプリに対しJSやCSSファイルが複数存在することがほとんど css/ mpp.css design.css visuals.css ... js/ service.js validator.js container.js ... しかしアプリの運用環境では複数のファイルが存在することはサイトの読み込みが遅くなることに繋がります (ユーザーがサイトにアクセスすると、各ファイルに追加のHTTPリクエストが必要になる為) なのでこの複数存在するファイルをマージしbuildという小さなディレクトリに圧縮する魔法のコマンド、それが「npm run build」 実行すると、、 build/ static/ css/ main.css js/ main.js buildディレクトリが作成されスッキリ これによりユーザーが取得するファイルの数とサイズを最小限に抑えることができました おわりに buildによるサイト表示速度改善問題 しかし規模の大きい開発ならまだしも、個人開発レベルでは恩恵は皆無といっていいそうな
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの基礎勉強記録

はじめに 初学者の私がReactについて学んだことをアウトプットする記事です。 主な目的は自分がこの記事を開けばReactについて復習できるようにする備忘録です。 目次 Reactとは JSXの基礎知識と文法 create-react-app コンポーネントの基本データの受け渡しと再利用 コンポーネントの状態stateの設定と取得と変更 コンポーネントのライフサイクル 副作用の有る処理を書こう importとexport モジュールを使いこなそう React-Hooks 関数コンポーネントでもステートを使おう React-Hooks 関数コンポーネントでもライフサイクルを使おう Reactとは 概要 Facebook社が開発したライブラリで、WebのUIを作るもので、決してSPAを作るものではない。 チャットボットだけReact、ボタン機能だけReactで実装することも出来る。 コンポーネントとは UIは2つに分類される。 1. 見た目(View) 2. 機能(Controller) コンポーネント=見た目+機能 コンポーネントのツリー構造 HTMLの下にヘッダーやメイン、その下にnav,login-button,content,linkなどが存在する。 DOM DOM=Document Object Model=インターフェイス HTMLにアクセスする窓口 HTML構造、見た目、コンテンツを変更する Virtual DOMとは ブラウザのレンダリングと別管理、効率よくDOM操作できる。 通常のDOM操作の場合、 document.getElementById('hoge').innerText = 'fuga' ReactのVirtualDOM操作 render(<div id='hoge'>fuga</div>) 差分描画について 変更されたVirtualDOMの差分を再描画するので、従来のHTMLをまるごと受け渡しするより早く描画できる。 JSXが使える JavaScript内でHTMLっぽく書ける。 Reactの最大の強み。 JSXの基礎知識と文法 JSXとは JavaScript内でHTMLを簡単に記述するための言語で、JavaScriptの拡張言語である。 Facebook社が開発していて、Reactの公式ドキュメントはほぼJSXで書かれている。 Reactでは業界標準である。 なぜJSXなのか (わかりにくいので後で書き直します) 通常のJavaScriptでHTMLを記述すると(DOM操作) const fuga = '<h1>Hello,World!</h1>' document.getElementById('hoge').innerHTML = huga となる。 量が増えると、const foo='<h2>React Commentary</h2>したあと、それのID、中身を変数で代入しなければならない。それをそれぞれやらなければならないので、わけわからなくなる。 JSXを使えばHTMLっぽく書けて可読性が高い。それだけでJSXを使う理由がある。 トランスパイラとは 翻訳のような役割。 JSXの構文はブラウザが理解出来ないので、トランスパイラで翻訳する必要がある。 例えば、JSXからJavaScript(ES6)など。 トランスパイラはBabel,CoffeeScript,TypeScriptなどが使われる。 もしJSXが無かったら React.createElomentを使う羽目になり、いちいちこれでHTMLを宣言しなければいけなくて面倒。 トランスパイラで翻訳されたあとはこのcreateElementが使われている。 基礎文法 Reactパッケージのインストールが必須。 .jsxファイルの先頭で宣言する。 import React from 'react' HTMLとほぼ同じ記述 例外はある。 <div class='hoge'> → <div className='hoge> など。 {}内に変数や関数を埋め込める const foo = <h1>Hello</h1> {foo} でHelloが表示できる。 つまり、{}内だけはJavaScriptの空間に出来る。 変数名などは全てキャメルケースで書く javascriptなので、fooBar,classNameなど、ラクダのコブのように単語の区切りが大文字になる。 ハイフンで繋ぐケバブケースは使わない。foo-bar,class-nameなどは禁止。 空要素は必ず閉じる。 HTMLでは<input ~~~ >でよいが、Reactでは<input ~~~ /> <img ~~~ />のように、/>で閉じる必要がある。 create-react-app 簡単に言うと React開発環境をワン・コマンドで。 必要なもの node 8.10 npm 5.6 インストール用にhomebrewとnodebrew なぜこれを使うのか 普通に環境構築しようとすると、くっっっっっっっっそ難しい。 トランスパイラのbabelやバンドラのwebpackの設定が必要だからである。 熟練者でもこれは難しいらしい。 create-react-appで作成されるフォルダ src:コンポーネントを作るjsファイルを入れる public:htmlファイルや設定ファイルを入れる build:本番環境用のファイルを入れる src(ソースフォルダ)の中身 js,cssをrootに書き換えて、publicのindex.htmlで出力する。 publicフォルダの中身 favicon.ico:タブの左側のアイコン 画像ファイル manifest.json:ホーム画面に追加が出来る設定ファイル buildフォルダの中身 srcの中に入ってたものを1つにまとめて入れてある。 まとめる役割を持つのがバンドラ(webpack)。 コマンド集 npm run build srcとpublic内のファイルを1つにまとめて(バンドル)、buildディレクトリに出力する。 同時にトランスパイル(翻訳)もされている。 npm start ローカルサーバーを起動してReactアプリを確認できる。 npm run eject babelやwebpackの設定を変更する その他の環境構築ツール Next.js : SSR,SSGが出来る。SEO対策に最適。これからどんどん流行るであろう今最もアツいフレームワーク。 Gatsby : 静的ウェブサイトに使える。 コンポーネントの基本 データの受け渡しと再利用 最初に コンポーネントとは見た目プラス機能ということ なぜコンポーネントを使うのか 再利用するため ボタンコンポーネントに引数を渡して(props)色んな場所で使う 分割統治するため それぞれのコンポーネント同士は互いに関連しあわないことで管理しやすい(疎結合) 変更に強くするため 分割統治すると変更に強くなる。 コンポーネントを変更しても、他のコンポーネントには一切関係がない(アトミックデザイン) コンポーネントの種類 Class Component : クラスによって定義されたコンポーネント(React-Hooksがデファクトスタンダードになっている現在、ほぼ使われていないので注意) Functional Component : 関数型で定義されたコンポーネント FunctionalComponentの特徴 ES6のアロー関数で記述 stateを持たない(stateless) propsを引数に受け取る JSXをreturnする。 ClassComponentの特徴 React.Componentを継承 ライフサイクルやstateを持つ コンポーネントが出てくるときにどう動くか、消えるときにどう動くか、これをライフサイクルと言う。 propsにはthisが必要 this.propsの意味は、このクラス内のporpsだよって意味 renderメソッド内でJSXをreturnする 記述量が多い代わりに状態(state)を持てる (最近はクラスコンポーネントを使わず、なるべくファンクショナルコンポーネントを使う。) propsでデータを受け渡す 親コンポーネント const Blog = () => { return ( <div> <Article title={'React'} /> </div> ); } 子コンポーネント const Article = (props) => { return ( <div> <h2>{props.title}</h2> </div> ); } 受け渡せるデータ型 {}内に記述する。 文字列、数値、真偽値、配列、オブジェクト、変数、何でも渡せる。 文字列は{}無しでもOK 再利用する コンポーネントを使い回す。 <Articl title={'React'} /> <Articl title={'JSX'} /> コンポーネントの状態 stateの設定と取得と変更 状態(state)とは コンポーネント内で管理する変数 ローカルステートと呼ばれる。(reduxではグローバルステートがある。) propsとして子コンポーネントに渡せる。 なぜstateを使うのか render()内で値を変更してはいけないから。 (stateが変わると再レンダーされる。その際にまたstateが変わって再レンダーされる。以下無限ループ) setState()で値を変更する。 stateの変更=再レンダーのきっかけ ページリロードせず表示を切り替えられる。 stateの設定方法 ClassComponentが前提 constructor()内で宣言 オブジェクト型で記述 constructor(props) { super(props); this.state = { isPublished: false }} stateの取得 同コンポーネント内ならthis.state.key名で取得できる 子コンポーネントで参照したい場合はpropsで渡す <Article title='React' isPublished={this.state.isPublished} />で渡せる stateの変更方法 setState()を使う 関数にラップするのが一般的 setState()内に記述されたstateのみを変更 公開状態を反転させる関数を定義する togglePublished = () => { this.setState({ isPublished: !this.state.isPublished }) }; // ここの!は反転させるの意味 コンポーネントのライフサイクル 副作用のある処理を書こう ライフサイクルとは コンポーネントの時間の流れ 生まれて、成長して、死ぬまでの循環 それぞれの段階で必要な処理を記述 Reactの基礎中の基礎 3種類のライフサイクル Mounting : コンポーネントが配置される(生まれる)期間 Updating : コンポーネントが変更される(成長する)期間 Unmounting : コンポーネントが破棄される(死ぬ)期間 なぜライフサイクルを使うのか 関数の外に影響を与える関数を記述するため DOM変更、API通信、ログ出力、setState()など 副作用=適切な場所に配置すべき処理 それぞれの期間 Mounting→constructor()→render()→componentDidMount() Updating→render()→componentDidUpdate() Unmounting→componentWillUnmount() 主要メソッド(Mount) constructor() : 初期化(stateなど) render() : VDOMを描画(JSXをリターン) componentDidMount() : render()後に1度だけ呼ばれる。リスナーの設定やAPI通信に使われる 主要メソッド(Update) render() : VDOMを再描画 componentDidUpdate() : 再render()後に呼ばれるスクロールイベントや条件付きイベント 例(新しいコメントが入ってきたときに、そこにスクロールするなど。) 主要メソッド(Unmount) componentWillUnmount() : コンポーネントが破棄される直前にリソースを開放するため リスナーの解除など。API通信の解除など。 importとexport モジュールを使いこなす モジュール化とは 多言語では昔からある概念 JavaScriptではES6から 基本的に1ファイル1モジュール 任意の場所で読み込める モジュール化の威力 分割統治できる : 大規模プログラムでも管理しやすい 任意の場所で読み込める : 必要なものを必要な分だけ 再利用ができる : 何度も同じコードを書かなくてよい Reactはモジュール化を強く推奨してる 名前付きexport 1モジュールから複数の関数をexport クラスはexportできない export funcrion 関数名(){} export const 関数名 = () => {} 名前無し(default)export 1ファイル(1モジュール)1export ES6で推奨方法 アロー関数は宣言後にexport クラスをexportできる モジュール全体のimport 名前無し(default)exportしたモジュールをimportする モジュール全体のimport 関数ごとのimport 名前付きexportされたモジュールをimportする {}内にimportしたい関数名 別名import 別名(エイリアス)をつけてimportできる モジュール全体なら* as name モジュール一部なら{A as B} ReactHooks 関数コンポーネントでもステートを使おう フックとは クラスの機能を関数コンポーネントでも使える React16.8から導入 100%後方互換だから小さく導入できる なぜフックを使うのか シンプルさを保つため クラスコンポーネントは難しい thisという悪魔 : JavaScriptでは他の言語のthisとは違う挙動をする stateを扱うロジックが複雑 複数のライフサイクルメソッドに副作用のある処理がまたがる : componentDidMountでイベントリスナーを設定して、componentDidUnmountで解除する。この記述をまとめたい。 これらを解決するのがフック useState()を使おう ステートフックと呼ばれる クラスコンポーネントでいうthis.stateとthis.setState()を代替する 複数のstateを扱うときはstateごとに宣言 useState()の使い方 1. useState関数をインポート : import React,{useState} from 'react; 2. 宣言する : const [isPublished, togglePublished] = useState(false); 左から順に「state変数名」、「state変更関数名」、「state初期値」 3. JSX内で使う : <input ~~~ onClick={() => togglePublishec(!isPublished)} /> ReactHooks 関数コンポーネントでもライフサイクルを使う useEffect()のメリット ライフサイクルメソッドを代替出来る 関数コンポーネントでライフサイクルを使える コードをまとめられる 機能ベースがよい(何をやっているのかわかる) 時の流れベースはよくない(ライフサイクルのメソッドごとがわかりづらい) useEffect()の仕組み レンダー毎にuseEffect()内の処理が走る 代替出来るメソッドは以下 componentDidMount componentDidUpdate componentDidUnmount パターン1 レンダーごと 基本の形 useEffect()内にCallback関数を書く Callbackはレンダー毎に呼ばれる returnするCallback関数はアンマウント時に呼ばれる(クリーンアップ関数) useEffect(() => { console.log('Render!') return () => { console.log('Unmounting!')}}) パターン2 マウント時のみ 第2引数の配列内の値を前回レンダーと今回レンダーで比較 : 変更があればCallback関数を実行 第2引数に空の配列を渡すと最初の1回のみ実行される useEffect(() => { console.log('Render!')}, [] ) パターン3 マウントとアンマウントのみ 1と2の複合系 通常のCallbackはマウント時のみ アンマウント時はreturn内のクリーンアップ関数が実行 useEffect(() => { console.log('Render!') return () => { console.log('Unmounting!)} }, [] ) パターン4 特定のレンダー時 マウント時に実行される limitの値が変わったときに実行される 例えば、limitの値がtrueからfalseになったとき。 const [limit, release] = useState(true); useEffect(() => { console.log('Render!') }, [limit])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript で React.forwardRef を簡潔に書く方法

はじめに Dwango でニコニコ生放送のフロント開発を担当している @misuken です。 TypeScript で React.forwardRef を簡潔に書く方法を紹介します。 React では Hooks が登場して以来、関数コンポーネントが主流となり、 forwardRef を使用する機会も増えているのではないでしょうか。 そんな身近な forwardRef ですが、以下のような問題に悩まされている方も少なくないはずです。 型の渡し方がよくわからない 型の渡し方が面倒くさい たまに型が通らないときがある そんなことを感じながら使っていたら、この記事を参考にしてみてください。 時間の無い方ヘ react-frec を使うと、 forwardRef 周りをシンプルに書けますよというお話です。 forwardRef の書き方に満足していますか? forwardRef の型パラメータには、 ref で扱うインスタンスの型 と コンポーネントの Props の型 を渡せるようになっており、 様々な型を自分で用意して渡すようになっています。 HTML の要素をそのままラップして利用する場合、 <button> に対応する型は JSX.IntrinsicElements["button"] で得ることができるので以下のように書けます。 // forwardRef<ref で扱うインスタンスの型, コンポーネントの Props の型> type ButtonProps = JSX.IntrinsicElements["button"]; const Component = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => { return <button {...props} ref={ref} /> }); しかし、この書き方の場合、 "button" と HTMLButtonElement と <button> を連動させる必要があり、冗長で面倒です。 型に慣れていない人にとっても、複数箇所の整合性を合わせるのでは負担が大きくなります。 forwardRef は戻り値の型が複雑 forwardRef で作成したコンポーネントの型を見てみると、思いの外複雑な型になっています。 React.ForwardRefExoticComponent< Pick< React.DetailedHTMLProps< React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement >, "key" | keyof React.ButtonHTMLAttributes<HTMLButtonElement> > & React.RefAttributes<HTMLButtonElement> > 型に慣れている人は読めば理解できるものの、そこまで展開してほしくないですし、型に慣れていない人からしたらもはや何が起きているのか全くわからないことでしょう。 簡単に書けるようにする この問題は、コンポーネントを代入する際に使用する型を用意することで解決できます。 以下のコードでは、ちゃんと props と ref にも適切な型が推論される上、 HtmlComponent1 と HtmlComponent2 は ElementFrec<"button"> というエイリアス型で表示されるため、型も把握しやすくなっています。 import React, { forwardRef, ForwardRefExoticComponent, PropsWithRef } from "react"; export type ElementFrec<T extends keyof JSX.IntrinsicElements> = ForwardRefExoticComponent<PropsWithRef<JSX.IntrinsicElements[T]>>; const HtmlComponent1: ElementFrec<"button"> = forwardRef((props, ref) => { return <button {...props} ref={ref} />; }); // タグ名をこうすると1箇所の修正で全てが連動します const TagName = "button"; const HtmlComponent2: ElementFrec<typeof TagName> = forwardRef((props, ref) => { return <TagName {...props} ref={ref} />; }); playground で実際のコードを確認 タグ名以外でも使えるようにする ElementFrec は HTML のタグ名しか対応していませんが、 forwardRef の中でクラスコンポーネントや、 ref の対象が HTMLElement ではない Props を持つコンポーネントもあります。 そのような場合にも対応した型も用意します。 export type Frec<T extends Component | ForwardRefExoticComponent<any> | ClassAttributes<any> | keyof JSX.IntrinsicElements> = // クラスコンポーネント用 T extends Component<infer P> ? ForwardRefExoticComponent<P & RefAttributes<T>> // ref を持つ Props 用 : T extends ClassAttributes<any> ? ForwardRefExoticComponent<PropsWithRef<T>> // タグ名用 : T extends keyof JSX.IntrinsicElements ? ElementFrec<T> // ForwardRefExoticComponent 用 : T; これで様々なコンポーネントの型でダイレクトに利用できるようになりました。 ref を転送して欲しいコンポーネントや Props 型が手元にあるとき、わざわざそのコンポーネントの Props 型を参照する必要も、冗長な記述もなくスマートに書けるようになります。 const TagName = "button"; // タグ名を利用 const HtmlComponent: Frec<typeof TagName> = forwardRef((props, ref) => { return <TagName {...props} ref={ref} />; }); // ForwardRefExoticComponent を再利用 const HtmlComponent2: Frec<typeof HtmlComponent> = forwardRef((props, ref) => { return <HtmlComponent {...props} ref={ref} />; }); // FooComponent と FooProps がクラスコンポーネントとして定義されている想定 declare class FooComponent extends Component<{ foo?: string }>{}; // クラスコンポーネントを利用 const ClassComponent: Frec<FooComponent> = forwardRef((props, ref) => { return <FooComponent {...props} ref={ref} />; }); playground で実際のコードを確認 styled-component と連携する場合 自分は普段 styled-component を使用していないので、もっと良い方法があるかもしれませんが、ざっと試した見た感じ、以下のように書くことで型が通ります。 この場合 Button は <button> の受け付ける Props 型と同じになります。 const tagName = "button"; const Styled = styled[tagName]``; const Button: Frec<typeof tagName> = forwardRef((props, ref) => { return <Styled {...props} ref={ref} />; }); まとめ 今回は forwardRef を使う際に面倒と感じる部分を簡潔に書く方法を紹介しました。 シンプルで明確な書き方が決まっているとスッキリしますね。 型推論が便利であると改めて感じました。 Frec を手軽に使いたい方もいると思うので、 react-frec として公開しています。 もし気になったら是非使ってみてはいかがでしょうか。 型の話 ここからはオマケで React の型の話をします。 React の型に詳しくない方も、これを読むと少し理解できる範囲が広がるかもしれません。 記事の上のほうで、普通に forwardRef を使用した場合、戻り値の型が複雑になるという話をしました。 なぜそういうことが起きるのかを一つずつ追って解説します。 React.DetailedHTMLProps は後で説明するので、 <button> に渡せる型とだけ理解しておけば大丈夫です。 React.ForwardRefExoticComponent< Pick< React.DetailedHTMLProps< React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement >, "key" | keyof React.ButtonHTMLAttributes<HTMLButtonElement> > & React.RefAttributes<HTMLButtonElement> > PropsWithoutRef 型の役割 この複雑さの根本は forwardRef 関数の戻り値型にある PropsWithoutRef<P> から来ています。 // T は HTMLElement の型で P は Props の型 function forwardRef<T, P = {}>( render: ForwardRefRenderFunction<T, P> ): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>; PropsWithoutRef<P> はその名の通り、 Props である P から ref を無くす挙動をとります。 これは P に { ref?: Ref<?> } が含まれていた場合。P & RefAttributes<T> とすると RefAttributes<T> つまり事実上の { ref?: Ref<T> } と交差型で合成する時に { ref?: Ref<?> } & { ref?: Ref<T> } となり ? の部分の内容によっては Ref<?> & Ref<T> で不整合が発生する可能性があるので、 P に ref のキーが含まれていたらそれを抜いた型にする。ということをやっています。 つまり、置換の形で上書き(Overwrite)している感じです。 PropsWithoutRef 型の中身 次に PropsWithoutRef の定義を見てみましょう。 type PropsWithoutRef<P> = // Just Pick would be sufficient for this, but I'm trying to avoid unnecessary mapping over union types // https://github.com/Microsoft/TypeScript/issues/28339 'ref' extends keyof P ? Pick<P, Exclude<keyof P, 'ref'>> : P; 'ref' が P の持つ全てのキーのいずれかに代入可能な条件を満たしていたら、 Pick<P, Exclude<keyof P, 'ref'>> それ以外なら P をそのまま返すように定義されています。 次は Pick<P, Exclude<keyof P, 'ref'>> の意味です。 Pick<P, Exclude<keyof P, 'ref'>> これは P というオブジェクトの型から Exclude<keyof P, 'ref'> のキーだけピックアップしたオブジェクトの型を得るということです。 次に Exclude<keyof P, 'ref'> の意味です。 Exclude<keyof P, 'ref'> これは P というオブジェクトの型が持つ全てのキーから 'ref' だけ除外するということです。 除外系の型を使うと型が展開されやすく、一気に理解しにくい型になりがちなので、型を作るときは引き算よりも足し算で作れるようにしたり、引き算するにも足した部分を引けるようにするなど工夫すると、理解しやすい型にできることがあります。 除外の仕組み P が React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> のとき、キーは React.ButtonHTMLAttributes<HTMLButtonElement> の持つキー(つまり <button> が持つ属性名全て)と "key" と "ref" で構成されています。 つまり React.ButtonHTMLAttributes<HTMLButtonElement> が HTML 責務の属性で、 React.DetailedHTMLProps の中で React 責務のプロパティ "key" と "ref" が追加されているということになります。 ここから "ref" を除外すると keyof React.ButtonHTMLAttributes<HTMLButtonElement> と "key" になります。 すると最初に紹介した以下の部分となり、 Props の "ref" 以外のキー全てを持った型となります。 Pick< React.DetailedHTMLProps< React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement >, "key" | keyof React.ButtonHTMLAttributes<HTMLButtonElement> > 意味としては実質以下と同じです。 Omit<JSX.IntrinsicElements["button"], "ref"> これに & React.RefAttributes<HTMLButtonElement> で ref 部分だけ足すことで、 Ref の不整合が起きないようになっています。 Overwrite 不要なパターン 不整合が起きる可能性があるパターンに関しては、たしかに必要な手続きではあるものの、確実に不整合が発生しないことがわかっている場合もあり、そのパターンを効率化しているのが Frec 型です。 例えば、以下のような場合。 type ButtonProps = JSX.IntrinsicElements["button"]; const Component = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => { return <button {...props} ref={ref} /> }); ButtonProps の ref は Ref<HTMLButtonElement> であることがわかっており、これを Overwrite したとしても、結果の型としては変わりません。(同じ型を抜いて追加するのは無意味) 結果が変わらないのに型を冗長に渡したり、戻り値の型がわかりにくくなっては損するだけなので、戻り値のほうの型から forwardRef の引数まで推論させることにより、効率化したというわけです。 DetailedHTMLProps 型 最後に DetailedHTMLProps 型の話をしておきましょう。 この型は、主に JSX.IntrinsicElements で使用されており、各要素ごとに対応する React.XxxHTMLAttributes<HTMLXxxElement> の組み合わせが定義されています。 <a> には IntrinsicElements["a"] が、 <button> には IntrinsicElements["button"] が Props 型として対応しています。 declare global { namespace JSX { // ... interface IntrinsicElements { // HTML a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>; abbr: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>; // ... // SVG svg: React.SVGProps<SVGSVGElement>; // ... } そして、この DetailedHTMLProps 型の定義を追っていくと結局は "ref" と "key" という React 責務の型を追加しているだけであることがわかります。 // E は HTML属性 の型で T は HTMLElement の型 type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E; // T は HTMLElement の型 interface ClassAttributes<T> extends Attributes { ref?: LegacyRef<T>; } // Attributes の中身 interface Attributes { key?: Key | null; } type Key = string | number; // LegacyRef の中身 // T は HTMLElement の型 type LegacyRef<T> = string | Ref<T>; type Ref<T> = RefCallback<T> | RefObject<T> | null; type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"]; interface RefObject<T> { readonly current: T | null; } フラット化するとこんな感じで意外とシンプルです。 type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = { key?: string | number | null; ref?: string | { bivarianceHack(instance: T | null): void }["bivarianceHack"] | { readonly current: T | null } | null } & E ref の string は LegacyRef という名前の定義に含まれているだけあって、廃止予定の文字列形式 ref のために残してある型なので、いずれ消えることでしょう。 bivarianceHack に関しては f_subal さんの bivarianceHack とは何か、なぜ必要なのか が参考になります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

5歳娘「パパのReact、めっちゃ遅いね!」

37歳無職ワイ ワイ「(カタカタカタカタ・・・ッターン!)」 娘(5歳)「パパ、今日は何してるの?」 ワイ「今日はな、むかしWordPressで作った自分用TODOリストの」 ワイ「デザインをリニューアルしてんねん」 よめ太郎「(そんなことより職を探せや)」 娘「へぇ〜」 娘「WordPressってことは、PHPを書いてるの?」 ワイ「いや、ちゃうで」 ワイ「リニューアル後は、フロント部分をReactで実装しようと思ってな」 ワイ「そこで、WordPressをREST APIモードで使うことにしたんや」 ワイ「つまり、WordPressを管理画面つきAPIみたいに使うってことや」 娘「要は、WordPressをヘッドレスCMSとして使うんだね」 ワイ「ヘッドレス・・・?」 ワイ「ちゃうちゃう、管理画面つきAPIや」 娘「・・・まぁいいや」 娘「とにかく、APIからTODOリストのデータを取得して」 娘「それをReactで表示したいわけだね」 ワイ「そういうことや」 現在の進捗状況 娘「それで、フロントの実装はどこまでできたの?」 ワイ「ほぼ完成してるで」 ワイ「↓こんな感じや」 よめ太郎「(酒ばっかりやないかい・・・)」 ワイ「だいぶ下のほうに『職を探す』っていうタスクも」 ワイ「ちゃんと入ってるで!」 よめ太郎「(ちゃんととは・・・)」 娘「へぇ〜」 娘「ちょっと触ってみてもいい?」 ワイ「もちろんええで」 娘「(ポチポチポチ・・・)」 娘「う〜ん・・・」 ワイ「え、どしたん」 娘「パパのReact、めっちゃ遅いね!」 ワイ「え、マジ・・・?」 娘「なんか動きが重いよ?」 ワイ「え、そんなことないやろ・・・」 ワイ「(ポチポチポチ・・・)」 ワイ「あれ、ほんまや」 ワイ「検索フォームにタスク名を入力するときに、なんかモタついてんな」 ワイ「キーを叩いてから文字が表示されるまでの間に、タイムラグが1秒近くあるで」 ワイ「何やこれ・・・」 娘「パパ、もしかしたらコンポーネントたちが無駄に再レンダーされてるのかもよ」 娘「そんな時は、React Developer Toolsを使って再レンダー状況を可視化してみようよ」 ワイ「そ、そんな機能あんの?」 娘「あるよ」 ChromeにReact Developer Toolsという拡張機能を追加する Chrome Devtoolsを開く Componentsタブを開く 歯車アイコンをクリック Highlight updates when components render.にチェックを入れる 娘「手順としては↑こんな感じだね」 ワイ「なるほど・・・」 ワイ「↑ここにチェックを入れるんやな!」 娘「そうだね!」 さっそくレンダー状況を見てみる ワイ「何やこれ」 ワイ「タスク名の入力フォームに文字を1つ入力するたびに」 ワイ「100件くらいあるTODOリスト全体が再レンダーされてるやん」 ワイ「まだ検索ボタンを押してもないのに・・・!」 娘「そうだね・・・」 ワイ「検索ボタンを押して、そんで検索結果が変わったことでリストが再レンダーされるならまだしも」 ワイ「文字を1つ入力したり消したりするたびにリスト全体が再レンダーされるんかい」 ワイ「何やそれ・・・」 娘「たぶん、タスク名のinputタグにイベントハンドラが設定されてて」 娘「文字を入力するたびに何かページの状態が変わって」 娘「それでページが再レンダーされてるんじゃない?」 娘「ページ内でuseStateとか使ってるよね?」 ワイ「おお、それはその通りや」 src/pages/TodoListPage.tsx const [taskName, setTaskName] = useState('') ワイ「↑この、useStateで作ったsetTaskNameっていう関数があってな」 ワイ「その関数が、タスク名の入力フォームに文字を入力するたびに実行される仕組みやねん」 娘「なるほどね」 娘「その時に、TODOリストも毎回ぜんぶ再レンダーされちゃってるんだね」 React.memoで、コンポーネントをメモ化しよう 娘「パパ、そんな時はReact.memoを使って」 娘「TODOリストのコンポーネントをメモ化してみようよ」 ワイ「ど、どうやんの・・・?」 娘「ええと・・・」 src/components/TodoList.tsx - const TodoList: React.FC<Props> = ({ list }) => { + const TodoList: React.FC<Props> = React.memo(({ list }) => { return ( <ul> {list.map((task) => ( <TodoItem task={task} key={task.id} /> ))} </ul> ) - } + }) + TodoList.displayName = 'TodoList' 娘「↑こうだね」 ワイ「おお、React.memoっていう関数に」 ワイ「TodoListコンポーネントの中身をぶち込んであげるだけなんやな」 娘「うん!」 よめ太郎「(前々から思ってたけど)」 よめ太郎「(引数をぶち込むって何やねん・・・)」 モタつきは解消されたのか ワイ「おお、TODOリストがピカピカせんようになっとるわ」 ワイ「文字の入力が重たい感じもなくなっとる」 娘「よかったね!」 ワイ「・・・これはどういうことなん?」 娘ちゃんによる解説 娘「えっとね、今までは」 React「お、検索フォームに文字が入力されたで!」 React「ほな、ページの状態が変わったことやし、再レンダーや!」 React「TodoListコンポーネントも再レンダーや!」 娘「↑こんな感じだったんだけど」 娘「コンポーネントをメモ化すると・・・」 React「お、検索フォームに文字が入力されたで!」 React「でも、TODOリストの中身が変わったわけやないから」 React「TodoListコンポーネントはレンダーしなおす必要ないか・・・」 React「さっきメモしといた結果を再利用しとこか」 娘「↑こんな感じだね」 よめ太郎「(なんでReactも関西弁やねん)」 ワイ「なるほどなぁ」 ワイ「useStateで管理している状態が、文字入力によって変わったとしても」 ワイ「TODOリストに関係のない変更だけが起こっていた場合には」 ワイ「再レンダーをスキップしてくれるんやね」 娘「そういうことだね!」 ワイ「ありがとうやで、娘ちゃん!」 しかし30分後・・・ ワイ「こ、今度はタスクの削除機能を実装したら」 ワイ「また画面の動きが遅くなってもうた・・・」 娘「パパ、今度はどうしたの?」 ワイ「おお、娘ちゃん・・・」 ワイ「あのな・・・」 src/pages/TodoListPage.tsx + const onDelete = (taskId: number): void => { + /* 省略 */ + } return ( <> <h1>TODOリスト</h1> <form> {/* 省略 */} </form> - <TodoList list={todoList} /> + <TodoList list={todoList} onDelete={onDelete} /> </> ) ワイ「↑こんな感じで、タスク削除用の関数を作って」 ワイ「それをTodoListコンポーネントに渡すようにしただけなんや・・・」 ワイ「それなのに・・・」 ワイ「また文字入力のたびにTODOリスト全体がピカピカしてんねん・・・」 娘「そんな時は、またメモ化だね!」 useCallbackで、関数もメモ化しよう src/pages/TodoListPage.tsx - const onDelete = (taskId: number): void => { + const onDelete = useCallback((taskId: number): void => { setTodoList((prevList) => { return prevList.filter((todoItem) => todoItem.id !== taskId) }) - } + }, []) ワイ「おお、今度はonDelete関数の中身を」 ワイ「useCallbackというやつにぶち込んだるわけか」 娘「そうそう」 娘「これでもう一度、画面を触ってみて?」 ワイ「おお、チカチカしないし、サクサク入力できとるわ」 ワイ「・・・でも、これ何でなん?」 ワイ「TodoListコンポーネントに渡すonDelete関数は、毎回おんなじ関数なんやから」 ワイ「TodoListは再レンダーされないはずちゃうの・・・?」 娘「それはね・・・」 src/pages/TodoListPage.tsx const onDelete = (taskId: number): void => { /* 省略 */ } return ( <> <h1>TODOリスト</h1> <form> {/* 省略 */} </form> <TodoList list={todoList} onDelete={onDelete} /> </> ) 娘「↑ここで、onDelete関数を作って」 娘「TodoListコンポーネントに渡してるでしょ?」 娘「そこが問題・・・というかポイントなの」 娘「つまり・・・」 React「よっしゃ、onDelete関数を作って」 React「TodoListコンポーネントにぶち込むでぇ!」 娘「↑こんな感じで、作りたての関数をTodoリストコンポーネントに渡す」 娘「そういうことになっちゃうの」 ワイ「作りたて・・・」 ワイ「そう言われてみれば、アロー関数式で関数を作って」 ワイ「それをonDeleteっていう変数に代入してるわけやもんな」 娘「そう」 娘「だから・・・」 React「さっきまでとは違う、今作ったばかりのonDelete関数を渡すから」 React「TodoListコンポーネントも再レンダーや!再レンダーや!」 娘「↑こんな感じになっちやうの」 ワイ「毎回新しい関数が作られて、別物扱いになってまうわけか」 ワイ「つまり、ReactはObject.isによる比較アルゴリズムを使用しているわけやな・・・」 よめ太郎「(いや、そこは急に理解早いんかい)」 娘「そういうことだね」 娘「そして、useCallbackを使うと」 ワイ「この関数は毎回作り直さないで、前回と同じのを使ってな!」 娘「ってことをReactに伝えられるわけだね」 ワイ「ふーん、そうするとTodoListコンポーネントが再レンダーされなくなるん?」 娘「そうだよ」 React「TODOリストの内容もonDelete関数も、前回と変わってへんな」 React「ほな今回はTodoListコンポーネントの再レンダーはスキップや!」 娘「↑って感じになるの」 ワイ「なるほどなぁ」 ワイ「ちなみにuseCallbackの第二引数の配列、これは何なん?」 娘「これは・・・」 ワイ「hogeっていう変数の中身が変わった場合には」 ワイ「前回と同じ関数を使いまわさずに、新しく作ってな!」 娘「↑的なことをReactに伝えるときのための配列だね」 ワイ「ほぇ〜」 ワイ「そうやって新しく関数が作り直された場合には」 ワイ「その関数を受け取ったコンポーネントは再レンダーされるわけか」 娘「そうそう」 ちなみに 娘「ちなみに、useCallbackを使わないでも」 娘「コンポーネントの外で関数を定義するだけで」 娘「再レンダーを抑止できる場合もあるよ!」 ワイ「そうかそうか」 ワイ「コンポーネントの外側で定義された関数は」 ワイ「コンポーネントが再レンダーされても変わらへんわけやな」 ワイ「まぁ、とにかく激重サイトにならんくてよかったわ!」 まとめ Reactアプリの動作が重い時は、React Developer Toolsで再レンダー状況をチェックしてみよう React.memoを使ってコンポーネントをメモ化し、無駄な再レンダーを抑止しよう メモ化したコンポーネントに渡すための関数もuseCallbackでメモ化しておこう ワイ「↑ってことやな!」 娘「そうだね!」 ワイ「いやー、娘ちゃんのおかげで良いTODOリストができそうやわ・・・!」 娘「あっ!」 娘「・・・パパ・・・!」 ワイ「なんや?」 娘「大変なことに気づいたんだけど・・・」 娘「このTODOリスト、どこからタスクを追加するの?」 ワイ「あ・・・」 ワイ「タスクの作成機能を作り忘れてたわ・・・」 娘「・・・」 ワイ「で、でも大丈夫や!」 タスク追加機能を実装する ワイ「↑っていうタスクを、このTODOリストに追加しておけばええねん!」 娘「で、そのタスクはどこから追加するの?」 ワイ「それな」 〜おしまい〜 参考文献 React の最上位 API『React.memo』 差分検出処理 – React Object.is() - JavaScript | MDN - Mozilla 補足
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでタイピングゲーム

作った 動作イメージ Link 所感 アニメーションのライブラリには、react-transition-group を使用したが、create-react-app した後の状態ではReact純正の最新バージョンが使えず、古めのバージョンを使う必要があった。 古めのバージョンなので、<React.StrictMode> も消さないと警告が出る。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubとGitLabのIssueをガントチャートで表示できるSPA作った

手っ取り早くDemoサイトを載せておきます。 セールスポイント GitHub/GitLabのIssueを無料で、環境なしですぐにガントチャート化できる! GitHub/GitLabに対してブラウザからREST APIをたたくだけなので、ブラウザからGitHub/GitLabにアクセスさえできれば利用できる! セルフホストしているGitLabサーバでも利用可能! asigneeとラベルでフィルタでき、依存関係が表せる! フィルタ条件はURLパラメータに保持しているので、Aチームのタスクなどをラベルを用意してブックマークすればチームごとの進捗管理などでも利用可能! issueは開始日、期限日の情報を設定可能、ソートも可能 背景 社内でGitlabを使って開発を回しているのですが、SIerに勤めているとやはり進捗管理の要望が強く上がってきます。普通はGitHubやGitLabでGit管理する場合、JiraやRedmine,Backlogなどのチケット管理ツールを利用して進捗管理や課題管理を行うのですが、そういった環境をすぐには用意できない場合もあります。そんな中でGitHubやGitLabのAPIを利用してガントチャートを作れないかと思い至ったのが開発のモチベーションです。 作りたいもの 以下を想定してGitHubとGitLabのIssueをガントチャートで表示できるものができないかと考えました。 5W1H このOSSの場合 Who(だれが) SIer When(いつ) ウォーターフォール開発など Where(どこで) プロキシ認証など制約のある社内環境 What(なにを) GitHub/GitLabのIssue Why(なぜ) チケット管理ツールなしでの進捗管理をするため How(どのように) ガントチャートで、バックエンドなしで 類似OSS 以下先人たちです。ラベルでフィルタリングできなかったり、自前でHostingしないといけなかったりで私たちは利用を断念しました。※セルフホストのGitLabで利用したいというのが一番大きかったです。 GanttLab: https://github.com/ganttlab/ganttlab gh-issues-gantt: https://github.com/ericabouaf/gh-issues-gantt このテーマで深く考察している海外記事があったので参考に載せておきます。 作ったもの Demoはこちら Githubはこちら 使い方 1. 準備するもの Gitレポジトリ(URL) とパーソナルアクセストークンを用意してください。 ①GitHub/GitLabレポジトリのURL (github.com / gitlab.com / your.self-host.gitlab.com) ex) https://github.com/lamact/react-issue-ganttchart ②パーソナルアクセストークン GitHub: https://github.com/settings/tokens/new Scopes: public_repo, write:discussion, read:org 手順: https://docs.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token GitLab: https://gitlab.com/-/profile/personal_access_tokens Scopes: api 手順: https://gitlab-docs.creationline.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token 2. 設定・使い方 セルフホストしない場合、Github Pagesにホスティングしてあるこちらを使ってください。 設定項目 説明 Git Repository URL * ①GitHub/GitLabレポジトリのURL (上記で準備) Access Token ②パーソナルアクセストークン (上記で準備) filter by labels Issueのラベル (複数選択可/プルダウンで候補リスト表示) Asignee IssueのAsignee (プルダウンで候補リスト表示) ライセンス・セキュリティなど ライセンスはMITではなく、 GPL-2.0なのでご注意ください。※商用利用は可能です。 このアプリはSPA(Single Page Application)です。ブラウザ上のみで動くため、外部のサーバに対してIssueの内容やトークンが送信されることはありません。フィルタクエリはURLパラメータに、トークンはCookieに保持されます。Demoサイトのgit_urlを自プロジェクトのURLに変更すればそのままセキュアに利用可能です。それでもセキュリティが気になる人は自前でHostingも可能なのでそちらでお願いいたします。 テクニカルな小話 React Hook初めて使ってみましたが、動作が軽快で気に入りました! フィルタクエリをURLに保持する実装はかなり大変でしたが、この機能でブックマークしておけば毎回フィルタを入れる必要がなくなったのでお気に入りです。 ガントチャート表示にはDHXを利用しました。機能やドキュメントが多く拡張もしやすかったです。ただ、Pro版でないと制約も多く、ガントチャートの左横のカラムの幅を変えられなかったり、オブジェクトの追加ができなかったりと細かいところで引っ掛かりました。 開発にはGit Podを利用してみました。vscodeとまったく同じ操作感をブラウザ上できるので感動しました。※月50hまでの制限があります。 開始日、期限日、progressなどはissueの説明欄にyamlで書き込むようにしてます。 テストコード書いてますが、カバレッジ追いついてません、申し訳ないです。。。 趣味の延長で時間もなかったため、もろもろコードに至らない点が多くてすみません。Pull Requestお待ちしております。 最後に Star,Pull Requestお待ちしております!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンド開発を始めるのに必要なワードを書き出してみる

フロントエンドとは フロントエンド・バックエンドという概念があるが、広義の意味でユーザが目に見える部分をフロントエンド、データベースとの通信やシステムの基幹処理を行うのがバックエンドという風に分類できる。 フロントエンドにもWebアプリ、ネイティブアプリがあり、別のフレームワークや言語を使うことが多い。 ※共通化できるフレームワークも出てきている Webアプリ ブラウザ上で動くアプリケーション。 PCやスマホの各ブラウザからのリクエストをWebサーバがHTTPで返す。 ※HTTPでの通信をベースに、websocketなど別のプロトコルも使用可能 OSやブラウザが異なっても、1ソースである程度動く 【OS】 ・Linux ・Windows ・MacOS 【ブラウザ】 ・Chrome ・Safari ・Edge ・FireFox ・InternetExplorer 挙動やデザインが完全に同じではないこともある ネイティブアプリ スマホやタブレット上で動くパッケージアプリで、AppStoreやPlayStoreに公開されたアプリをダウンロードして使用する。 スマホを持っているのが通常の今日、最も使用される頻度が高いのはネイティブアプリである。 上述のようにiOSとAndroidでストアが分かれていることからも分かる通り、iOS仕様・Android仕様に合わせた異なるパッケージ(IPA/APK)となる。 OSで提供されているものを使用するため、OSの種類や、OSのバージョンにより挙動が異なることがある。 フレームワーク フレームワークのトレンドでは React.JS Vue.js Angular.js が上がってくるが、中でもReact.JS/Vue.jsがツートップなのが現状。 これらが、SPAで作られているのに対し、従来ながらのPHP、Ruby on Rails、ASP.netのMVCモデルのWebアプリケーションもある。 その他プロジェクトの選択肢として覚えておくべき技術 PWA (Progressive Web Apps) ハイブリッドアプリ SSG (静的サイトジェネレーター) Jest Storybook まとめ プロジェクトの規模や予算により様々な選択肢が可能なように現在広く使われている技術の吸収が大事である。 JestやTestCafeは自動テストを取り入れることで、テストコストの削減につながる。 モックやワイヤーフレームを用いてUIの要件定義をすることで実装コストを下げるのも有効で、 sketch・figma・XD・Cacoo・InVisionなどのツールを使いこなすことも必要。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

create-react-appでのパッケージマネージャー

今回create-react-appで環境構築をした後、npm installでライブラリなどをインストールした際に起きたビルドエラーの対処法を備忘録として残します。 環境構築 npx create-react-app my-app --template redux-typescript 上記コマンドで環境を作りmaterial-uiなどをnpm installで入れた後にnpm startをした時に 下記のビルドエラーが発生。 Could not find a declaration file for module ‘react’. implicitly has an ‘any’ type. If the ‘react’ package actually exposes this module, consider sending a pull request to amend 原因 調べてみると、どうやらcreate-react-appではデフォルトでパッケージマネージャーにyarnを使用されていて、既存のnode_modulesがyarnで作成されているのに対して、npmで追加の依存関係をインストールしたことが原因だとのこと。 解決方法 node_modulesとyarn.lockファイル削除し、npm installする。 すると新たにnode_modulesフォルダが生成され、最初にインストールしていたmaterial-uiなども全てインストールされます。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Slickの基礎知識

はじめに 業務の中でreact-slickを使用する機会があったので 使用方法やオプションなどメモ代わりに書いていきたいと思います。 ユースケースは別で書こうと思います。 アジェンダ React Slickとは 導入 使用方法 個人的よく使うオプション React Slickとは react-slick/git-hub React Slick Documentation スライダーやカルーセルを簡単に実装でき、 レスポンシブ対応や豊富なオプションを使用できるのが特徴です 元々jQeryのプラグインであるSlickを Reactでも使用できるようモジュール化したものになります。 導入 npmを使用する場合の導入までを公式から拝借。 パッケージのインストール npm install react-slick --save CSSをインストール npm install slick-carousel // Import css files import "slick-carousel/slick/slick.css"; import "slick-carousel/slick/slick-theme.css"; これで準備は整いました。 使用方法 ここでは画像3枚を配列に入れて使用しています。 settingsにオプションを指定し、Sliderに渡すだけで 簡単にカスタムすることができます。 import React from "react"; import Slider from "react-slick"; export default function SimpleSlider() { const settings = { dots: true, infinite: true, speed: 500, }; const images = [image, image2, image3] return ( <Slider {...settings}> {images.map(img)=> <div> <img src={img} alt="pictuer" /> </div } </Slider> ); } 個人的よく使うオプション 個人的によく使うオプションを中心に紹介します。 jQueryのSlickとは異なる部分もあるので注意が必要です。 なお主観で機能と見た目でオプションを分類しています。 機能に関するオプション autoplay スライドを自動再生します。 Type: bool Default: false infinite コンテンツをループさせます(① -> ② -> ③ -> ① ...) Type: bool Default: true initialSlide 最初に表示するスライドを指定します。 Type: int Default: 0 lazyLoad 画像の読み込むタイミングを指定します。 progressiveは最初にまとめて、 ondemandは遅延読み込みをします。 Type: ondemand / progressive Default: null slidesToScroll 一度にスライドを何枚スクロールするか指定します。 Type: int Default: 1 slidesToShow フレーム内にスライドを何枚表示するかを指定します。 Type: int Default: 1 speed スクロール、フェードアニメーションの速度msで指定します。 Type: int Default: 500 pauseOnHover autoplayがtureの場合に 自動再生をマウスホバーで一時停止します。 Type: bool Default: false 見た目に関するオプション arrows 「前」「次」のスライドを操作する矢印を表示します。 Type: bool Default: true centerMode 現在表示しているスライドを中央に配置し、 次のスライドを少し見切れて表示させます。 Type: bool Default: false centerPadding centerModeを指定した場合に 見切れて表示をさせる割合を指定します。 Type: string Default: '50px' customPaging dotsがtrueの場合ドットナビをカスタムできます。 Type: func Default: i => {i + 1} dots ドットナビを表示します。 Type: bool Default: false fade スライドの切り替え方をフェードにします。 Type: bool Default: false nextArrow arrowsがtureの場合に 「次」の矢印の見た目をHTMLでカスタムできます。 Type: html Default: NEXT prevArrow arrowsがtureの場合に 「前」の矢印の見た目をHTMLでカスタムできます。 Type: html Default: Previous おわりに カルーセルも一から実装しようと思うと手間ですが Slickだと、とても簡単に実装できます。 最後まで、見ていただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

30分で習うより慣れる! React + Redux Toolkit + TypeScript の Todoアプリチュートリアル その1

はじめに 最近 React を勉強し始めた新参者です。 「とりあえず TypeScript で始めよう → Redux って便利そう → Redux Toolkit ってのがあるのか~ ・・・・・・・・・ 」 という具合にインターネットを潜ってチュートリアル始めたら、情報が多かったり古かったりEnglishだったりでカオスったので、習うより慣れるチュートリアルをここに残します。 習うより慣れたい皆様の参考になれば幸いです。 環境 % sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 //Catalinaさん、そろそろ上げないと、、、 BuildVersion: 19H2 % brew -v Homebrew 3.0.11 % nodebrew -v nodebrew 1.0.1 % node -v v14.16.1 % yarn -v 1.22.10 Start!! プロジェクト作成 Redux+TypeScript テンプレートを使用 こちら yarn create react-app redux-sample --template redux-typescript 作成したプロジェクトへ移動 cd redux-sample テンプレートに含まれてるRedux関連のモジュール達です % ls -d -e node_modules/*redux* node_modules/@reduxjs node_modules/react-redux node_modules/redux node_modules/redux-thunk Redux を TypeScript に対応させるやつをインストールします (雑 yarn add typescript-fsa typescript-fsa-reducers 以下の構成が出来上がります ~/redux-sample % tree -I ".DS_Store|node_modules|.git" -La 4 --dirsfirst . ├── public │   ├── favicon.ico │   ├── index.html │   ├── logo192.png │   ├── logo512.png │   ├── manifest.json │   └── robots.txt ├── src │   ├── app │   │   ├── hooks.ts │   │   └── store.ts │   ├── features │   │   └── counter │   │   ├── Counter.module.css │   │   ├── Counter.tsx │   │   ├── counterAPI.ts │   │   ├── counterSlice.spec.ts │   │   └── counterSlice.ts │   ├── App.css │   ├── App.test.tsx │   ├── App.tsx │   ├── index.css │   ├── index.tsx │   ├── logo.svg │   ├── react-app-env.d.ts │   ├── serviceWorker.ts │   └── setupTests.ts ├── .gitignore ├── README.md ├── package.json ├── tsconfig.json └── yarn.lock これを以下に変更します (こちらはお好みでどうぞ) . ├── public │   └── # 省略 ├── src │   ├── app │   │   ├── hooks.ts │   │   └── store.ts │   ├── features │   │   └── counter │   │      ├── counterAPI.ts │   │      ├── counterSlice.spec.ts │   │      └── counterSlice.ts │   ├── pages # <-- 作成 │   │   ├── assets # <-- 作成 │   │   │   └── logo.svg # <-- 移動 │   │   ├── counter # <-- 作成 │   │   │   ├── Counter.module.css # <-- 移動 │   │   │   └── Counter.tsx # <-- 移動 │   │   ├── App.css # <-- 移動 │   │   ├── App.test.tsx # <-- 移動 │   │   └── App.tsx # <-- 移動 │   ├── index.css │   ├── index.tsx │   ├── react-app-env.d.ts │   ├── serviceWorker.ts │   └── setupTests.ts └── # 省略 既存のソースをちょっと修正する src/index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './pages/App'; // <-- 修正 // 以下省略 src/pages/App.tsx import React from 'react'; import logo from './assets/logo.svg'; // <-- 修正 import { Counter } from './counter/Counter'; // <-- 修正 import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> {/* --- ここから削除 --- <Counter /> <p> Edit <code>src/App.tsx</code> and save to reload. </p> --- ここまで --- */} <span> // 以下省略 src/pages/counter/Counter.tsx import React, { useState } from 'react'; // 省略 } from '../../features/counter/counterSlice'; // <-- 修正 import styles from './Counter.module.css'; export function Counter() { //省略 const incrementValue = Number(incrementAmount) || 0; return ( <div> <div className={styles.row}> // 省略 </div> {/* --- ここから追記 --- */} <p> Edit <code>src/pages/counter/Counter.tsx</code> and save to reload. </p> {/* --- ここまで --- */} </div> ); } TodoアプリのUI作成 Todoアプリ用のページディレクトリを作成 mkdir src/pages/todo Todoアプリのベースを作成 src/pages/todo/TodoApp.tsx import React from 'react'; import AddTodo from './components/AddTodo' function TodoApp() { return ( <div> <AddTodo /> <p> Edit <code>src/pages/todo/TodoApp.tsx</code> and save to reload. </p> </div> ); }; export default TodoApp; Todo 入力用の Input と Button を作成 src/pages/todo/components/AddTodo.tsx import React from "react"; export default function AddTodo(): JSX.Element { const [text, setText] = React.useState(""); function handleChange(e: { target: HTMLInputElement }) { setText(e.target.value); } function handleSubmit(e: any) { e.preventDefault(); if (!text.trim()) { return; } setText(""); } return ( <form onSubmit={handleSubmit}> <input value={text} onChange={handleChange} /> <button type="submit">Add Todo</button> </form> ); } ルーティングの実装 ルーティングモジュールをインストール yarn add react-router-dom @types/react-router-dom App.tsx から各ページを URL 毎に読み込むように修正 src/pages/App.tsx import React from 'react'; import logo from './assets/logo.svg'; import { BrowserRouter as Router, Route } from 'react-router-dom'; // <-- 追記 import { Counter } from './counter/Counter'; import Todo from './todo/TodoApp'; // <-- 追記 import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> // --- ここから追記 --- <Router> <Route exact path="/" component={Counter} /> <Route exact path="/todo" component={Todo} /> </Router> // --- ここまで --- // 省略 </header> </div> ); } export default App; ロゴの主張が強いので以下の css を修正 src/App.css .App-logo { height: 20vmin; /* 40 --> 20 */ /* 省略 */ } .App-header { min-height: 10vh; /* 100 --> 10 */ /* 省略 */ } 起動して見た目を確認 yarn run start localhost:3000/ にアクセスしたときの見た目 (サンプルソース) localhost:3000/todo にアクセスしたときの見た目 Todoを追加できるようにする Todoリストの componet を作成 Todoリスト src/pages/todo/components/TodoList.tsx import * as React from "react"; import TodoItem from './TodoItem' export default function TodoList() { const text: any = "" return ( <ul> <TodoItem {...text} /> </ul> ); } Todo単体 src/pages/todo/components/TodoItem.tsx import * as React from "react"; interface TodoProps { text: string; } export default function TodoItem( { text }: TodoProps ) { return ( <li> {text} </li> ); } 作成した Todo Components を読み込む src/pages/todo/TodoApp.tsx import React from 'react'; import AddTodo from './components/AddTodo' import TodoList from './components/TodoList' // <-- 追記 export default function TodoApp() { return ( <div> <AddTodo /> <TodoList /> {/* <-- 追記 */} <p> Edit <code>src/pages/todo/TodoApp.tsx</code> and save to reload. </p> </div> ); }; 起動して見た目を確認 Todoリストの表示部分ができました 入力した値をTodoリストに出力する Slice を作成 src/features/todo/todoSlice.ts import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { AppThunk, AppDispatch, RootState } from "../../app/store"; import { Todo } from './types' const initialState: Todo[] = []; const todoSlice = createSlice({ name: 'todos', initialState, reducers: { addTodo(state, action: PayloadAction<Todo>) { state.push(action.payload); }, } }); export const addTodo = (text: string): AppThunk => async ( dispatch: AppDispatch ) => { const newTodo: Todo = { id: Math.random().toString(36).substr(2, 9), text: text }; dispatch(todoSlice.actions.addTodo(newTodo)); }; export default todoSlice.reducer; Todo Interface type を作成 src/features/todo/types.ts export interface Todo { id: string; text: string; } Reducer を作成 src/app/rootReducer.ts import { combineReducers } from "@reduxjs/toolkit"; import counter from "../features/counter/counterSlice"; import todos from "../features/todo/todoSlice"; const rootReducer = combineReducers({ counter, todos }); export type RootState = ReturnType<typeof rootReducer>; export default rootReducer; Store を修正 src/app/store.ts import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit' import rootReducer from "./rootReducer"; export const store = configureStore({ reducer: rootReducer }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType<typeof store.getState>; export type AppThunk<ReturnType = void> = ThunkAction< ReturnType, RootState, unknown, Action<string> >; 各 Todo Components を修正して結合する src/pages/todo/components/AddTodo.tsx import React from "react"; import { useDispatch } from "react-redux"; // <-- 追記 import { addTodo } from "../../../features/todo/todoSlice"; // <-- 追記 export default function AddTodo(): JSX.Element { const dispatch = useDispatch(); // <-- 追記 const [text, setText] = React.useState(""); function handleChange(e: { target: HTMLInputElement }) { setText(e.target.value); } function handleSubmit(e: any) { e.preventDefault(); if (!text.trim()) { return; } dispatch(addTodo(text)); // <-- 追記 setText(""); } return ( <form onSubmit={handleSubmit}> <input value={text} onChange={handleChange} /> <button type="submit">Add Todo</button> </form> ); } src/pages/todo/components/TodoList.tsx import * as React from "react"; import { useSelector } from "react-redux"; // <-- 追記 import { RootState } from "../../../app/rootReducer" // <-- 追記 import { Todo } from "../../../features/todo/types"; // <-- 追記 import TodoItem from './TodoItem' //export default function TodoList() { <-- 削除 // const text: any = ""; <-- 削除 export default function TodoList() { // <-- 追記 const todos: Todo[] = useSelector((state: RootState) => // <-- 追記 state.todos // <-- 追記 ); // <-- 追記 return ( <ul> {/* --- 追記 --- */} {(todos || []).map(todo => ( <TodoItem {...todo} /> ))} {/* --- ここまで --- */} {/* <TodoItem {...text} /> <-- 削除 */} </ul> ); } 起動して動作確認 最後に いかがでしたでしょうか、慣れましたか? こちらのサイトがとても分かりやすく解説してくれています。感謝(-人-) おそらく、この後にこちらをみると理解が深まると思います。 皆様の助けになると幸いです。 チュートリアル通りだと、その2以降は、タスクを完了させたり、タスクの完了・未完了のフィルタリングですが、そのうち投稿したいとおもうのですが、もう皆さんなら出来ると思うのです。 断じて力尽きたわけではありません。私もこれから勉強していきます(><) 今回のソースはこちらに置いておきます でわ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/CORE UIテンプレートのメモ

$ git clone https://github.com/coreui/coreui-free-react-admin-template.git $ cd coreui-free-react-admin-template $ npm install $ npm start
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactJS-AdminLTEのメモ

$ git clone https://github.com/booleanhunter/ReactJS-AdminLTE.git $ cd ReactJS-AdminLTE $ npm install $ npm start ブラウザでhttp://localhost:8000にアクセス。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-admin-templateのメモ

$ git clone https://github.com/delprzemo/react-admin-template.git $ cd react-admin-template $ npm install $ npm start
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む