20210913のReactに関する記事は9件です。

html文字列をReactElementに変換する

やりたいこと html文字列をReactElementに変換する. 例えば,"<img src='sample.png'>"という文字列をアプリ上で入力すると画像が表示されるなど. 方法 react-html-parserというライブラリを使う. 実装例 "<img src='sample.png'>"という文字列をReactElementに変換して表示する例 index.tsx import React from "react"; import ReactHtmlParser from "react-html-parser"; const Index: React.VFC = () => { const imgHtml = "<img src='sample.png'>"; return ( <div> {ReactHtmlParser(imgHtml)} </div> ); }; export default Index; 文字列をtextareaに入力し,ReactElementに変換して表示する例(TypeScript) index.tsx import React, { useState } from "react"; import ReactHtmlParser from "react-html-parser"; const Index: React.VFC = () => { const [html, setHtml] = useState<string>(""); return ( <> <div> {ReactHtmlParser(html)} </div> <textarea onChange={(e) => setSlide(e.target.value)} /> </> ); }; export default Index;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React]onClickするたびに配列に要素を追加する

はじめに ラジオボタンを押すと、押した選択肢のvalueが配列に追加するようにしたかった。 方法がわからず、記事を探しても中々見つからず苦労したので、こちらに記録しておきます。 実現したい事 2つのラジオボタングループがある。 それぞれ3つずつ選択肢があり、ユーザーがクリックした選択肢を配列に追加したい。 解決策 const initialValue = Array(selections.length).fill('0'); const [formData, setFormData] = useState<string[]>(initialValue); const updateSelections = (index: number, value: string) => { const newFormData = formData.slice(); newFormData[index] = value; setFormData(newFormData); }; ///... return ( <div> <FormLabel>{data.formLabel}</FormLabel> <RadioGroup onChange={(e) => updateSelections(index, e.target.value)}> {data.options.map((option) => ( <FormControlLabel key={option.value} control={<Radio />} label={option.label} value={option.value} id={option.value} /> ))} </RadioGroup> </div> ); ①formDataを用意 ②formDataの初期値を0にする(initialValue) ③onChangeにupdateSelectionsを実行し、index,選択肢のvalueを渡す  updateSelectionsでは、  (1)newFormDataによってformDataのコピーを作る。(本物を破壊したくないため)  (2)newFormData[index]によって、配列・newFormDataのindexの位置にvlue(選択肢の値)を挿入する。 (3)(2)の値をsetFormDataに入れる ④formDataにonClickごとに選択肢の値が追加されるようになる   indexを渡すことで、要素を判別できるようになり、複数個追加できるようになった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Stitchesはデザインシステム構築をサポートする最高のソリューション(css-in-js)になるか?

