20210611のReactに関する記事は11件です。

React-Reduxに関する理解

はじめに React+React-Reduxを用いてWebシステムを開発しているの際に引っかかった所やよく分からなかった箇所の復習用にこのような記事を投稿しようと考えました。 Reduxってなに? 簡単に説明すると、ReduxとはUIの状態を簡単に管理できるフレームワークです。 このReduxをReactと組み合わせて使う場合、UIの状態を専用の場所(ストア)で管理し、Reactはそれを表示するだけの役割を担っています。 フロー全体図 画面から入力されたInputをもとにAction Creatorからactionがdispatchされる Reducerがdispatchに対してStore内のstateを更新する 更新されたstateを参照する Store コンポーネントの状態(state)を集約している場所 ActionとActionCreator Storeに対して何か処理を行う場合は、Actionを発行します。そのActionを発行するための関数としてActionCreatorが存在します。 Reducer Actionが発行された時、このReducerが反応します。Reducerが発行されたActionに反応するとStore内のStateを変更します。 connect Reactのコンポーネント単体では、Reduxを利用することはできません。Reduxを使えるようにするためにはReact-Reduxのconnectという関数を用いて下記のように書きます。 1 export default connect( 2 (state) => (state.myStore), 3 (dispatch) => bindActionCreators({ ...myStore.actionCreators }, dispatch) 4 )(MyComponet); 2行目 storeのstateをpropsとしてコンポーネントに渡す。 3行目 発行したactionを指定する。 4行目 どのコンポーネントにstoreを接続するのか指定する。 mapStateToPropsとmapDispatchToProps mapStateToPropsは、コンポーネントに渡したいstateを入れることができます。 mapDispatchToPropsは、actionをstoreにdispatchしてくれる関数です。 最後に ここまで自分の理解できている範疇で書いてみましたが、まだまだ不明瞭な点が多いのが正直なところです。 さらに勉強して知識を蓄えていきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactにおけるアロー関数

要するに、関数オブジェクトのことです(´ω`) (誰に向けて書こうかな・・・う~ん、JSの初心者向けで) Reactは1ファイルに限り、HTMLのタグを、そのままScript内部に書く JSXという拡張機能を使うことが出来ます。 それはBabelというモジュールをインポートすることで、 使えるようになります。 <script src="https://unpkg.com/react@17/umd/react.development.js"> </script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"> </script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"> </script> <div id="JSX_root"></div> //JSXを使ったJavaScriptであることを宣言 <script type="text/babel"> //DOMセレクタ let jsxdom = document.querySelector("#JSX_root") //JSXの構文 let el = ( <div> <h2>タイトル/h2> <p>メッセージ/p> </div> ) // 仮想DOMをレンダリング ReactDOM.render(el, jsxdom) そして、JSX内部では、アロー関数と言うものを使うことが出来ます。 以下の例文を見てください。 let data2 = [ {name:'Taro', mail:'taro@yamada', age:45}, {name:'Hanako', mail:'hanako@flower', age:37}, {name:'Sachiko', mail:'sachiko@happy', age:29}, {name:'Jiro', mail:'jiro@change', age:18}, {name:'Kumi', mail:'kumi@class', age:56}, ] {data2.map((value) => ( <tr> <td>{value.name}</td> <td>{value.mail}</td> <td>{value.age}</td> </tr> ))} data2は連想配列、Dictionary型のオブジェクトの配列です。 そして、map関数の中で何やら書いていますね。 これはmap関数の拡張機能で、 map関数は、引数に関数オブジェクトを指定することが出来ます。 そして、map関数は「その配列の数だけ、引数に指定された関数の、戻り値を返却する という処理を書くことが出来ます map((引数。ループ処理なのでイテレーターと呼びます) => ( <tr> <td>{イテレーター.フィールド1}</td> <td>{イテレーター.フィールド2}</td> <td>{イテレーター.フィールド3}</td> </tr> )) そして、このような関数自体を引数に持つ、特殊な関数は 結構いっぱいあって、 このような書き方を、アロー関数と呼びます。(=> ← これね。矢印っぽいでしょ) 一般的な書き方はこんな感じ アロー関数に対応した特殊関数((引数) => (    中の処理 )) さらに、JSXの構文として、 アロー内部の関数をそのまま使うということも出来ます { (() => {中の処理})() } こうすると、 {}の中には、中の処理の戻り値が自動で設定されます。 (ぶっちゃけて言うと、何がいいんだろう・・・ ただ、JSXでは頻出の構文、らしい) コラム 今回はアロー関数と呼びましたが、一般的にはラムダ式と呼ばれているものです。 似たような機能は、他のプログラム言語にもたくさんありますよヾ(。>﹏<。)ノ゙✧*。 Java int[] numbers = {-1, 2, 0, -3, 8}; List<Integer> numbersList = new ArrayList<>(); for(int n : numbers) { numbersList.add(n); } // ソート方法として(引数1,引数2)->{return 引数1-引数2}を設定している // 処理の戻り値が0より大きいと、引数1を配列の上に、 // 処理の戻り値が0より小さいと、引数1を配列の下に、移動する // sort()メソッドの拡張機能である。 Collections.sort(numbersList, (a, b) -> { return a - b; }); 他にも例えばGUIアプリケーションで 「ボタンに紐づくイベント処理を作る」時とか 基本的に、提供されてるフレームワークの処理をカスタマイズする時に使います。 割かしフレームワークをいじり始めたら、割と必須知識かも
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでRuffleを使ってflashを再生する

使いどころ 遺産 Flash ファイル(*.swf)を React などの SPA で使いたいとき! 当然、Flash Player も頼れない。 React + Ruffle + *.swf または、フロントWebフレームワーク + Ruffle + *.swf 公開に至るまで Adobe が Flash Player のサポートを完全終了してから久しいですね。 あれは、2020年12月31日の出来事でした…。 遺産 Flash ファイル(*.swf)を再生する方法は、未だに需要があります。 動画内にクリックできる部分が必要だったりして、 単純にMP4とかに動画にすれば済むわけでもありません。 かといって、HTML5+CSS3アニメーションで1から作るのも面倒です。 そのため Ruffleとか、swf2js を検証してみました。 静的ページなら Ruffle の公式ドキュメントを見れば簡単です。 しかし動的ページや SPA だと、そうはいきません。 URL が切り替わると再生されない! Rust から自分でコンパイルすればいいらしい? wasm-loader が要るらしいけど、webpack なの? create-react-app なの? みたいな感じで大混乱です。 再生する方法を模索していて、解決策を見つけたので紹介します。 方法 SPA内コンテンツが描画されるとき、動的にカスタムイベントを発生させます。 そのカスタムイベント内で、Flash の再生を開始させます。 キーワードは、下記4つです。 componentDidMount dispatchEvent addEventListener CustomEvent ※後述する例は React + TypeScriptです。しかし React + JavaScript や他のフロント Web フレームワーク構成でも、少しの書き換えで済むと思います。 ※ruffle.js内で*.wasmファイルを読み込みます。wasmのサイズが大きいため、これをgzip化などして最適化したい人には不向きな方法です。そういう時はRustからコンパイルしたほうが良さそう。 ※この方法はRuffle の公式ドキュメントに書かれている、Website Self Hosted の延長線上にある手法です。Rust開発環境は不要です。 構成 ┣━public ┃ ┣━ flash ┃ ┃ ┗━ flash ┃ ┃ ┗━ legacy.swf ┃ ┗━ ruffle ┃ ┗━ ruffle-nightly-2021_05_17-web-selfhosted ┃ ┗━ ruffle.js ┗━src ┣━ components ┃ ┣━ main.tsx ┃ ┣━ fixed_mainnav.tsx ┃ ┣━ header.tsx ┃ ┗━ maincontent.tsx ┣━ index.html ┗━ index.tsx maincontent.tsx内の JSX が読み込まれたときに、毎回legacy.swfを再生します。 コード index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script> </head> <body> <div id="root"></div> </body> <script> var config = { // Options affecting the whole page "publicPath": "Failed to load the video!", "polyfills": true, // Options affecting files only "autoplay": "true", "unmuteOverlay": "hidden", "backgroundColor": null, "letterbox": "fullscreen", "warnOnUnsupportedContent": true, "contextMenu": false, "upgradeToHttps": window.location.protocol === "https:", "logLevel": "error", }; function loadflash(elementId,flashUrl) { const ruffle = window.RufflePlayer.newest(); const player = ruffle.createPlayer(); const container = document.getElementById(elementId); container.appendChild(player); player.config = config; player.load(flashUrl); } function loadflash001() { loadflash('flash001','%PUBLIC_URL%/flash/legacy.swf'); } window.addEventListener("playflash", loadflash001); window.addEventListener("load", loadflash001); </script> </html> headタグ内で、下記のように、ruffle.jsを読み込んでいます。 <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script> loadflashが Flash を再生させる関数です。 最終的にloadイベントとplayflashイベントで Flash を再生させようとしています。 なぜloadのほうに登録が必要かと言うと、URL 直打ちでplayflashイベントが発生できなかったからです。 ※この辺り細かく検証できていなくて、分かる方いたらコメントいただけると嬉しいです。 直接loadflashをaddEventListenerしてみましたが、 loadイベント時にplayflashイベントも同時発生することが分かりました。 そのため、loadflash001関数を使って、あえて2段編成にしています。 index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.sass'; import reportWebVitals from './reportWebVitals'; import MainApp from "./components/main"; import HeaderApp from "./components/header"; import FooterApp from "./components/footer"; import { createBrowserHistory } from "history" import { Router } from 'react-router'; const history = createBrowserHistory({ basename: '/new_web' }); ReactDOM.render( <React.StrictMode> <Router history = {history}> <HeaderApp></HeaderApp> <MainApp></MainApp> <FooterApp></FooterApp> </Router> </React.StrictMode>, document.getElementById('root') ); reportWebVitals(console.log); main.tsx import React from 'react'; import { Route, Switch } from 'react-router-dom'; import OutlineApp from './outline'; import MainContentApp from './maincontent'; class MainApp extends React.Component { render() { return ( <main id="wrap_main"> <div id="sidearea"></div> <div id="wrap_contents"> <Switch> <Route exact path="/" component={MainContentApp} /> <Route exact path="/outline/" component={OutlineApp} /> <Route render={() => <p>NotFound</p>} /> </Switch> </div> <div id="main-right-spacer"></div> </main > ); } } export default MainApp; 上記のmain.tsxの中で、 <Route exact path="/" component={MainContentApp} /> <Route exact path="/outline/" component={OutlineApp} /> という部分があります。ここで、URL が/に遷移した時には、MainContentAppが読み込まれます。 そこには、Flash を再生させたいタグid='flash001'があります。 しかし、URL が/outline/に遷移した時には、MainContentAppの代わりにOutlineAppが読み込まれます。 そのため、タグid='flash001'は一度無くなります。 その後、再びURL が/に遷移すると、もう一度MainContentAppが読み込まれます。 タグid='flash001'が、再度出現することになります。 この時、window.addEventListener("load", func)だけだと、Flash は再生されません。 loadイベントは、初めて DOM を読み込んだ時にしか発生しないからです。 maincontent.tsx import React from 'react'; let FlashEvent = new CustomEvent('playflash', { bubbles: true }); class MainContentApp extends React.Component { componentDidMount() { window.dispatchEvent(FlashEvent); } render() { return ( <React.StrictMode> <div id="flash001"></div> </React.StrictMode> ); } } export default MainContentApp; 上記maincontent.tsxに<div id="flash001"></div>があります。ここにFlashを読み込みます。 また、maincontent.tsxでは2行目で、下記のようにplayflashイベントを宣言しています。 let FlashEvent = new CustomEvent('playflash', { bubbles: true }); そして、このMainContentAppコンポーネントがマウントされると、 componentDidMount() { window.dispatchEvent(FlashEvent); } によって、FlashEventがディスパッチされます。 これにより、index.htmlで用意した、playflashのイベントリスナーがキックされます。 header.tsx import React from 'react'; import { NavLink } from 'react-router-dom'; import FixedMainNavApp from "./fixed_mainnav"; class HeaderApp extends React.Component { render() { return ( <React.StrictMode> <header id="wrap_header"> <div id="wrap_logo"> <a href="/"> <img id="mainlogo" alt="株式会社logo" /> </a> </div> <nav id="wrap_gmenu"> <ul> <FixedMainNavApp /> </ul> </nav> </header> </React.StrictMode> ); } } export default HeaderApp; 上記のheader.tsxでは、FixedMainNavAppでナビゲーションのリンクを読み込んでいます。 fixed_mainnav.tsx import React from 'react'; import { Link } from 'react-router-dom'; class FixedMainNavApp extends React.Component { render() { return ( <React.StrictMode> <li><Link to="/">ホーム</Link></li> <li><Link to="/outline/">会社概要</Link></li> <li><Link to="/digital/">デジタル事業</Link></li> <li><Link to="/recruit/">リクルート</Link></li> <li><Link to="/sitemap">サイトマップ</Link></li> </React.StrictMode> ); } } export default FixedMainNavApp; 上記のfixed_mainnav.tsx内でLinkタグをユーザーがクリックすると URL 遷移します。 まとめ 負の遺産は早く捨てたほうが幸せ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでRuffleを使ってFlashを再生する

使いどころ 遺産 Flash ファイル(*.swf)を React などの SPA で使いたいとき! 当然、Flash Player も頼れない。 React + Ruffle + *.swf または、フロントWebフレームワーク + Ruffle + *.swf 公開に至るまで Adobe が Flash Player のサポートを完全終了してから久しいですね。 あれは、2020年12月31日の出来事でした…。 遺産 Flash ファイル(*.swf)を再生する方法は、未だに需要があります。 動画内にクリックできる部分が必要だったりして、 単純にMP4とかに動画にすれば済むわけでもありません。 かといって、HTML5+CSS3アニメーションで1から作るのも面倒です。 そのため Ruffleとか、swf2js を検証してみました。 静的ページなら Ruffle の公式ドキュメントを見れば簡単です。 しかし動的ページや SPA だと、そうはいきません。 URL が切り替わると再生されない! Rust から自分でコンパイルすればいいらしい? wasm-loader が要るらしいけど、webpack なの? create-react-app なの? みたいな感じで大混乱です。 再生する方法を模索していて、解決策を見つけたので紹介します。 方法 SPA内コンテンツが描画されるとき、動的にカスタムイベントを発生させます。 そのカスタムイベント内で、Flash の再生を開始させます。 キーワードは、下記4つです。 componentDidMount dispatchEvent addEventListener CustomEvent ※後述する例は React + TypeScriptです。しかし React + JavaScript や他のフロント Web フレームワーク構成でも、少しの書き換えで済むと思います。 ※ruffle.js内で*.wasmファイルを読み込みます。wasmのサイズが大きいため、これを最適化したい人には不向きな方法です。そういう時はRustからコンパイルしたほうが良さそう。 ※この方法はRuffle の公式ドキュメントに書かれている、Website Self Hosted の延長線上にある手法です。Rust開発環境は不要です。 構成 ┣━public ┃ ┣━ flash ┃ ┃ ┗━ flash ┃ ┃ ┗━ legacy.swf ┃ ┗━ ruffle ┃ ┗━ ruffle-nightly-2021_05_17-web-selfhosted ┃ ┣━ ???.wasm ┃ ┣━ ... ┃ ┗━ ruffle.js ┗━src ┣━ components ┃ ┣━ fixed_mainnav.tsx ┃ ┣━ header.tsx ┃ ┣━ main.tsx ┃ ┗━ maincontent.tsx ┣━ index.html ┗━ index.tsx maincontent.tsx内の JSX が読み込まれたときに、毎回legacy.swfを再生します。 コード index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script> </head> <body> <div id="root"></div> </body> <script> var config = { // Options affecting the whole page "publicPath": "Failed to load the video!", "polyfills": true, // Options affecting files only "autoplay": "true", "unmuteOverlay": "hidden", "backgroundColor": null, "letterbox": "fullscreen", "warnOnUnsupportedContent": true, "contextMenu": false, "upgradeToHttps": window.location.protocol === "https:", "logLevel": "error", }; function loadflash(elementId,flashUrl) { const ruffle = window.RufflePlayer.newest(); const player = ruffle.createPlayer(); const container = document.getElementById(elementId); container.appendChild(player); player.config = config; player.load(flashUrl); } function loadflash001() { loadflash('flash001','%PUBLIC_URL%/flash/legacy.swf'); } window.addEventListener("playflash", loadflash001); window.addEventListener("load", loadflash001); </script> </html> headタグ内で、下記のように、ruffle.jsを読み込んでいます。 <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script> loadflashが Flash を再生させる関数です。 最終的にloadイベントとplayflashイベントで Flash を再生させようとしています。 なぜloadのほうに登録が必要かと言うと、URL 直打ちでplayflashイベントが発生できなかったからです。 ※この辺り細かく検証できていなくて、分かる方いたらコメントいただけると嬉しいです。 直接loadflashをaddEventListenerしてみましたが、 loadイベント時にplayflashイベントも同時発生することが分かりました。 そのため、loadflash001関数を使って、あえて2段編成にしています。 index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.sass'; import reportWebVitals from './reportWebVitals'; import MainApp from "./components/main"; import HeaderApp from "./components/header"; import FooterApp from "./components/footer"; import { createBrowserHistory } from "history" import { Router } from 'react-router'; const history = createBrowserHistory({ basename: '/new_web' }); ReactDOM.render( <React.StrictMode> <Router history = {history}> <HeaderApp></HeaderApp> <MainApp></MainApp> <FooterApp></FooterApp> </Router> </React.StrictMode>, document.getElementById('root') ); reportWebVitals(console.log); main.tsx import React from 'react'; import { Route, Switch } from 'react-router-dom'; import OutlineApp from './outline'; import MainContentApp from './maincontent'; class MainApp extends React.Component { render() { return ( <main id="wrap_main"> <div id="sidearea"></div> <div id="wrap_contents"> <Switch> <Route exact path="/" component={MainContentApp} /> <Route exact path="/outline/" component={OutlineApp} /> <Route render={() => <p>NotFound</p>} /> </Switch> </div> <div id="main-right-spacer"></div> </main > ); } } export default MainApp; 上記のmain.tsxの中で、 <Route exact path="/" component={MainContentApp} /> <Route exact path="/outline/" component={OutlineApp} /> という部分があります。ここで、URL が/に遷移した時には、MainContentAppが読み込まれます。 そこには、Flash を再生させたいタグid='flash001'があります。 しかし、URL が/outline/に遷移した時には、MainContentAppの代わりにOutlineAppが読み込まれます。 そのため、タグid='flash001'は一度無くなります。 その後、再びURL が/に遷移すると、もう一度MainContentAppが読み込まれます。 タグid='flash001'が、再度出現することになります。 この時、window.addEventListener("load", func)だけだと、Flash は再生されません。 loadイベントは、初めて DOM を読み込んだ時にしか発生しないからです。 maincontent.tsx import React from 'react'; let FlashEvent = new CustomEvent('playflash', { bubbles: true }); class MainContentApp extends React.Component { componentDidMount() { window.dispatchEvent(FlashEvent); } render() { return ( <React.StrictMode> <div id="flash001"></div> </React.StrictMode> ); } } export default MainContentApp; 上記maincontent.tsxに<div id="flash001"></div>があります。ここにFlashを読み込みます。 また、maincontent.tsxでは2行目で、下記のようにplayflashイベントを宣言しています。 let FlashEvent = new CustomEvent('playflash', { bubbles: true }); そして、このMainContentAppコンポーネントがマウントされると、 componentDidMount() { window.dispatchEvent(FlashEvent); } によって、FlashEventがディスパッチされます。 これにより、index.htmlで用意した、playflashのイベントリスナーがキックされます。 header.tsx import React from 'react'; import { NavLink } from 'react-router-dom'; import FixedMainNavApp from "./fixed_mainnav"; class HeaderApp extends React.Component { render() { return ( <React.StrictMode> <header id="wrap_header"> <div id="wrap_logo"> <a href="/"> <img id="mainlogo" alt="株式会社logo" /> </a> </div> <nav id="wrap_gmenu"> <ul> <FixedMainNavApp /> </ul> </nav> </header> </React.StrictMode> ); } } export default HeaderApp; 上記のheader.tsxでは、FixedMainNavAppでナビゲーションのリンクを読み込んでいます。 fixed_mainnav.tsx import React from 'react'; import { Link } from 'react-router-dom'; class FixedMainNavApp extends React.Component { render() { return ( <React.StrictMode> <li><Link to="/">ホーム</Link></li> <li><Link to="/outline/">会社概要</Link></li> <li><Link to="/digital/">デジタル事業</Link></li> <li><Link to="/recruit/">リクルート</Link></li> <li><Link to="/sitemap">サイトマップ</Link></li> </React.StrictMode> ); } } export default FixedMainNavApp; 上記のfixed_mainnav.tsx内でLinkタグをユーザーがクリックすると URL 遷移します。 まとめ 負の遺産は早く捨てたほうが幸せ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react+typescript+Material-UI+Styled Components .Part2 (Styled Componentsを使う)

Styled Componentsの具体的な使い方 準備色々しましてやっと今回はStyled Componentsの使い方 test.tsx import React from "react"; import styled from "styled-components"; // Title コンポーネントの作成、中身は h1 タグにスタイルを適応したもの export const Title = styled.h1` font-family: "Yu Gothic"; font-weight: normal; font-size: 16px; color: black; `; // Wrapper の作成、中身は div タグにスタイルを適応したもの export const Wrapper = styled.div` width: 299px; height: 431px; background: #fff; box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16); `; // 作成した Title と Wrapper はコンポーネントとして利用できる const HelloWorld = () => ( <Wrapper> <Title>SABORITAI!!</Title> </Wrapper> ); export default HelloWorld; まずはざっくりと外観。 HTMLの各タグを変数化して装飾してます。 基本の使い方  const 任意のコンポーネント名 = styled.装飾するHTML要素` ここにCSSを記述していく。 `; てな形で記述していく。 ``(バッククォート)を忘れないように。 スタイルの拡張 装飾した各コンポーネントはimportして使いまわし可能 例えばtitleコンポーネントの色だけ変えたいときは test2.tsx import { Title, wrapper } from "./test"; const RedTitle = styled(Title)` color: red; `; const Hogehoge = () => ( <Wrapper> <RedTitle>SABORITAI!!</RedTitle> </Wrapper> ); export default Hogehoge; こんな感じでほかのCSSを引き継いだうえで色だけ変える事とかできる。 コンポーネントを装飾する時は const 任意のコンポーネント名 = styled(装飾するコンポーネント)` ここにCSSを記述していく。 `; 引数を使う test3.tsx import { wrapper } from "./test"; const Text = styled.p` color: ${props => props.color ? props.color : "black"}; `; const hogehoge = () => ( <Wrapper> <Text color="red">hoge</Text> <Text color="green">hogehoge</Text> <Text>hogehogehoge</Text> </Wrapper> ); export default HelloWorld; 引数によって文字の色を変更。 propsに引数が渡されるのでテンプレートリテラルでその属性を使用すればいい。 Styled Componentsの基本的な使い方は以上です。 次はMaterial-UIの基本的な使い方を踏まえたうえで material-ui + styled components をやっていく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

strapiの自作プラグインのアイコンを変更する方法

はじめに strapiのプラグインのメニューアイコンを変更する方法です。 動作環境 Mac: MacBookPro macOS Big Sur 11.3.1 node: v12.18.3 yarn: 1.22.10 Strapi v3.6.2 自作プラグインの追加 現象を再現するために新規にプロジェクトを作成します。 $ yarn create strapi-app my-project --quickstart 適当にadminユーザーを作成してログインします。 以下のコマンドからプラグインを追加します。 cd my-project/ yarn strapi generate:plugin qiitaPlugin adminモードでもう一度起動します。(デフォルトで8000ポートで起動します。) yarn develop --watch-admin アイコンの変更 アイコンを変更したい場合は、プラグインのディレクトリのpackage.jsonのiconの部分を変更します。 プロジェクト/plugin/プラグイン名/package.json package.json { "name": "strapi-plugin-{プラグイン名}", "version": "0.0.0", "description": "This is the description of the plugin.", "strapi": { "name": "{プラグイン名}", "icon": "plug", <- ここを変更 "description": "Description of *** plugin." }, "dependencies": { "@buffetjs/core": "^3.3.6", "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "styled-components": "^5.3.0" }, "author": { "name": "A Strapi developer", "email": "", "url": "" }, "maintainers": [ { "name": "A Strapi developer", "email": "", "url": "" } ], "engines": { "node": ">=10.16.0 <=14.x.x", "npm": ">=6.0.0" }, "license": "MIT" } デフォルトではplugとなっており、選択肢は、github上で確認することができます。 おそらくですが、FontAwesomeから流用していると思われますが、公式サイトから利用したいアイコンを探すと良いと思います. 例えば、歯車アイコンのcogsに変更すると以下のようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] useCallbackで作られる関数に対してgenericを使いたい

問題 import React from "react" interface Props {} interface CallbackProps { a: number b: string } interface CallbackReturn { c: boolean } const Test: React.FC<Props> = (props) => { const callback = React.useCallback<(props: CallbackProps) => CallbackReturn>( (props) => { const {a, b} = props return { c: true, } }, [] ) const x = callback({ a: 1, b: "弐" }) console.log('x.c:', x.c) return <></> } export default Test useCallback自体にはgenericで型付けが出来るんだけど、ここで言うcallback変数となった関数にはgenericは渡せない。 callback<Generic>(...) // こうしたい場合 解決 関数を返すuseMemoに切り替える。こうすることでdependencyも使え無駄に関数を生成することはないし、genericを渡せる関数にもなる。 import React from "react" interface Props {} interface CallbackProps<T> { a: T b: string } interface CallbackReturn { c: boolean } const Test: React.FC<Props> = (props) => { const callback = React.useMemo(() => function<T>( props: CallbackProps<T> ): CallbackReturn { const { a, b } = props return { c: true, } }, []) const x = callback<boolean>({ a: false, b: "弐" }) console.log('x.c:', x.c) return <></> } export default Test
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseのタイムスタンプをいい感じの日付型へ変換

はじめに Firebaseでデータにタイムスタンプを持たせることは多いが、そのデータをいい感じに表示させる方法 タイムスタンプ素材の型 まずタイムスタンプをそのままの型でコンソールに表示すると、一見不思議な数列が返ってくる test.jsx console.log(timestamp); => 063758775786.728000000 これをtoDate()で日付型に変換すると、何時なのか分かる型で返ってきたものの、このままアプリに表示するには少しブサイク test.jsx console.log(timestamp.toDate()); => Wed Jun 09 2021 04:03:06 GMT+0900 (日本標準時) いい感じに変換する関数を作る test.jsx const datetimeToString = (date) => { return date.getFullYear() + '-' + ('00' + (date.getMonth() +1)).slice(-2) + '-' + ('00' + date.getDate()).slice(-2) + ' ' + ('00' + date.getHours()).slice(-2) + ':' + ('00' + date.getMinutes()).slice(-2) + ':' + ('00' + date.getSeconds()).slice(-2) } この関数に日付型へ変換したタイムスタンプを引数として渡すと、、、 test.jsx const date = datetimeToString(updated_at.toDate()) console.log(date); => 2021-06-09 04:03:06 よくある形へ変換できた! おわりに Firebaseのドキュメントは肌に合わない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React × Three.js】Blenderで作成したモデルをReactプロジェクトに読み込む

概要 Blenderでモデリングしたmodelを、react-three-fiber(Three.jsのReact用ラッパーパッケージ)を使用してReactプロジェクトに読み込みます。 この記事では要点の説明をしますが、step by step の解説はしていないです。 最終的にこんな感じになりました。 前提 react-three-fiber(r3f) を使用して、Introduction を完了している方 環境 Windows10 VSCode React (17.0.2) typescript (4.0.3) three (0.129.0) @react-three/fiber (6.2.2) blender (2.93) セットアップ create-react-app を使用してプロジェクトを作成します。 作成方法は、以下を参照してください。 Three 関連のパッケージをインストールします。 npm i three @types/three @react-three/fiber @react-three/drei モデリング 保存とエクスポートの確認は、こまめにしましょう!(自戒を込めて) Lego人形 blender を使用してモデリングをします。 今回作成した Lego人形 は、以下を参考にさせて頂きました。 エクスポート Three.js で扱うためには、.gltf か .glb ファイルとしてエクスポートします。 エクスポートの設定は、以下のようにします。 モデリングの何某かが原因で、エクスポート中にエラーになることがあるので、こまめにエクスポートできるか確認するといいと思います。 テクスチャー Lego人形 の顔部分は、テクスチャーを使用します。リソース画像は、React側で再度読み込む必要がありますが、貼り付けに関する情報はエクスポートされます。 注意点として、Three.js ではテクスチャーの貼り付けオプションが以下の3つしかありません。 THREE.RepeatWrapping :テクスチャーをリピートする THREE.ClampToEdgeWrapping :テクスチャーを拡張する THREE.MirroredRepeatWrapping :テクスチャーをミラーリピートする Wrapping Modes blender でクリップ(繰り返しなし)を設定しても、Three.js側では対応していないので、拡張 を選択します。 拡張に設定すると、テクスチャー画像(青枠)の端(縦横の辺)が、貼り付け範囲(赤枠)まで引き延ばされます。 これを回避するためには、テクスチャーを張り付ける範囲に面(青枠)を作成して、それ以外の部分を縦横の辺の延長(赤線)から追い出します。 インポート インポートファイルの作成 React でエクスポートしたモデルをインポートします。 そのために、以下のツールを使ってインポートファイルの雛形を作成します。 npx gltfjsx model.glb -t -s -t :TypeScript 対応 -s :読み込んだ model に影の設定を割り当てる 成功すると、上記のファイル名の場合、Model.ts が作成されます。 リソース リソースファイル(.glb ファイル、テクスチャー画像)は、React プロジェクトの public フォルダに置きます。 インポートファイルの読み込み .gltf .glb ファイルをロードする場合、componet は Suspense で囲う必要があります。 export const TLego: React.FC = () => { return ( <> {/* カメラ */} <OrbitControls enablePan={false} /> {/* オブジェクト */} <Suspense fallback={null}> <Lego /> <Environment preset='sunset' background={true} /> </Suspense> </> ); }; gltfjsx で作成したファイル ExactLego.tsx(抜粋) const modelFolderPath = './resource/blenderModels/'; const modelPath = modelFolderPath + 'ExactLego.glb'; useGLTF.preload(modelPath); export const Lego: React.FC = (props: JSX.IntrinsicElements['group']) => { const group = useRef<THREE.Group>(); // model load const { nodes, materials } = useGLTF(modelPath) as GLTFResult; const [texture] = useTexture([modelFolderPath + 'face.png']); texture.flipY = false; materials.head.map = texture; 煮るなり焼くなり これで、モデルはインポートできると思います。あとは、煮るなり焼くなりしていきます。 参考にしたコード カラーセレクター 状態管理 Canvas と dom 間で状態をやり取りするときに、Recoil を使用するとひと工夫必要になります。 valtio を使うと、シンプルな状態管理を行うことができます。 UI
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【react-three-fiber】Blenderで作成したモデルをr3fで読み込む

概要 Blenderでモデリングしたmodelを、Reactプロジェクトに読み込んで、Three.jsを使って表現します。 要点の説明をしますが、step by step の解説ではないです。 最終的にこんな感じになりました。 前提 react-three-fiber(r3f) を使用して、Introduction を完了している方 環境 Windows10 VSCode React (17.0.2) typescript (4.0.3) three (0.129.0) @react-three/fiber (6.2.2) blender (2.93) セットアップ create-react-app を使用してプロジェクトを作成します。 作成方法は、以下を参照してください。 Three 関連のパッケージをインストールします。 npm i three @types/three @react-three/fiber @react-three/drei モデリング 保存とエクスポートの確認は、こまめにしましょう!(自戒を込めて) Lego人形 blender を使用してモデリングをします。 今回作成した Lego人形 は、以下を参考にさせて頂きました。 エクスポート Three.js で扱うためには、.gltf か .glb ファイルとしてエクスポートします。 エクスポートの設定は、以下のようにします。 モデリングの何某かが原因で、エクスポート中にエラーになることがあるので、こまめにエクスポートできるか確認するといいと思います。 テクスチャー Lego人形 の顔部分は、テクスチャーを使用します。リソース画像は、React側で再度読み込む必要がありますが、貼り付けに関する情報はエクスポートされます。 注意点として、Three.js ではテクスチャーの貼り付けオプションが以下の3つしかありません。 THREE.RepeatWrapping :テクスチャーをリピートする THREE.ClampToEdgeWrapping :テクスチャーを拡張する THREE.MirroredRepeatWrapping :テクスチャーをミラーリピートする Wrapping Modes blender でクリップ(繰り返しなし)を設定しても、Three.js側では対応していないので、拡張 を選択します。 拡張に設定すると、テクスチャー画像(青枠)の端(縦横の辺)が、貼り付け範囲(赤枠)まで引き延ばされます。 これを回避するためには、テクスチャーを張り付ける範囲に面(青枠)を作成して、それ以外の部分を縦横の辺の延長(赤線)から追い出します。 インポート インポートファイルの作成 React でエクスポートしたモデルをインポートします。 そのために、以下のツールを使ってインポートファイルの雛形を作成します。 npx gltfjsx model.glb -t -s -t :TypeScript 対応 -s :読み込んだ model に影の設定を割り当てる 成功すると、上記のファイル名の場合、Model.ts が作成されます。 リソース リソースファイル(.glb ファイル、テクスチャー画像)は、React プロジェクトの public フォルダに置きます。 インポートファイルの読み込み .gltf .glb ファイルをロードする場合、componet は Suspense で囲う必要があります。 export const TLego: React.FC = () => { return ( <> {/* カメラ */} <OrbitControls enablePan={false} /> {/* オブジェクト */} <Suspense fallback={null}> <Lego /> <Environment preset='sunset' background={true} /> </Suspense> </> ); }; gltfjsx で作成したファイル ExactLego.tsx(抜粋) const modelFolderPath = './resource/blenderModels/'; const modelPath = modelFolderPath + 'ExactLego.glb'; useGLTF.preload(modelPath); export const Lego: React.FC = (props: JSX.IntrinsicElements['group']) => { const group = useRef<THREE.Group>(); // model load const { nodes, materials } = useGLTF(modelPath) as GLTFResult; const [texture] = useTexture([modelFolderPath + 'face.png']); texture.flipY = false; materials.head.map = texture; 煮るなり焼くなり これで、モデルはインポートできると思います。あとは、煮るなり焼くなりしていきます。 参考にしたコード カラーセレクター 状態管理 Canvas と dom 間で状態をやり取りするときに、Recoil を使用するとひと工夫必要になります。 valtio を使うと、シンプルな状態管理を行うことができます。 UI
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】ざっくりReducerのススメ

Reducerのススメ React勉強中ですが、Reducerがとんでもねぇものだということを伝えたく筆を執りました。 実際に使用した実感として、コンポーネント間でロジックや値、stateを共通化できるのが一番のメリットだと思っています。 あと、reducerのaction.typeを変えるだけで処理を切り替えることができるのも、何気に便利です。 ともあれ、さっそく、使ってみましょう! 実装 よく使われるbuttonコンポーネントの例でReducer使ってみます。 コードの参照はこのサイトから Hoge.jsx const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); } ここで宣言しています。ざっくり左のstateにはinitializeState、dispatchにreducerを入れています。 Hoge.jsx const [state, dispatch] = useReducer(reducer, initialState); ここで、実際にdispatchの中にあるファンクションを呼び出しています。 ちなみに、onClickの中の「()=>」を消すと、無限ループバグになるときがあるので注意です。(renderごとにdispatchの中の処理が走り、stateが更新されると、再びrenderされ、再び読み込まれたdispatchが走ります) Hoge.jsx #正しい <button onClick={() => dispatch({type: 'decrement'})}>-</button> #無限ループ <button onClick={dispatch({type: 'decrement'})}>-</button> 以下は、先ほどの宣言時に入れていた値とファンクションの中身になります。 initializeStateでは値やstateを初期化しています。 reducerでは引数を受け取り、action,typeごとにそれぞれ処理を実行してくれます。 Hoge.jsx const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } さきほど書いたdispatchでの括弧にあるtyoeが上のaction.typeで識別する値になります Hoge.jsx <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> 発展 ここから簡単に発展させられるところとして、 ・ファイル分割 ・指定するaction.typeを楽に参照できるようにする があります。ざっくりこんな感じです。 Hoge.jsx const initialState = {count: 0}; #ここ追加 export const actionTypes = () { increment:"increment", decrement:"decrement", } export function reducer(state, action) { switch (action.type) { case actionTypes.increment: return {count: state.count + 1}; case actionTypes.decrement: return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: actionTypes.decrement})}>-</button> <button onClick={() => dispatch({type: actionTypes.increment})}>+</button> </> ); } ここで追加したのは下記のコードです。 このコードの役割は、actiontypeで値を参照させることで、このReducerで使う単語を指定することができます。 Hoge.jsx export const actionTypes = () { increment:"increment", decrement:"decrement", } また、べた書きしていた箇所を変えられます。 以下では、case文の値をべた書きではなく、参照して値を判定しています。 Hoge.jsx export function reducer(state, action) { switch (action.type) { case actionTypes.increment: return {count: state.count + 1}; case actionTypes.decrement: return {count: state.count - 1}; default: throw new Error(); } } またこちらではtypeで渡している値をべた書きではなく参照値にして、記載ミスがないようになっています。 Hoge.jsx <button onClick={() => dispatch({type: actionTypes.decrement})}>-</button> <button onClick={() => dispatch({type: actionTypes.increment})}>+</button> 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む