Stitchesとは 2021年8月31日にver1がリリースされたcss-in-jsライブラリー(公式サイト) Near-Zero Runtimeを謳っており、Emotionやstyled-componentsといった他のcss-in-jsライブラリーより高速(ベンチマーク) VariantsやToken、theme機能をサポートしており、『デザインシステムの構築と運用を支援する強力なcss-in-jsソリューション』という佇まいを見せている。 前置き: Stitchesに興味をもった個人的背景 個人的に仕事でもプライベートでもNext.jsをよく使うが、これまでNext.jsでプロジェクトをするときは公式の推しに乗ってCSS Modulesを利用してきた。 CSS Modulesはwebpackのcss-loaderで『名前空間(クラス名)の安全性を確保する』という特定の問題の解決にのみフォーカスした特にモダンでもない技術で、新しいもの好きの知的好奇心や冒険心を掻き立てたりはしてくれないものの、生cssという標準技術との距離が圧倒的に近いこともあり、確立されたエディターの補完やstylelintなどのエコシステムの恩恵を存分に受けることができ、また、技術の流行り廃りに伴う将来的な技術的負債化リスクが低いと考えられ、また、パフォーマンス的にもcss-in-jsなどに比べて優位なことから、割と消去法的選択としてCSS Modulesを選択してきた。 CSS Modulesは確立されたしっかりした技術でViteなどの次世代ビルドツールにも標準サポートされているし、State of CSS in 2020のレポートによると他のcss-in-jsのソリューションに比べてもいまだに一番使用されてもいるし、安牌ではあると今も思っている。今年の6月には、Tailwind CSSの爆発的な広がりに対してそれでも私がTailwind CSSではなく、CSS Modulesを推す理由などの記事も個人的に書かせていただいたりして、それなりの人に読んでいただけたりもした(賛否は不明)。 しかし、一方で近年フロントエンド界隈でデザインシステムの構築と運用を支える技術的アプローチへの関心が急激に高まってきているのを感じており、CSS Modulesの運用におけるscssで定義する定数集やmixin集よりもっと強力な技術的な支援が必要なのかもしれないとも思っていた‥。 Stitchesのことを初めて知ったのは、ShopifyのデザインシステムPolarisをSassから何に移行するかというGithubの議論で、個人的にも今度また仕事でNext.jsで新規プロジェクトをやる予定があったところ、丁度ver1がリリースされ、改めて公式ドキュメントなどを見たところ、Stitchesは明確に『デザインシステムの構築と運用を支援するcss-in-jsソリューション』といった佇まいを見せている(気がした)ので、プロダクションへの投入の検討に、ある程度まとまった量のスタイルを実際にStitchesで書いてみて、検証してみることにした。 Stitches(in Next.js)で作ってみたデモページ こんな感じの擬似ブログ的なもののトップページを今回作ってみました(Stitchesの検証用のため、機能はついていない)。 Vercelでデプロイしてありますので、よろしければチェックしてみてください(こちら)。 また、今回のデモのコードはhttps://github.com/70ki8suda/stitches-demo にアップしてあります。 ページを構成するコンポーネント 今回はザックリとコンポーネントを4つ、Header・SideMenu・FeaturedContent(右上に大きく表示される記事)・ArticleItem(4つ並ぶ記事)作ってページを構成した。 以下、Storybookのスクショ tokenを定義するconfigファイル Stitchesでは、サイト全体で使われる定数などをstitches.config.jsに記述する。 stithces.config.js import { createStitches } from '@stitches/react'; export const { config, createTheme, css, getCssText, globalCss, styled, theme, keyframes } = createStitches({ theme: { colors: { textBlack: '#0b0d0e', grayBorder: '#F3F3F4', accentBlue1: '#3B86CB', }, space: { 1: '5px', 2: '10px', 3: '15px', 4: '20px', 5: '25px', 6: '35px', }, sizes: { 1: '5px', 2: '10px', 3: '15px', 4: '20px', 5: '25px', 6: '35px', }, fontSizes: { 1: '12px', 2: '13px', 3: '15px', 4: '17px', 5: '19px', 6: '21px', 7: '24px', titleSize: '30px', }, fonts: { system: 'system-ui', }, }, utils: { marginX: (value) => ({ marginLeft: value, marginRight: value, }), marginY: (value) => ({ marginTop: value, marginBottom: value, }), paddingX: (value) => ({ paddingLeft: value, paddingRight: value, }), paddingY: (value) => ({ paddingTop: value, paddingBottom: value, }), }, media: { sp: '(max-width: 959px)', pc: '(min-width: 960px)', }, }); このような形でサイト全体で共通して使用したい、色・フォント・フォントサイズ・マージン・メディアクエリーなどをこの設定ファイルに記述する。また、この設定ファイル内のutilsでユーティリティプロパティを設定することもできる(今回は使用しなかった)。 case1: pages/index.jsx トップページのjsx部分は以下のようにコーディングしてみた pages/index.jsx //省略 <MainContainer> <SideMenu /> <MainArea> <FeaturedContent color='yellow' /> <ArticlesArea> <h2 className='ArticlesArea__lead'>Recent update project</h2> <ul className='ArticlesArea__list'> {articles && articles.length > 0 && articles.map((article, i) => <ArticleItem title={articles[i].title} body={articles[i].body} i={i} />)} </ul> </ArticlesArea> </MainArea> </MainContainer> トップページのstitchesによるスタイリング部分 pages/index.jsx const MainContainer = styled('div', { display: 'flex', '@sp': { display: 'block', }, }); const ArticlesArea = styled('div', { padding: '30px', '& .ArticlesArea__list': { display: 'flex', gap: '20px 20px', '@sp': { flexWrap: 'wrap', }, }, '& .ArticlesArea__lead': { fontSize: '$5', marginBottom: '1.4em', }, }); こんな感じでDOMの詳細なタグ名などを隠蔽して、styled-componentsのようにスタイルコンポーネントを書くことが出来る。 styled-componentではテンプレートリテラル記法で書けるが、Stitchesはobject-literal記法しかサポートしていない。 しかし、エディターの補完が効くので、意外とそこまで面倒くさくはなかった。 Reactで定義したコンポーネントかスタイルコンポーネントかパッと見でわからないなどはあるものの、個人的にはタグ名をセマンティックに書けるのは気に入っている。 '@sp': { flexWrap: 'wrap', }, みたいに、メディアクエリーを省略してスタイル書けるのはとても楽。 今回、スタイルコンポーネントとして定義するのはBEMでいうブロック要素単位で行ってみることにした。 全てをスタイルコンポーネントとして定義するのでなく、ブロックのエレメント要素にはクラス名でスタイルをあてている。 これによって、若干、コーディングの面倒くささが減る。また、スタイルコンポーネントに対してあてたスタイルはハッシュ値のクラス名を最終的に付与されてレンダリングされるが、スタイルコンポーネントの子孫要素としてクラス名であてたスタイルのクラス名は残る(上記の例の.ArticlesArea_listで定義したスタイルは .[ハッシュ] .ArticlesArea_listとして出力される)ので、デバッグ時にdeveloper toolなどでクラス名から修正箇所を探す際などのフックにもなって保守の際、便利になるのでは?という気がしている。 case2: components/FeaturedContent.jsx components/FeaturedContent.jsx export default function FeaturedContent({ color }) { return ( <Wrapper> <div className='image-wrap'> <img src='https://www.syfy.com/sites/syfy/files/styles/1140x640_hero/public/screen-shot-2021-09-09-at-9.07.24-am.png' /> <p className='image-credit'> Credit: The Matrix Resurrections – Official Trailer 1 / Warner Bros. Pictures YouTube </p> <div className='featured-lead-wrap'> <FeaturedLead className='featured-lead' color={color}> 'THE MATRIX RESURRECTIONS' <br /> TRAILER SENDS TWITTER DOWN A RABBIT HOLE OF FAN THEORIES </FeaturedLead> </div> </div> </Wrapper> ); } このコンポーネントでは、Stitchesの強力な機能であるVariantsを使ってみた。 Qiita埋め込み用Stitches: Variants demo pic.twitter.com/o50ArPiMNt— 70ki8suda (@70ki8suda) September 13, 2021 Featured-lead.jsx <FeaturedLead className='featured-lead' color={color}> 'THE MATRIX RESURRECTIONS' <br /> TRAILER SENDS TWITTER DOWN A RABBIT HOLE OF FAN THEORIES </FeaturedLead> const FeaturedLead = styled('h2', { zIndex: '2', display: 'inline', fontSize: '2.4vw', maxWidth: '60%', background: '#fff', '@sp': { fontSize: '4vw', }, variants: { color: { yellow: { boxShadow: 'inset 0 -2.4px #fff, inset 0 -11.2px #ffff00, 5px 5px 0 5px #fff, -5px 5px 0 5px #fff', }, red: { boxShadow: 'inset 0 -2.4px #fff, inset 0 -11.2px #ff0000, 5px 5px 0 5px #fff, -5px 5px 0 5px #fff', }, blue: { boxShadow: 'inset 0 -2.4px #fff, inset 0 -11.2px #0000ff, 5px 5px 0 5px #fff, -5px 5px 0 5px #fff', }, }, }, }); のように、スタイルコンポーネントで定義したcolorアトリビュートを設定することで、簡単にスタイルを切り替えられる。 これをうまく使用してサイズや色やちょっとした装飾などのバリエーションを作れば、いい感じにコンポーネントが作れそうだ。 また、Storybookとの相性も◎ StitchesはCSS Modulesを越える選択肢になり得るか? 今回、はっきりいってStitchesを使ってCSSを書くDXはとても良かった。 最終的にコード量が増えてくるとコード分割するだろうとはいえ、CSS Modulesと違って、jsxの同ファイルにいきなりスタイルを書き始めることが出来るのは、DXが良い。 CSS Modulesだと、普通のcssファイルで書くので、font-weight:bold;とか書きたいときは、短縮記法でfwb+tabとかのコマンドをターンと入力すれば速攻でcssを書いていけるが、css-in-jsはそういうのは無理。 しかし、TypeScriptの補完でfonくらいまで入力すればfontWeightという選択肢がエディター上に表示されるので、そこまでストレスは感じなかった。 デザインシステムという観点から言うと、stitches.config.jsファイルで定数を入力することによるデザイントークンの強制力はcss modulesの定数集とさして変わることはない。 Variantsだって、Stitchesのシステムはエレガントではあるものの、propsでクラス名を差し替えればcss modulesで同じことは出来る。 はっきり言って CSS Modulesで既に書いたプロジェクトをStitchesに書き換える必要が出てくるほどの機能的な優位性などはないと思う(ランタイムのパフォーマンスもcss modulesの方がはやいし) しかしStitchesにはTokenやVariantsを駆使してスタイルコンポーネントをうまく作ってデザインシステムを構築していこうというレールが機能として敷かれているので、自然とデザインシステムに対するメンバーの意識は上がり易いと思われる ここの部分にどこまで価値を見出せるかだと思う。 Stitchesっていう最高にイケてるcss-in-jsライブラリーを使って、最高の俺らだけのデザインシステムを作ろうぜ!ってテンションになって開発できるのならば、自分は十分Stitchesを採用する価値はあると思う。 だから、個人的には次のプロジェクトの技術選定ではStitchesを推していきたいと思ったし、個人プロジェクトではほぼ100%使うと思う。 今回の記事で言及しなかったが、Stitchesの開発元のmodulzはRadix-uiというUIコンポーネントライブラリーも開発している。 Radix-UIではcssのついていないプレーンなアクセシビリティ(wai-aria)など対応済みのコンポーネントを配布してくれており、これらをStitchesと共に利用すれば、各サービスのデザインや要件に応じたコンポーネントの開発スピードをブーストしてくれそうだなと思っている。 Modulzの開発者がこれらの技術を使ったデザインシステムの構築に関して非常に興味深いブログ記事Why I Build Design Systems with Stitches and Radixを書いてくれていたりもするので、是非参照なさってください!(近日中にこちらの記事の方も翻訳します)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactアプリをセキュアにする最良の方法

こんにちは。devsecopsのデベロッパ向けの記事として javaのフレームワークとして人気沸騰中のReactを取り上げました。 コーディングはできませんが、 英語の技術記事を読むのはそこそこ得意なので、DevSecOpsに関連するSnykのブログ記事を少しずつ読んでいます。ブログ記事は、デベロッパの方の個性が文章にみられ、なかなか面白いです。 今回は、Ron Perris(ロン・ペリス)氏の2020年10月28日のReactアプリをセキュアにする最良の方法についてのブログ記事をご紹介します。Reactコードのセキュリティ関連のエラーを自動的にテストし、自動的に修正する方法についての記事をご紹介します。 React におけるセキュリティのベストプラクティス 10項目 1.データバインディングによるデフォルトのXSS保護 波括弧{}を使用したデフォルトのデータバインディングの使用で、ReactはXSS攻撃から保護するために値を自動的にエスケープします。この保護はtextContentレンダリング時にのみ実行され、HTML属性のレンダリング時には実行されないことに注意してください。 JSXデータバインディング構文{}を使用して、エレメントにデータを配置します。 推奨: <div>{data}</div> カスタムバリデーションなしの動的属性値を回避します。 危険: <form action={data}>... 2.危険なURL URLには、javascript:プロトコルURLを介して動的なスクリプトコンテンツを含めることができます。バリデーションによりリンクがhttp:またはhttps:であることを確認し、javascript:のURLベースのスクリプトインジェクションを回避してください。ネイティブのURLパーシング機能を使用してURLバリデーションを実行し、パースされたプロトコルプロパティが許可リストに一致していることを確認してください。 推奨: function validateURL(url) { const parsed = new URL(url) return ['https:', 'http:'].includes(parsed.protocol) } <a href={validateURL(url) ? url : ''}>Click here!</a> 危険: <a href={attackerControlled}>Click here!</a> 3.HTMLのレンダリング dangerouslySetInnerHTMLを使用してレンダリングされたDOMノードにHTMLを直接挿入することができます。この方法で挿入されたコンテンツは、事前にサニタイズする必要があります。 値をdangerouslySetInnerHTMLのpropに配置するまえに、dompurifyのようなサニタイズライブラリを使用してください。 HTMLをDOMに挿入するときにdompurifyを使用します。 import purify from "dompurify"; <div dangerouslySetInnerHTML={{ __html:purify.sanitize(data) }} /> 4.ダイレクトDOMアクセス DOMにアクセスしてコンテンツをDOMノードに直接挿入することは避けてください。HTMLをインジェクトする必要がある場合にはdangerouslySetInnerHTMLを使用し、dompurifyを使いインジェクトをする前にサニタイズしてください。 推奨: import purify from "dompurify"; <div dangerouslySetInnerHTML={{__html:purify.sanitize(data) }} /> refやfindDomNode()を使ってレンダリングされたDOMエレメントにアクセスし、innerHTMLおよび同様のプロパティまたはメソッドを介してコンテンツを直接インジェクトすることは避けてください。 危険: this.myRef.current.innerHTML = attackerControlledValue; 5.サーバーサイド レンダリング ReactDOMServer.renderToString()やReactDOMServer.renderToStaticMarkup()のようなサーバーサイド レンダリング関数を使用するときに、データバインディングによりコンテンツが自動的にエスケープされます。 ハイドレーションのためにストリングをクライアントに送信する前に、renderToStaticMarkup()のアウトプットにストリングを連結することは避けてください。 XSSを回避するために、サニタイズされていないデータをrenderToStaticMarkup()のアウトプットと連結させることは避けてください: app.get("/", function (req, res) { return res.send( ReactDOMServer.renderToStaticMarkup( React.createElement("h1", null, "Hello World!") ) + otherData ); }); 6.依存関係における脆弱性の検出 サードパーティコンポーネントのいくつかのバージョンには、セキュリティの問題が含まれている場合があります。依存関係を確認し、より信頼性の高いバージョンが利用可能になった時に、すぐに更新してください。 脆弱性を確認するためにSnykCLI などのツールを利用してください。 ソースコード管理システムと統合して自動修正を受け取ることにより、自動的にSnykの脆弱性を修正できます: $ npx snyk test 7.JSON ステートのインジェクション サーバー側でレンダリングされたReactページと一緒にJSONデータを送信するのが一般的です。インジェクション攻撃を回避するために、常に無害な値で、<などの文字をエスケープしてください。 JSONに含まれるHTMLの重要な値については無害な文字を使用して、エスケープしてください: window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace( /</g, '\\u003c')} 8.脆弱な Reactの バージョンの検出 Reactライブラリには過去にいくつかの重大度の高い脆弱性があったため、常にバージョンを最新に保つことをお勧めします。 reactとreact-domの脆弱なバージョンを避けるため、npm outdated を使用して最新バージョンであることを確認してください。 9.セキュリティ Linter の設定 コード内のセキュリティ問題を自動的に検出し、修復のアドバイスを提供するLinterコンフィグレーションとプラグインをインストールします。 ESLint Reactセキュリティコンフィグを使用して、コードベースのセキュリティ問題を検出します。 huskyなどのライブラリを使用してセキュリティ関連のLinterの問題が検出されたときにエラーを起こすpre-commitフックを設定します。 使用しているバージョンに脆弱性が存在する場合、Snykを使用して新しいバージョンに自動的に更新しましょう。 10.危険なライブラリーコードの回避 HTMLをDOMに直接挿入するなどの危険な操作を実行するようなライブラリコードが使用されることがあります。ライブラリコードを手動またはLinterでレビューすることにより、Reactのセキュリティメカニズムの危険な使用法を検出します。 dangerouslySetInnerHTML、innerHTML、未検証のURLまたはその他の危険なパターンを使用しているライブラリーを避けましょう。node_modulesフォルダでSecurity Linterを使用して、ライブラリコード内の危険なパターンを検出します。 長い翻訳記事を、最後まで読んでいただいてありがとうございます! reference: Contents provided by: Jesse Casman, Fumiko Doi, Content Strategists for Snyk, Japan, and Randell Degges, Community Manager for Snyk Global
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】おいでおいでするGitHubリンクボタンを作成する

概要 ホバーすると手招きするGitHubリンクボタンを作成します。 以下のサイトを見ていたときに、いいなと思ってReactに転用してみました。 サイトで紹介しているパッケージも便利なのでおすすめです。 環境 React TypeScript 実装 SVG サイトのHTMLのSVG要素を見ると、手と体でpathが分かれているのがわかります。 これの手の部分をCSSのアニメーションで動かせば良さそうです。 <svg width="80" height="80" viewBox="0 0 250 250" style="fill: #fff; color: #151513; position: absolute; top: 0; border: 0; right: 0" aria-hidden="true" > <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px" class="octo-arm" ></path> <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body" ></path> </svg> アニメーションのタイミング 手が動くタイミングは、マウスカーソルをホバーしたときです。 要素ごとに分解すると下図のようになっていて、白い三角形領域(△)をホバーしたときにアニメーションを実行すれば良さそうです。 コード import React, { useEffect, VFC } from 'react'; import { css, keyframes } from '@emotion/css'; export const GitSVG: VFC = () => { const hoverHandler = () => { const octo_arm = document.getElementById('octo-arm')! as HTMLElement octo_arm.classList.add(styles.octoArm) } useEffect(() => { const octo_arm = document.getElementById('octo-arm')! as HTMLElement const removeStyles = () => { octo_arm.classList.remove(styles.octoArm) } octo_arm.addEventListener('animationend', removeStyles) return () => octo_arm.removeEventListener('animationend', removeStyles) }, []) return ( <div> <svg width="80" height="80" viewBox="0 0 250 250" style={{ fill: '#fff', color: '#151513', position: 'absolute', top: 0, border: 0, right: 0 }} aria-hidden="true"> <g className={styles.group} onMouseEnter={hoverHandler}> <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> <path id="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style={{ transformOrigin: '130px 106px' }}></path> <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor"></path> </g> </svg> </div> ) } const animations = { arm: keyframes` 0% { transform: rotate(0); } 50% { transform: rotate(-30deg); } 100% { transform: rotate(0deg); } ` } const styles = { octoArm: css` animation: ${animations.arm} 0.3s ease-in 0s 3; `, group: css` cursor: pointer; ` } アニメーションは、-30度回転して戻る×3とするようにしています。 まず、<g>タグで、三角形領域、タコの手、体をグループ化します。 これは、三角形領域とタコをホバーしたときだけアニメーションが実行されるようにするためです。 <g className={styles.group} onMouseEnter={hoverHandler}> <path>{/* 三角形領域 */}</path> <path>{/* タコの手 */}</path> <path>{/* タコの体 */}</path> </g> 次に、ホバー時(<g>タグにマウスが入ったとき)にタコの手にアニメーションのスタイルを追加します。 const hoverHandler = () => { const octo_arm = document.getElementById('octo-arm')! as HTMLElement octo_arm.classList.add(styles.octoArm) } ここまでだと、一回目のホバー時にしかアニメーションが実行されないので、アニメーションの実行が終わったらスタイルを削除するようにします。 そうすることで、次のホバーのタイミングで再度タコの手にアニメーションのスタイルが追加されて、アニメーションが実行されるようになります。 useEffect(() => { const octo_arm = document.getElementById('octo-arm')! as HTMLElement const removeStyles = () => octo_arm.classList.remove(styles.octoArm) octo_arm.addEventListener('animationend', removeStyles) return () => octo_arm.removeEventListener('animationend', removeStyles) }, []) まとめ animationendイベントがキモです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【完全版】React / Next.js で Material-UI を使う (w/ TypeScript)

ReactやVue、Angularが普及した現代のフロントエンド開発において、NextやNuxtのようなフレームワーク(パッケージ)は欠かせないと言えるでしょう。単にSSRやSSGだけでなく、さまざまな最適化を任せることができます。一方で、 ボタンやナビゲーションバーなどをスクラッチで実装することなく、既存のコンポーネントを使用したい。 スタイルをコンポーネントファイルごとに管理したい。 などの動機から、スタイルを管理するフレームワークを使用するディベロッパーも少なくありません。そこで、本稿ではReact / Next.jsにフォーカスし、Material-UIを使ってスタイル管理を行うことを目指します。もちろん、実装にはTypeScriptを使用します。 目次 0. 環境 macOS Big Sur 11.5.2 MacBook Pro (13-inch, M1, 2020) Node.js v16.9.1 WebStorm 2021.1 1. 技術スタックの概説 1.1. React Reactは主にFacebook社が開発しているUI構築フレームワークで、コンポーネントの管理や宣言的な記述を可能にします。同様のものにVueやAngularがあります。Google Trendsによると、2020年2月16日以降ReactはVueとAngularより多く検索されています。また、地域別に見ると、アメリカやイギリス、カナダ、ロシアで最も多く検索されています。 1.2. Next.js Next.jsは主にVercel社が開発している、Reactをエンパワーするフレームワークです。サーバーサイドレンダリング(SSR)や静的サイトジェネレーション(SSG)はもちろん、画像の最適化やバンドル、SEOなどさまざまなことを勝手に行ってくれます。 1.3. TypeScript TypeScriptは主にMicrosoft社が開発している、ディベロッパーエクスペリエンスを改善するためのJavaScriptのスーパーセットです。JavaScriptに「型」を付与することで、本来は実行するまで分からないエラーをコンパイル時に(ほとんどはタイプした瞬間に)検出するようにします。モダンなフロントエンド開発にはほとんど欠かせないと言って良いでしょう。 1.4. Material-UI Material-UIはコミュニティが開発している、スタイリングのためのフレームワークです。大きく分けて3つのタスクを追います。 Material DesignベースのReactコンポーネントを提供します。BootstrapのようなCSSフレームワークと同じ役割と考えて差し支えないと思います。 コンポーネントのスタイリングを行います。styled-componentsやCSS Modules、Tailwind CSSとおおよそ同じような役割と考えて差し支えないと思います。 全体のテーマを管理します。例えば、ダークモードへのスイッチを簡単にします。 Material-UIに類するフレームワークのどれを使うべきかは、それぞれ一長一短であり、半ば宗教的な議論に終着してしまいがちですが、Material-UIを使うメリットとして ゼロから実装する必要はなく、実装の手間が省ける。 コンポーネント横断的なスタイル(色など)を管理しやすい。 コンポーネントファイル(.jsxや.tsxファイル)内では、コンテンツとスタイルを分離できる。 などが挙げられます。 1.5. Vercel VercelはVercel社が提供する、Next.jsアプリのデプロイ先の一つです。SSGやCDNへの配置などを一手に引き受けてくれます。デプロイだけでなく、プルリクエストに紐づいたプレビューなどを驚くほど簡単に実現できます。個人開発用には無料で使えます。(ただし、組織ではなく個人のリポジトリに紐づける必要があります。)今回はデプロイについての説明は省略しますが、Next.jsアプリ開発で使いたい技術スタックの一つです。 2. 実装例 2.1. Next.jsプロジェクトを立ち上げる Node.jsがインストールされているかを確認します。 zsh % node -v v16.9.1 TypeScriptをサポートしたnextプロジェクトを立ち上げます。npmであれば zsh % npx create-next-app --ts Yarnであれば zsh % yarn create next-app --typescript でインストールできます。(以下、簡単のためnpmのコマンドのみを記載します。)プロジェクト名など必要事項を適宜記入してください。確認のため、アプリを立ち上げてみましょう。 zsh % cd path/to/project % npm run dev ブラウザで以下のリンクを立ち上げてみましょう。 開発用サーバは Ctrl+C で閉じられます。 ついでにですが、./src ディレクトリを作成し、./pages を ./src 配下に移動させておきましょう。 2.2. Material-UIをインストールする 2.2.1. npmでインストールする npmからMaterial-UIをインストールしましょう。Material-UIは主なものに限ると3つのパッケージを提供しています。 @material-ui/core 最も一般的なパッケージです。各種コンポーネントやスタイリングのための関数などが提供されています。 @material-ui/icons Material Designのアイコンを使用できます。こちらから使用できるアイコンを検索できます。 @material-ui/lab core に移動する前のアルファ版的なコンポーネントが提供されています。 必要となるパッケージを選んでください。本項ではすべてインストールしようと思います。 zsh % npm i -S @material-ui/core @material-ui/icons @material-ui/lab ./package.json を確認すると、"dependencies" に追加されていることが分かります。 2.3. SSRに対応させる このままだとスタイル定義のタイミングにずれが生じ、Warning: Prop `className` did not match. のような注意がなされます。これを解消するために、nextアプリにおける2つの特別なファイル、すなわち、./src/pages/_app.tsx と ./src/pages/_document.tsx を編集します。これらのファイルの概説は以下の通りです。 _app.tsx 各ページに共通する部分を記述するためのものです。 _document.tsx <html> や <body> をカスタマイズするためのものです。 これらを以下のように編集してください。 ./src/pages/_app.tsx import React, { useEffect } from 'react' import type { AppProps } from 'next/app' export default function MyApp({ Component, pageProps }: AppProps) { useEffect(() => { const jssStyles = document.querySelector('#jss-server-side') jssStyles?.parentElement?.removeChild(jssStyles) }, []) return ( <Component {...pageProps} /> ) } ./src/pages/_document.tsx import React from 'react' import Document, { Head, Html, Main, NextScript } from 'next/document' import { ServerStyleSheets } from '@material-ui/core/styles' class MyDocument extends Document { render() { return ( <Html> <Head> </Head> <body> <Main/> <NextScript/> </body> </Html> ) } } MyDocument.getInitialProps = async (ctx) => { const sheets = new ServerStyleSheets() const originalRenderPage = ctx.renderPage ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheets.collect(<App {...props} />), }) const initialProps = await Document.getInitialProps(ctx) return { ...initialProps, styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], } } export default MyDocument 2.4. テスト 適当にボタンを配置して、Material-UIが適切に使えるかどうかをテストしてみましょう。その前にまず、今回はCSS Modulesを使用しないので、デフォルトの ./styles ディレクトリを削除します。次に ./src/pages/index.tsx を次のように書き換えます。 ./src/pages/index.tsx import React from 'react' import Button from '@material-ui/core/Button' export default function Home() { return ( <Button variant="contained">Success!</Button> ) } 単にSuccess!という表示のボタンを配置しました。最後に、これを確認するために npm run dev コマンドで開発用サーバを起動し、ブラウザで http://localhost:3000 にアクセスします。その結果、ボタンが表示されるとOKです。念のため、開発者用ツールからコンソールを確認し、Warningが出ていないこともチェックしておいてください。 3. 運用例 ここまでで、Next.js上でMaterial-UIを使用する準備ができました。本章では、実際にコーディングする際に、どのようにMaterial-UIを使うべきかの一例を示します。 3.1. React Componentについて まずいきなりですが、コンポーネントファイルの一例を見ていきましょう。 import React from 'react' import withStyles, { WithStyles, StyleRules } from '@material-ui/core/styles/withStyles' import { createStyles, Theme } from '@material-ui/core/styles' import { Typography } from '@material-ui/core' interface TitleProps extends WithStyles<typeof styles> {} const styles = (theme: Theme): StyleRules => createStyles({ root: { '& .MuiTypography-subtitle1': { fontSize: '1.5em', fontWeight: 300 }, '& .MuiTypography-h5': { fontWeight: 200, }, }, wrapper: { backgroundImage: 'linear-gradient(to right, #000428BB, #004e92BB), url(\'/path/to/image\')', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', backgroundSize: 'cover', position: 'relative', margin: 0, height: '95vh', minHeight: '450px', display: 'flex', justifyContent: 'center', alignItems: 'center', }, title: { padding: '0 20%', textAlign: 'center', color: theme.palette.common.white, [theme.breakpoints.down('xs')]: { padding: '0 5%', }, }, }) const TitleSection: React.FC<TitleProps> = ({classes}) => { return ( <section className={classes.root}> <div className={classes.wrapper}> <div className={classes.title}> <Typography variant="h2"> <strong>Title</strong> </Typography> </div> </div> </section> ) } export default withStyles(styles, { withTheme: true })(TitleSection) これをもとに詳しく見ていきましょう。 3.1.1. React Componentの型 React Componentの定義式では通常通り、React.FC<FooBarProps> を使用します。FooBarProps は WithStyles<typeof styles> を拡張したインタフェースで、プロパティを自由に定義できます。(名前はパスカルケースである限り任意です。)最終的には withStyles でこのReact Componentをラップしたものをエクスポートします。こうすることで、次項3.1.2で説明するスタイリングが有効となります。 3.1.2. React Componentのスタイリング スタイリングは基本的に styles が担います。styles は Theme 型(3.2.3で詳しく説明)を引数にとり、StyleRules 型を返す関数です。上の例では createStyles 関数をそのまま返しています。createStyles 関数はCSSライクなオブジェクトを引数にとります。つまり、この関数の引数に各種スタイルを定義していきます。このスタイリングは、コンポーネント内で閉じられており、子コンポーネント以外の別のコンポーネントのスタイルに影響することはありません。 プロパティ名はケバブケースではなく、キャメルケースで記述してください。また、プロパティ名を '& .MuiFooBar' などとすることでオリジナルのMaterial-UIコンポーネントを上書きすることができます。さらには、[theme.breakpoints.down('xs')] などとすることでブレイクポイントごとのスタイルの変更、すなわち、レスポンシブデザインを可能になります。 定義するだけではスタイルは適用されません。具体的には以下の2つが必要です。 前項で説明した通り、withStyles 関数でReact Componentをラップする必要があります。 スタイルを適用したい要素に className を通じてスタイルを与えます。このとき、createStyles に渡したオブジェクトがReact Componentの props.classes に格納されています。 3.1.3. スタイルに props の内容を反映させたい場合 makeStyles 関数を用います。React Componentの外で、 const useStyles = makeStyles({ foo: (props: FooProps) => ({ bar: 'baz', }), }) のように useStyles フックを定義します。この中では props を使用することができ、スタイルに props の内容を反映できるようになります。あとはReact Component内で const SomeComponent: React.FC<SomeProps> = ({classes, ...props}) => { const classesWithProps = useStyles(props) return ( ... ) } のように呼び出せば、前項3.1.2.の classes と同じように、classesWithProps を使用できます。 3.2. 全体のスタイルとテーマについて 3.2.1. テーマ管理 色やフォントファミリー、ブレイクポイントやスペースなどのテーマを管理することができます。これを用いるとダークモードなどを容易に実装できます。./src/styles に次のようなファイルを追加します。 ./src/styles/theme.ts import { createTheme, responsiveFontSizes } from '@material-ui/core' /* COLOR */ const black = '#343a40' const white = '#F4F4F4' const background = '#F4F4F4' /* BREAKPOINTS */ const xl = 1920 const lg = 1280 const md = 960 const sm = 600 const xs = 0 /* SPACING */ const spacing = 8 /* FONT FAMILY */ const fontFamily = [ 'Poppins', '"Helvetica Neue"', 'Arial', 'Noto Sans JP', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', ] const theme = createTheme({ palette: { primary: { main: '#69D2E7', light: '#B1E2EC', dark: '#51A4B5', }, secondary: { main: '#FA6900', light: '#FDAD0D', dark: '#E3460B', }, common: { black: '#343a40', white: '#F4F4F4', }, info: { main: '#005CAF', light: '#0087FC', dark: '#00437D', }, success: { main: '#00AA90', light: '#00F7D2', dark: '#007866', }, warning: { main: '#FFB11B', light: '#FFDB0F', dark: '#E8820E', }, error: { main: '#CB1B45', light: '#D55B78', dark: '#991433', }, tonalOffset: 0.2, background: { default: background, }, }, spacing: spacing, breakpoints: { values: { xl, lg, md, sm, xs } }, typography: { fontFamily: fontFamily.join(','), }, overrides: { MuiToolbar: { root: { justifyContent: 'space-between' } }, }, }) export default responsiveFontSizes(theme) メインカラー、サブカラーの定義などの他に、Material-UIコンポーネントのグローバルな上書き(カスタマイズ)もできます。 これを使用するには _app.tsx 内で MuiThemeProvider を用いてテーマを提供する必要があります。 渡すテーマを入れ替えれば、テーマが変更できます。 各コンポーネントファイルでコンポーネントのラッピングの際に withStyles(styles, { withTheme: true }) のようにオプション引数を与える必要があります。 3.2.2. グローバルスタイル グローバルに適用したいスタイルにはスタイリングの際に @global プロパティを与えます。新たに GlobalStyles というコンポーネントを作り、それを _app.tsx で呼び出すという方法を私は採用することが多いです。 ./src/styles/GlobalStyles.tsx import { withStyles } from "@material-ui/core"; import { Theme } from "@material-ui/core/styles"; type Styles = (theme: Theme) => object const styles: Styles = theme => ({ '@global': { 'html': { padding: 0, margin: 0, scrollBehavior: 'smooth', }, 'body': { padding: 0, margin: 0, }, 'a': { color: 'inherit', textDecoration: 'none', }, '*': { boxSizing: 'border-box' }, '.container': { width: "100%", height: "100%", paddingRight: theme.spacing(4), paddingLeft: theme.spacing(4), margin: "auto", [theme.breakpoints.up("sm")]: { maxWidth: 540 }, [theme.breakpoints.up("md")]: { maxWidth: 720 }, [theme.breakpoints.up("lg")]: { maxWidth: 1170 } }, } }) function GlobalStyles() { return null; } export default withStyles(styles, { withTheme: true })(GlobalStyles); ./src/pages/_app.tsx import React, { useEffect } from 'react' import Head from 'next/head' import { AppProps } from 'next/app' import theme from '../styles/theme' import CssBaseline from '@material-ui/core/CssBaseline' import GlobalStyles from '../styles/GlobalStyles' import { MuiThemeProvider } from '@material-ui/core/styles' export default function PutchApp(props: AppProps) { useEffect(() => { const jssStyles = document.querySelector('#jss-server-side') jssStyles?.parentElement?.removeChild(jssStyles) }, []) const {Component, pageProps} = props return ( <> <Head> <title>Title</title> <meta charSet="utf-8"/> <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width"/> </Head> <MuiThemeProvider theme={theme}> <CssBaseline/> <GlobalStyles/> <Component {...pageProps} /> </MuiThemeProvider> </> ) } <CssBaseLine/> はCSSをリセットするものです。 4. 最後に ここまでNext.jsアプリでMaterial-UIを使う方法を説明してきました。Material-UIは単にMaterial Designのコンポーネントを提供するだけでなく、テーマやスタイル全体を管理してくれる非常に強力なものです。ぜひ一度、使用を検討してみてはいかがでしょうか。この記事良かったなと思った方は、ぜひLGTMをお願いします。(記事投稿のモチベーションとなります。) 5. 参考 5.1. Next.jsについて 公式ドキュメント 5.2. Material-UIについて 公式ドキュメント MUI Treasury
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSXの書き方

JSXって? Reactが表示される流れは、Reactの表示される仕組みについて〜DOMとは?〜にも書いた通り、 ①DOMの要素を取得 ②仮想エレメントを作成 ③レンダリング です。 この流れを毎回書くのは大変なので、同じことをやるとしてももっと簡単な書き方があります。 それが、JSXです。 JSXについて JSXはJavascriptを拡張した書き方で、HTMLのタグをJavascriptのスクリプトに直接記述する仕組み。JSXはJavascriptなので、もちろんJavascriptを埋め込むこともできます。 JSXは②のエレメントオブジェクトを生成する部分を担当します。 JSXが作るのは1つのエレメントで、複数のエレメントを作ることはできません。 <div>...</div>で囲んであげて、一つの要素として扱います。 大事 JSXの書くときの決まり これからJSXの書き方のルールについて、まとめていきます。 変数をうめこむ let text = "テキスト" let link = "https://qiita.com/" let el = ( <div> <h4>{text}</h4> <h5><a href={link}>Qiita</a></h5> </div> ) タグの中身でも、属性にも{}で指定できる。 変数として認識されなくなるので、クォーテーションで囲んだりしないように。 クラスを設定 <div className="〇〇"> クラス属性を設定するときはこのように記述します。 スタイルを設定 2つのやり方があります。 ①タグに直接styleを書き込む {{}}で囲む let el = ( <div> <h4 style={{color:"red",fontSize:20}}>message</h4> </div> ) ②変数にまとめて定義してstyle属性に設定する const msg_style = { fontSize:20, color:"red" } let el = ( <div> <h4 style={msg_style}>message</h4> </div> ) 注意 ハイフン(-)は使わない font-size、margin-topなど間にハイフンが入るプロパティはハイフンをなくして、その後の文字を大文字にして対応します。ハイフンを使うとエラーになっちゃいます! 数字を扱うとき 20pxと20では扱いがちがいます。 20pxと単位が付く場合は""とクォーテーションで囲まないとエラーになるので、気をつけましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【VSCode】.tsxファイルにユーザースニペットが登録されない際の解決方法

使用環境 ・MacOS BigSur(11.5.2) ・VScode(1.59.1) ・Node.js (16.1.1) ・React(17.0.2) ・Next.js(11.1.0) はじめに エンジニアなら誰しも、開発効率化のためにVS Codeのユーザースニペット(入力補完ツール)を登録したいですよね。 自分もそう思って記事を参考に手を出してみたのですが、登録したはずのスニペットがtsxファイルになぜか現れてくれません・・・涙 そこで自分が使ってるVS Codeの拡張機能が原因かもしれないといろいろ探っていましたがいい方法が見つからず。。 しばらく放置してまたやってみようと思った今日、とってもシンプルな方法を見つけたのでそれを紹介します。(前提としてスニペットの登録方法は調べておいてください。) 参考までに↓ VsCodeのスニペットのススメ 結論/解決策 もったいぶらずにやり方を説明します。 ReactとTypeScriptを使用している.tsxファイルにスニペットを適用させるには、スニペット登録ようのJSONファイル内のscopeにtypescriptreact を記述します。以上。 "scope": "javascript, typescript, typescriptreact" 例としてclassName=''をスニペットとして登録する場合の記述方法をあげておきます { "className": { "scope": "javascript,typescript,typescriptreact", //ここに記述 "prefix": ["cla", "class"], "body": ["className=''"], "description": "className input" } } tsxファイルは別物なのですね。おそらくjsxファイルも同様にjavascriptreactとか必要になるのかもしれません。 ちなみに設定を反映するのにVS Codeの再起動とかは必要ありません。 これで入力されることを確認してみてください。 参考資料 StackOverflow Visual Studio Code user snippets not working
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基礎から学ぶReact/React Hooks学習メモ 5-3 useCallback React.memo useMemo

React Hooksを基礎から理解する 5-3 useCallback React.memo useMemo 参考 - 基礎から学ぶReact/React Hooks useCallback()、useMemo()は、パフォーマンスチューニングのためのフック コンポーネントの不要な再レンダリングを防ぐ Reactコンポーネントでは、state、propsが更新されるたびに再レンダリングが行われる useCallback()、React.memo()、useMemo()は、前回の差分がないことが事前に分かっている場合、再レンダリングをスキップできる useCallback()は、React.memo()と組み合わせて使用する メモ化とは 同じ結果を返す処理について、初回のみ処理を実行して記録 2回目以降は、保持していた結果を再利用する React.memoとは コンポーネントのレンダリング結果をメモ化するReact API 親コンポーネントから渡されるpropsについて、変更差分をチェックして、コンポーネントが返した描画結果を記録してメモ化し、差分があった倍の三再レンダリングする propsの前後を比較し、等価かどうかチェック 等価であれば、メモ化したコンポーネントを利用するので、再レンダリングをスキップ 等価でなければ、再レンダリングする React.memo()でラップしていても、コンポーネント内部でuseState()やuseContext()を使用している場合、その変化に応じた再レンダリングが発生する useMemo()の基本構文 React.memo(親コンポーネントからpropsを受け取る子コンポーネント) React.memo()の利用例 React.memo()未使用時(AボタンとBボタンを1回ずつ押す) React.memo()使用時(AボタンとBボタンを1回ずつ押す) import React, { useState } from "react"; import "./styles.css"; // React.memo()で関数全体を括る。propsが同じときはメモ化したコンポーネントを使用する const CountResult = React.memo(({ text, countState }) => { console.log(`${text}ボタンがクリックされました!`); return ( <p> {text}: {countState} </p> ); }); const Counter = () => { const [countStateA, setCountStateA] = useState(0); const [countStateB, setCountStateB] = useState(0); const countIncrementA = () => { setCountStateA((prevCount) => prevCount + 1); }; const countIncrementB = () => { setCountStateB((prevCount) => prevCount + 1); }; return ( <> <CountResult text="Aボタン" countState={countStateA} /> <CountResult text="Bボタン" countState={countStateB} /> <button onClick={countIncrementA}>Aボタン</button> <button onClick={countIncrementB}>Bボタン</button> </> ); }; export default function App() { return <Counter />; } useCallbackとは React.memoを使っても親コンポーネントから、「コールバック関数」をpropsとして受け取った場合、子コンポーネントは再レンダリングされてしまう。 関数自体のメモ化に利用できるのが、メモ化されたコールバック関数を返すuseCallback() インポート import React, { useCallback } from "react"; useCallback()の基本構文 useCallback(コールバック関数, [コールバック関数が依存している要素の配列]); a,bどちらも変化しなければ、メモ化した関数を再利用する // メモ化したいsampleCallbackFunc関数を宣言 const sampleCallbackFunc = () => doSomething(a, b); // useCallbackでメモ化したい、sampleCallbackFunc関数をラップ const memorizedSampleCallbackFunc = useCallback(sampleCallbackFunc, [a, b]); 配列を空にすると、初回レンダリング時に生成した関数をずっと使い続ける const memorizedSampleCallbackFunc = useCallback(sampleCallbackFunc, []); useCallbackの利用例 useCallback()は、React.memo()と組み合わせて使用する useCallback()で使用する配列の項目は、関数内で使われてないといけない。逆に使われていないときはその項目はいらない。 setCountStateA(countStateA + 1) を、setCountStateA((prevCount) => prevCount + 1) とすれば、[]で良い Aボタン、Bボタンを1回ずつ押したとき、異なるボタンのログが出ていない。 import React, { useState, useCallback } from "react"; import "./styles.css"; // useCallback()は、React.memo()と組み合わせて使用する const Button = React.memo(({ counterState, buttonValue }) => { console.log(`${buttonValue}がクリックされました!`); return <button onClick={counterState}>{buttonValue}</button>; }); const Counter = () => { const [countStateA, setCountStateA] = useState(0); const [countStateB, setCountStateB] = useState(0); // 関数をメモ化する const countIncrementA = useCallback(() => { setCountStateA(countStateA + 1); }, [countStateA]); // 関数をメモ化する const countIncrementB = useCallback(() => { setCountStateB(countStateB + 1); }, [countStateB]); return ( <> <p>Aボタン: {countStateA}</p> <p>Bボタン: {countStateB}</p> <Button counterState={countIncrementA} buttonValue="Aボタン" /> <Button counterState={countIncrementB} buttonValue="Bボタン" /> </> ); }; export default function App() { return <Counter />; } useMemo 関数の結果を保持する 何度計算しても結果が同じ場合の、「関数による計算の結果」をメモ化し、そこから値を取得する useCallback() → 関数自体をメモ化する useMemo() → 関数の結果を保持する インポート import React, { useMemo } from "react"; useMemo()の基本構文 useMemo(() => 値を計算する関数の呼び出し, [値の計算に必要な要素の配列]); a,b のどちらかに変更があった場合、関数を再実行する。 useMemo(() => doSomething(a, b), [a, b]); 第二引数を空とした場合、初回の一度のみ実行され、それ以降はキャッシュから値を取得する useMemo(() => doSomething(a, b), []); useMemoの利用例 Bの計算がとても重いプログラム useMemo()を使用したとき(A,Bボタンを1回ずつクリック。重い処理が1度実行) useMemo()を使用しないとき(A,Bボタンを1回ずつクリック。重い処理が2度実行) import React, { useState, useMemo } from "react"; import "./styles.css"; const square = (param) => { const testData = [...Array(1000).keys()]; testData.forEach(() => { console.log( `「計算: B + 1」がボタンクリックされ、square関数実行、ループ処理を${testData.length}回実行中...` ); }); return param * param; }; const Counter = () => { const [countStateA, setCountStateA] = useState(0); const [countStateB, setCountStateB] = useState(0); // squreがとても重い処理なので、countStateBが変化した時だけ実行してほしい const squareArea = useMemo(() => square(countStateB), [countStateB]); // ラップしないとき // const squareArea = square(countStateB); const countIncrementA = () => { setCountStateA((prevCount) => prevCount + 1); console.log("計算: A + 1ボタンがクリックされました!"); }; const countIncrementB = () => { setCountStateB((prevCount) => prevCount + 1); console.log("計算: B + 1ボタンがクリックされました!"); }; return ( <> <p> 計算結果A: {countStateA} <button onClick={countIncrementA}>計算: A + 1</button> </p> <p> 計算結果B: {countStateB} <button onClick={countIncrementB}>計算: B + 1</button> </p> <p>【正方形の面積】</p> <p>計算結果B × 計算結果B = {squareArea}</p> </> ); }; export default function App() { return <Counter />; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む