- 投稿日:2021-01-25T23:05:07+09:00
[React Native] サーバーやDBからのデータ取得
0. 目的
Hooksを用いてサーバーやDBからデータを取得
1. 環境
- React : 16.8.6
- React Native : 0.63.4
2. ソースコード
GetData.jsimport React from 'react'; import {FlatList, Text, View} from 'react-native'; import {useEffect, useState} from 'react'; function App() { const [response, setResponse] = useState([]); useEffect(() => { // データ取得(DBの場合もここでread処理を行う) fetch('https://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060') // [1] .then((response) => response.json()) .then((responseJson) => { setResponse(responseJson['results']); }); }, []); if (!response.length) { // データ取得前 return <Text>Loading...</Text>; } else if (response.length) { // データ取得後 return ( <FlatList data={response} renderItem={({item}) => ( <View> <Text>〒{item.zipcode}</Text> <Text> {item.address1} {item.address2} {item.address3} </Text> </View> )} keyExtractor={(item) => item.zipcode} /> ); } } export default App;3. 結果
「Loading」が表示された後、上の画像のように描画される.
4. 解説
処理の流れ
1. 「Loading」を表示
2. 「useEffect」内の処理を実行
3. 「住所」を表示useEffectはレンダリング後に実行されるため、responseの有無により描画内容を変更する.
また、setResponseによりresponseの値が変更された際に再レンダリングされるため、データ取得後には再度if文が実行され、データ内容が表示される.5. 参考
[1]郵便番号検索API
- 投稿日:2021-01-25T21:13:02+09:00
ReactのConcurrent Modeでインターフェースプレビューを実装してみた
はじめに
都内の企業でモバイルアプリ開発を行っているすずきと申します。早いもので、エンジニアになって半年経ちました。
最近、開発アプリのある画面でAPIから最新のデータを都度取得するような実装を行いました。データ取得中の画面として以下のようなインジケータを追加したのですが、テストで何度か挙動を確認したところ、画面遷移のたびに表示されて軽くストレスを感じました。
UXの質が落ちてるな...と感じたので、YouTubeアプリみたいな感じで画面遷移中も前の画面を残す(インターフェースプレビュー)ようにできないかなぁと調べたのですが、Reactの性質上、そのような実装は難しいということがわかりました。
Reactにも弱点はあるんだな...と思いながらReactの公式ドキュメントを眺めていたところ、Concurrent Modeというページをふとみつけました。
Concurrent Mode(並列モード)
公式Docsを読みすすめてみると、どうやらConcurrent Modeを使うとReactで中断可能なレンダリングが可能になるとのことでした。
更新のレンダーを一度始めたら中断することはできませんよ!というのが、ReactのようなUIライブラリの動作の基本だったのですが、Concurrent Modeだと中断できるようになったということですね。
前の画面を描画させながら、更新後の画面のレンダーをメモリ内で継続し、データが到着するごとに再レンダーを行うことができるので、この仕組みを利用することで、前の画面を表示したまま、インジケータをインライン表示するよう React に伝えることができます。新しい画面の準備が完了したら、React が自動的にそちらを表示します。
SuspenseとuseTransition
Concurrent Modeでインターフェースプレビューを実現するためにキーワードとなるのが、SuspenseとuseTransitionです。
Suspense
SuspenseはReact 16.6で追加されたコンポーネントです。コンポーネントが読みだそうとしているデータが準備できていないとき、宣言的にロード中の状態を指定することができるようになります。
Suspenseを使用する目的の1つに、RESTやGraphQLのデータ取得ライブラリとReactとの深い連携があります。データ取得ライブラリがSuspenseをサポートすることで、コンポーネントからそれを自然に扱えるようになります。Suspenseは連携するライブラリの種類を選びませんが、Facebookで本番環境として利用しているライブラリはRelayのみだそうです(Apolloでは今のところテストされていないようです...)。
useTransition
新しい画面に遷移する前に新しいコンテンツがロードされるのを待機するために使用するのがReact Hooksの1つであるuseTransitionです。
const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });startTransitionでどのstateの更新を遅延させたいのかをReactに伝え、isPendingでトランジションが進行中かどうかを伝えます。
timeoutMsプロパティでトランジションが終了するまでどれだけ待てるかを指定します。例えば、{timeoutMs: 3000}で、「新しい画面がロードされるのに3秒以上かかったら大きなスピナーを表示せよ、それまでは前の画面を表示しつづけて構わない」ということを伝えています。
データ取得アプローチの比較
インターフェースプレビューの実装が今回の目的なのですが、勉強のためにReactのデータ取得の各種アプローチについて検証してみることにしました。公式Docsの例をベースに、Material-UIの勉強も兼ねて新しく実装しました。
プロフィール部分とグラフ部分がどのような順番で表示されるのかどうかを検証します。ソースはGitHubにあります。
1. Fetch-on-Render(Suspense不使用)
Reactアプリケーションのデータ取得は、classコンポーネントであればライフサイクルメソッド(component~)、functionalコンポーネントであれば副作用(effect)を用いるのが一般的です。
Fetch-on-Renderというのは、このようなレンダー後にデータ取得が始まるようなアプローチのことをいいます。
FetchOnRender.jsx
export const FetchOnRender = () => { const [userData, setUserData] = useState(null); useEffect(() => { // ①, ③ fetchUserData().then((u) => setUserData(u)); }, []); if (userData === null) { // ② return ( <Typography variant="p" component="h7"> Loading profile... </Typography> ); } return ( <div> <DetailsContent company={userData.data.company} name={userData.data.name} image={userData.data.image} /> <ProfileChart /> </div> ); }; const ProfileChart = () => { const [chartData, setChartData] = useState(null); useEffect(() => { // ④, ⑥ fetchChartData().then((p) => setChartData(p)); }, []); if (chartData === null) { // ⑤ return ( <Typography variant="p" component="h7"> Loading chart... </Typography> ); } return <ChartContent data={chartData} />; };コードの実行順は以下のようになります。
①プロフィールデータ取得開始->②待機->③プロフィールデータ取得完了->④グラフデータ取得開始->⑤待機->⑥グラフデータ取得完了
グラフデータの取得はプロフィールデータを取得するまで開始されません。これをウォーターフォールといいます。
2. Fetch-Then-Render(Suspense未使用)
Fetch-Then-Renderは、Fecth-on−Renderで起こったウォーターフォールを防止するために、データ取得を1つの副作用で行うアプローチのことをいいます。
FetchThenRender.jsx
const promise = fetchProfileData(); export const FetchThenRender = () => { const [userData, setUserData] = useState(null); const [chartData, setChartData] = useState(null); useEffect(() => { // ①, ②, ③, ④, ⑤ promise.then((data) => { setUserData(data.userData); setChartData(data.chartData); }); }, []); if (userData === null) { return ( <Typography variant="p" component="h7"> Loading profile... </Typography> ); } return ( <div> <DetailsContent company={userData.data.company} name={userData.data.name} image={userData.data.image} /> <ProfileChart chartData={chartData} /> </div> ); }; const ProfileChart = ({ chartData }) => { if (chartData === null) { return ( <Typography variant="p" component="h7"> Loading chart... </Typography> ); } return <ChartContent data={chartData} />; };コードの実行順は以下のようになります。
①プロフィールデータ取得開始->②グラフデータ取得開始->③待機->④プロフィールデータ取得完了->⑤グラフデータ取得完了
Fetch-on-Renderで発生したウォーターフォールは解決されましたが、ここでも別のウォーターフォールが発生しています。fetchProfileData内部のPromise.all()ですべてのデータが到着するまで待機しているので、グラフデータがロードされるまでプロフィール部分をレンダーすることができません。
noSuspenseApi.js
export const fetchProfileData = () => { return Promise.all([fetchUserData(), fetchChartData()]).then( ([userData, chartData]) => { return { userData, chartData }; } ); };3. Render-as-You-Fetch(Suspense使用)
Fetch-on-RenderやFetch-then-RenderのようなSuspenseを使用しないアプローチでは、データ取得完了->レンダー開始という順番でしたが、Suspenseを使うと、このステップが入れ替わり、レンダー開始->データ取得完了という順番になります。
RenderAsYouFetch.jsx
const resource = fetchProfileData(); export const RenderAsYouFetch = () => { return <ProfilePage />; }; const ProfilePage = () => { return ( <Suspense fallback={ <Typography variant="p" component="h7"> Loading profile... </Typography> } > <ProfileDetails /> <Suspense fallback={ <Typography variant="p" component="h7"> Loading chart... </Typography> } > <ProfileChart /> </Suspense> </Suspense> ); }; const ProfileDetails = () => { const userData = resource.userData.read(); return ( <DetailsContent company={userData.data.company} name={userData.data.name} image={userData.data.image} /> ); }; const ProfileChart = () => { const data = resource.chartData.read(); return <ChartContent data={data} />; };以上のコードでは、レンダー時点でfetchProfileData()をつかってリクエストがスタートしています。今回の検証ではフェイクのAPI実装を行っていますが、実際はRelayやApolloのようなデータ取得ライブラリのSuspense連携機能を使います(上述していますが、連携機能がテストされているのは今のところRelayのみです)。
RenderAsYouFetchのレンダーを試みます。子要素として、ProfileDetailsとProfileChartが返ります。
ProfileDetailsのレンダーを試みます。内部でresource.userData.read()が呼び出されます。データはまだ取得されていないので、このコンポーネントはSuspendし、ツリーの他のコンポーネントのレンダーを試みます。
ProfileChartのレンダーを試みます。内部でresource.chartData.read()が呼び出されます。こちらのデータもまだ取得されていないので、このコンポーネントはSuspendします。Reactはこのコンポーネントも飛ばして、ツリーの他のコンポーネントのレンダーを試みます。
レンダーを試みる他のコンポーネントは残っていないので、ProfileDetails直上のSuspendのfallbackを表示します。
fallback={ <Typography variant="p" component="h7"> Loading chart... </Typography> }このように、データが到着するごとに再レンダーを試み、そのたびに深いところまで表示できるようになります。Fetch-Then-Renderでも存在していたウォーターフォールの問題がありません。
Fetch-Then-Renderのようにプロフィールとグラフを同時に出現させたい場合は、ProfileChartをラップしているSuspenseを取り除けば可能です。
<Suspense fallback={ <Typography variant="p" component="h7"> Loading profile... </Typography> } > <ProfileDetails /> <ProfileChart /> </Suspense>4. Race Condition
Suspenseによるインターフェースプレビューの実装の前に、useEffectやcomponentDidMountでデータ取得を行った場合に引き起こされるRace condition(競合状態)についても検証しました。
ContentionalStateコンポーネントをつくり、ProfilePageと複数のプロフィールを切り替えるボタンを配置しました。
const getNextId = (id) => { return id === 3 ? 0 : id + 1; }; export const ContentionState = () => { const [id, setId] = useState(0); return ( <div> <Button variant="outlined" color="primary" onClick={() => setId(getNextId(id))} > 次へ </Button> <ProfilePage id={id} /> </div> ); };ProfilePageとProfileChartの両方にuseEffectがあり、idが変わるたびにそれぞれのデータを取得します。"次へ"ボタンを素早く押すと、プロフィールを別の ID に切り替えた後に以前のプロフィールのリクエストが返ってくることがあります。
React コンポーネントにはライフサイクルがありますが、非同期なリクエストにも同様に、発行したときに始まり、レスポンスを得た時に終わるというライフサイクルをもっています。このような複数のプロセスが互いに影響しあうことで、競合状態が起こります。
ContentionalState.jsx
const getNextId = (id) => { return id === 3 ? 0 : id + 1; }; export const ContentionState = () => { const [id, setId] = useState(0); return ( <div> <Button variant="outlined" color="primary" onClick={() => setId(getNextId(id))} > 次へ </Button> <ProfilePage id={id} /> </div> ); }; const ProfilePage = ({ id }) => { const [userData, setUserData] = useState(null); useEffect(() => { fetchUserData(id).then((u) => setUserData(u)); }, [id]); if (userData === null) { return ( <Typography variant="p" component="h7" style={{ marginLeft: 10 }}> Loading profile... </Typography> ); } return ( <div> <DetailsContent company={userData.data.company} name={userData.data.name} image={userData.data.image} /> <ProfileChart id={id} /> </div> ); }; const ProfileChart = ({ id }) => { const [chartData, setChartData] = useState(null); useEffect(() => { fetchChartData(id).then((p) => setChartData(p)); }, [id]); if (chartData === null) { return ( <Typography variant="p" component="h7"> Loading chart... </Typography> ); } return <ChartContent data={chartData} />; };5. Concurrent Mode
ここからインターフェースプレビューを実装するのですが、まずはRace Conditionを解決する必要があります。
Render-as-You-Fetchの実装ではリソースをトップレベルの変数として定義していましたが、今回は複数リソースを扱うため、ConcurrentModeコンポーネント内で定義します。
const initialResource = fetchProfileData(0); export const ConcurrentMode = () => { const [resource, setResource] = useState(initialResource);"次へ"ボタンのクリックハンドラで、プロフィールを切り替えるためのstateを設定し、さらにこのstateの更新をuseTransitionのstartTransitionでラップします。これにより、stateの更新で望ましくないローディング中の状態の表示が起きてしまった場合に、Reactがstate更新を遅延させることができます。
<Button variant="outlined" color="primary" disabled={isPending} onClick={() => { startTransition(() => { const nextUserId = getNextId(resource.userId); setResource(fetchProfileData(nextUserId)); }); }} > 次へ </Button>さらにuseTransitionのisPendingを使用することで、トランジションの完了を待機しているかどうかを確認することができ、これでようやく前の画面と"Loading..."を同時に表示するというインターフェースプレビュー の状態を実現することができました。
const [startTransition, isPending] = useTransition({ timeoutMs: 3000, });ConcurrentMode.jsx
const getNextId = (id) => { return id === 3 ? 0 : id + 1; }; const initialResource = fetchProfileData(0); export const ConcurrentMode = () => { const [resource, setResource] = useState(initialResource); const [startTransition, isPending] = useTransition({ timeoutMs: 3000, }); return ( <div> <Button variant="outlined" color="primary" disabled={isPending} onClick={() => { startTransition(() => { const nextUserId = getNextId(resource.userId); setResource(fetchProfileData(nextUserId)); }); }} > 次へ </Button> {isPending ? ' Loading...' : null} <ProfilePage resource={resource} /> </div> ); }; const ProfilePage = ({ resource }) => { return ( <Suspense fallback={ <Typography variant="p" component="h7" style={{ marginLeft: 10 }}> Loading profile... </Typography> } > <ProfileDetails resource={resource} /> <Suspense fallback={ <Typography variant="p" component="h7"> Loading chart... </Typography> } > <ProfileChart resource={resource} /> </Suspense> </Suspense> ); }; const ProfileDetails = ({ resource }) => { const userData = resource.userData.read(); return ( <DetailsContent company={userData.data.company} name={userData.data.name} image={userData.data.image} /> ); }; const ProfileChart = ({ resource }) => { const data = resource.chartData.read(); return <ChartContent data={data} />; };おわりに
公式Docsの説明が詳しかったこともあり、自分で実装しながらReactのレンダリングへの理解を深めることができました。Concurrent Modeは実験的機能であり、React Nativeにもまだ対応していません。アプリへの導入はまだ先になるかもしれませんが、正式リリースが今から楽しみです。
- 投稿日:2021-01-25T18:58:18+09:00
Next.jsでネストした動的なルーティングの書き方
Next.jsのドキュメント見たけど、自分のコードに落とし込むのが難しいよって人には参考になると思います。
バージョン
- MacOS Catalina 10.15.7
- Next.js 10.0.5
今回やりたいこと
以下のような感じで1や10など動的な場合のディレクトリ構成をお伝えできればと思います。
next/linkを使っての説明はなく、あくまでもディレクトリ構成に絞って書いていきます。
- ユーザー一覧(/users)
- ユーザー詳細(/users/1)
- 投稿一覧(/users/1/posts)
- 投稿詳細(/users/1/posts/10)
先に最終的なディレクトリ構成
pages ├── index.tsx └── users ├── [userId] │ ├── index.tsx │ └── posts │ ├── [postId].tsx │ └── index.tsx └── index.tsxやってみる
ユーザー一覧ページの作成
% mkdir src/pages/users && touch src/pages/users/index.tsx/pages/users/index.tsximport React from 'react'; const index: React.FC = () => { return <h1>/users</h1>; }; export default index;ユーザー詳細ページの作成
% mkdir src/pages/users/\[userId] && touch src/pages/users/\[userId]/index.tsx/pages/users/[userId]/index.tsximport React from 'react'; import { useRouter } from 'next/router'; const index: React.FC = () => { const router = useRouter(); const { userId } = router.query; console.log({userId}); return <h2>/users/: {userId}</h2>; }; export default index;/users/1の場合
/users/100の場合
{userId: ${param}}というqueryが渡ってきてますね!
投稿一覧ページの作成
% mkdir src/pages/users/\[userId]/posts && touch src/pages/users/\[userId]/posts/index.tsx/src/pages/users/\[userId]/posts/index.tsximport React from 'react'; import { useRouter } from 'next/router'; const index: React.FC = () => { const router = useRouter(); const { userId } = router.query; console.log({userId}); return <h1>/users/{userId}/posts</h1>; }; export default index;ちゃんと{userId: "1"}をpostsでも持てていますね!
投稿詳細ページの作成
% mkdir src/pages/users/\[userId]/posts && touch src/pages/users/\[userId]/posts/[postId].tsx/src/pages/users/\[userId]/posts/[postId].tsximport React from 'react'; import { useRouter } from 'next/router'; const index: React.FC = () => { const router = useRouter(); const { userId, postId } = router.query; console.log({userId, postId}); return <h1>/users/{userId}/rooms/: {postId}</h1>; }; export default index;{userId: "1", postId: "1"}が取得できているので良さそうですね!
注意点
const {userId} = router.query;
やconst {postId} = router.query
などの変数名はディレクトリ名やファイル名と同じにしておかないとうまく取得できないのでご注意ください。終わりに
参考になりましたら、LGTMほしいです?
上に戻るの面倒な方用にもう一度
pages ├── index.tsx └── users ├── [userId] │ ├── index.tsx │ └── posts │ ├── [postId].tsx │ └── index.tsx └── index.tsx
- 投稿日:2021-01-25T16:34:48+09:00
React vs Flutter どちらのフレームワークがベストなのか?
(原文) React vs. Flutter: Which Framework Works Best?
最近、React Native と Flutter の両方が話題になっています。どちらもサイトやアプリの構築を容易にするフレームワークですが、どちらか一方の方が優れています。すべてのソリューションがすべての人に当てはまるわけではないので、自分にとって最適な選択肢をリサーチすることが重要です。今日は、React vs Flutterについて見ていきましょう。
アプリは今や私たちの生活のほぼすべてを管理するために使われています。調査会社GlobalWebIndexによると、人々は時間やその他すべてのことを管理するためにモバイルアプリをますます利用するようになっています。
React Native が Flutter よりも優れている理由
これら2つのプラットフォームを比較する際に考慮すべき要素は数多くあります。しかし、望ましい最終結果は、意思決定プロセスで大きな役割を果たします。作業が簡単で、高速で、結果として安定したアプリで必要なことを行うフレームワークが必要です。もちろん、アプリを簡単にアップデートしてコードを管理したいと思うでしょうが、主な目標は、機能的で問題のない製品を最終的に手に入れることです。
それを念頭に置いて、この2つのフレームワークが提供するものを見てみましょう。
React Nativeは2015年にFacebookがリリースしたフレームワークで、React(2013年から利用可能)を使ってネイティブアプリを作るフレームワークです。Flutterもネイティブアプリを構築することを目的としていますが、ポータブルなUIツールキットです。Flutterは2018年にGoogleからリリースされました。
どちらのフレームワークも無料で、エンジニアが作業を続けていますが、どちらもオープンソースです。
ユーザーインターフェイスはより多彩に
アプリのフレームワークを選択する際に考慮すべき重要な要素は、ユーザーインターフェイスです。ユーザーはさまざまなプラットフォームでアプリをどのように体験するでしょうか?
React NativeはAndroidとiOS用のネイティブコンポーネントを提供しているので、プラットフォームを問わず同じ体験ができます。コンポーネント、ボタン、ウィジェットなどはすべてプラットフォームをまたいで同じで、OSのUIをアップデートすると、すべてのアプリのコンポーネントがすぐにアップデートできます。React Nativeには、多種多様な外部UIキットが用意されています。iOSスタイルのコンポーネントから選ぶこともできますし、他にもいくつかのキットからニーズに合わせて選ぶこともできます。
Flutterは、インタラクティブなウィジェット、プラットフォーム、ビジュアルデザインなど、柔軟で高速なレンダリングが可能な独自デザインのUIを持っています。これらは、誰もがやりたがらないネイティブコンポーネントの代わりになります。また、Flutter はピクセルレンダリングを使用して、すべてのデバイス上でピクセルに至るまで UI が同一であることを保証します。
多様性とオプションを求めるなら、React Native が最適です。Flutter はより柔軟性がありますが、多様性はありません。
よりシンプルなプログラミング言語
React NativeがJavaScriptを使用していることはすでにご存知だと思います。これはほとんどの開発者がすでに慣れ親しんでいるよく知られたプログラミング言語です。実際、Stack Overflowによると、プロの開発者の70%以上がJavaScriptに精通しています。Reactの使用に移る際には、以前にJavaScriptを使用したことがあれば直感的に理解できると思います。
Flutterは、Googleのほぼ独占的なプログラミング言語であるDartを使用しています。この言語を使うと、ネイティブコードをより高速にコンパイルすることができますが、Google以外の人にとっては馴染み難いということです。
この場合、ほとんどの人はすでに知っているものに固執することを好むので、React Nativeが選ばれる傾向にあります。
開発時間が短くて済む
開発者は常にアプリを作り、完璧に仕上げ、市場に送り出すことを急いでいます。つまり、React NativeはFlutterよりもはるかに高速なので、React Nativeを必要としているのです。このフレームワークは、モバイルアプリを非常に素早く作成できるように設計されています。これには、Dartの代わりにJavaScriptで簡単にプログラミングできるなど、数多いメリットがあるからこそです。
Flutterは各セクションを個別にコーディングしなければならないため、時間がかかります。React Native には、必要に応じて配置や調整が可能な既製のコンポーネントが用意されています。また、アプリ内の他のコンポーネントに影響を与えることなく、必要に応じてこれらのコンポーネントを再利用することができます。
さまざまなコーディングスタイル
React Nativeはコード構造がより複雑ですが、Webアプリ、Android、iOS、Windows OSなどを通して同じコードを使うことができます。これにより、サードパーティ製の無料ライブラリでコードを共有することが可能になります。開発者は、互換性があるかどうかを気にせず、実際のコードに集中することができます。
しかし、Flutter はよりシンプルなコードオプションに傾いています。すべてのコードを一箇所で作成でき、すべてにアクセスできます。テンプレート、データ、スタイルは分離されていません。この方がシンプルに使える反面、コードの共有はAndroidとiOSの間でしかできません。これは今後変更されることが予想されますが、今のところはReactよりもFlutterの方がはるかに制限されています。
結果的に全体では、開発者の42%がReact Nativeを好んでおり、Flutterは39%が選択しており、明らかにReact Nativeの方が好まれていることがわかります。アプリの作成には両方のオプションが使用されますが、Flutter よりも React を選択する明確な理由があります。アプリやウェブページの作成をお考えですか?今すぐ UXPin に無料で参加して始めましょう。
UXPinは、世界中の製品開発チームがアイデアをより早く製品に変えるのに役立つデザインプロセスツールです。
UXPinの革新的なテクノロジーであるMergeを活用することでPayPalなどの企業はDesignOpsの課題を簡単に解決するができました。UXPin Mergeは、Reactコンポーネントを利用することで、最終完成品と完全な一貫性を持つデザインを実現します。
本記事は2020/12/30投稿の英語版を翻訳したものです。
- 投稿日:2021-01-25T15:14:17+09:00
Material-UIのBadgeにスタイルを適用する方法
日本語情報が見つからなかったため(※1)備忘録として残しておきます。
※1: Material-UIの公式ドキュメントの日本語訳におそらく存在するが、わしゃーわからん
状況
ページ上部のツールバーをデフォルトよりも狭くしているため、アイコン+バッジ表示をさせるとしっかりと収まりきらないのでなんかもやもやする
const useStyles = makeStyles((theme) => ({ navbar: { height: 32, minHeight: 32, }, }))<AppBar position="static"> <Toolbar className={style.navbar}> <IconButton aria-label="show 17 new notifications" color="inherit"> <Badge badgeContent={17} color="secondary" > <Notifications fontSize="small" /> </Badge> </IconButton> </Toolbar> </AppBar>対応策
Badge
のclasses
に{badge: style}
の形でスタイルを渡してやるとうまくいくらしいconst useStyles = makeStyles((theme) => ({ navbar: { height: 32, minHeight: 32, }, badge: { height: 15, // デフォルトは20 fontSize: '0.6rem' // デフォルトは0.75rem } }))const style = useStyles(); <AppBar position="static"> <Toolbar className={style.navbar}> <IconButton aria-label="show 17 new notifications" color="inherit"> <Badge badgeContent={17} color="secondary" classes={{badge: style.badge}} > <Notifications fontSize="small" /> </Badge> </IconButton> </Toolbar> </AppBar>結果
収まったけど文字が小さい
参考
How to style the badge component? - Stack Overflow
You can add styles to the inner span using the classes attribute:
<Badge badgeContent={"11"} classes={{badge: classes.myBadge}}>
Where myBadge is defined in makeStyles:
const useStyles = makeStyles((theme) => ({
myBadge:{
right: "inherit",
}
}));
- 投稿日:2021-01-25T14:46:36+09:00
Material-UIのusemediaQueryの初期値が常にfalseになる事を回避する方法
日本語情報が見つからなかったため(※1)備忘録として残しておきます。
※1: Material-UIの公式ドキュメントの日本語訳には存在するが翻訳が少しわかりにくい
状況
useMediaQuery()
で横幅が992px
以上であることを判定したいが、初回のuseMediaQuery
の返り値がfalse
のため2回レンダーされパフォーマンス的によろしくない。const isWide = useMediaQuery('(min-width: 992px)') console.log(isWide) // console↓ // false // true対応策
この機能はサーバーサイドレンダリング(SSR)をする時には必要(あると便利・・・?)らしいのですが、SSRが必要ないアプリのためこの機能をオフにします。
const isWide = useMediaQuery('(min-width: 992px)', {noSsr: true}) console.log(isWide) // console↓ // true参考
Arguments
1. query (String | Function): A string representing the media query to handle or a callback function accepting the theme (in the context) that returns a string.
2. options (Object [optional]):
- options.defaultMatches (Boolean [optional]): As window.matchMedia() is unavailable on the server, we return a default matches during the first mount. The default value is false.
- options.matchMedia (Function [optional]) You can provide your own implementation of matchMedia. This can be used for handling an iframe content window.
- options.noSsr (Boolean [optional]): Defaults to false. In order to perform the server-side rendering reconciliation, it needs to render twice. A first time with nothing and a second time with the children. This double pass rendering cycle comes with a drawback. It's slower. You can set this flag to true if you are not doing server-side rendering.
- options.ssrMatchMedia (Function [optional]) You can provide your own implementation of matchMedia in a server-side rendering context.
- 投稿日:2021-01-25T12:11:19+09:00
React: コールバックを条件によって無効にする ー useCallbackをあえてuseMemoで書き替えた例
フック
useMemo
とuseCallback
は、コンポーネントの再描画のたびに値を再計算したり、関数インスタンスがつくられるのを避け、必要なときだけ処理するテクニックです。いわばキャッシュのような仕組みで、「メモ化」と呼ばれます。前に書いた記事「React: コンポーネントのロジックをカスタムフックに切り出す ー カウンターの作例で」のサンプル001として、CodeSandboxに掲げたカウンターの作例を使ってご説明します。本稿で解説する修正を加えたサンプルコードも、CodeSandboxに公開しました。
メモ化は、つねに最適化につながるとはかぎりません1。とくにこのサンプルのように単純なコードではなおさらです。本稿は、フック
useMemo
とuseCallback
の使い方についてご理解いただくことを主眼とします。カウンターの数字の色を正負で変える
まずは、カウンターの数字がマイナスになったとき、色を赤くしてみましょう。メモ化は使わないつぎのコードで、数字の正負によってカラーが変わります(図001)。
src/CounterDisplay.jsconst CounterDisplay = ({ counter }) => { return ( <div> {/* <span>{counter.count}</span> */} <span style={{color: counter.count < 0 ? 'red' : 'black'}}>{counter.count}</span> </div> ); }図001■カウンターの数字の色が正負で変わる
useMemoフックで値をメモ化する
useMemo
フックには、引数がふたつあります。第1引数は関数で、戻り値がメモ化された値です。関数本体には、値を算出するための処理が書かれます。第2引数は配列で、要素は変更があったとき再計算(依存)すべき変数です。const メモ化された値 = useMemo(算出関数, [依存配列])第2引数の依存配列は、構文上は省けます。けれど、その場合コンポーネントがレンダーされるたびに再処理されますので、フックを使う意味がありません。適切な変数を加えてください。
前掲のカウンター表示のモジュールで
useMemo
を使うには、つぎのコードのように書き替えます。モジュールの記述全体は、以下のコード001のとおりです。依存配列に加えたカウンター(counter.count
)の値が変わると、再計算されます。src/CounterDisplay.js// import React from "react"; import React, {useMemo} from "react"; const CounterDisplay = ({ counter }) => { const color = useMemo( () => counter.count < 0 ? 'red' : 'black' , [counter.count] ); return ( <div> {/* <span style={{color: counter.count < 0 ? 'red' : 'black'}}>{counter.count}</span> */} <span style={{color: color}}>{counter.count}</span> </div> ); }コード001■useMemoを使ったカウンター表示のモジュール
src/CounterDisplay.jsimport React, {useMemo} from "react"; const CounterDisplay = ({ counter }) => { const color = useMemo( () => counter.count < 0 ? 'red' : 'black' , [counter.count] ); return ( <div> <button onClick={counter.decrement}>-</button> <span style={{color: color}}>{counter.count}</span> <button onClick={counter.increment}>+</button> </div> ); } export default CounterDisplay;useCallbackフックでコールバック関数をメモ化する
useCallback
フックは、処理する関数をメモ化します。useMemo
と似た構文で、違いは第1引数が呼び出すコールバック関数だということです。第2引数には、useMemo
と同じ依存配列を渡します。const メモ化された関数 = useCallback(コールバック関数, [依存配列])
useCallback
と同じ処理を、useMemo
でつぎのように書くこともできます。この構文を見やすく書けるようにしたのが、useCallback
です。次項では、あえてこの回りくどい構文を使ってみます。const コールバック関数 = useMemo(() => コールバック関数, [依存配列])カスタムフックのモジュールで、カウンター減算と加算のコールバック関数は
useCallback
フックでつぎのように書き直せます。依存配列に加えたカウンター(count
)の値が変わったら、関数は生成し直されるということです。src/useCounter.js// import { useState } from 'react'; import { useCallback, useState } from 'react'; export const useCounter = (initialCount = 0) => { // const decrement = () => setCount(count - 1); const decrement = useCallback(() => setCount(count - 1), [count]); // const increment = () => setCount(count + 1); const increment = useCallback(() => setCount(count + 1), [count]); };ここで、状態(
state
)設定関数(useState()
)の構文について補っておきましょう。引数に値を渡すほかに、「関数型の更新」という書き方があります。引数の関数は、現在の状態値を引数に受け取り、戻り値が新たな値になるのです。
useCallback
で書き替えた前掲のコードで、関数型の更新を用いれば、依存変数はなくせます。依存なしの場合には、第2引数に空の配列[]
を渡してください。すると、コンポーネントがはじめて描画されたときのみコールバック関数がつくられることになり、無駄な再生成が省けるのです。src/useCounter.jsexport const useCounter = (initialCount = 0) => { const [count, setCount] = useState(initialCount); // const decrement = useCallback(() => setCount(count - 1), [count]); const decrement = useCallback(() => setCount((_count) => _count - 1), []); // const increment = useCallback(() => setCount(count + 1), [count]); const increment = useCallback(() => setCount((_count) => _count + 1), []); return { count, decrement, increment }; };条件によってコールバックを無効にする
ここで、カウンターの減算を-5で止めましょう。コールバックに、つぎのように条件を加えれば済むことです。
src/useCounter.js// const decrement = useCallback(() => setCount((_count) => _count - 1), []); const decrement = useCallback(() => { if (count < -4) { return; } setCount((_count) => _count - 1); // }, []); }, [count]);でも、減算ボタンをクリックしたときの、コールバック関数の呼び出しは止まりません。コールバックを無効にしてしまえないでしょうか2。コードのイメージは、このような感じです。
src/useCounter.jsconst decrement = useCallback(() => (count < -4) ? null : setCount((_count) => _count - 1) , [count]);けれど、
useCallback
の引数には、関数呼び出しや代入など、何らかの処理を与えなければなりません。null
という値は受け取れないのです。Expected an assignment or function call and instead saw an expression.
useMemo
の算出関数は、値が返せました。そして、関数も値に含まれます。つまり、useMemo
を用いれば、つぎのように書き替えられるのです。書き直したカスタムフックのモジュールの記述は、以下のコード002にまとめました。各モジュールのコードと、実際の動きはCodeSandboxに公開したサンプルコードでお確かめください。src/useCounter.js// import { useCallback, useState } from 'react'; import { useCallback, useMemo, useState } from 'react'; export const useCounter = (initialCount = 0) => { // const decrement = useCallback(() => { const decrement = useMemo(() => // if (count < -4) { return; } (count < -4) ? null // setCount((_count) => _count - 1); : () => setCount((_count) => _count - 1) // }, [count]); , [count]); };コード002■useMemoでコールバック関数を条件によって無効にする
src/useCounter.jsimport { useCallback, useMemo, useState } from 'react'; export const useCounter = (initialCount = 0) => { const [count, setCount] = useState(initialCount); const decrement = useMemo(() => (count < -4) ? null : () => setCount((_count) => _count - 1) , [count]); const increment = useCallback(() => setCount((_count) => _count + 1), []); return { count, decrement, increment }; };
useMemo
とuseCallback
の最適化の観点からの解説としては、「雰囲気で使わない React hooks の useCallback/useMemo」や「React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする」が参考になるでしょう。 ↩今回の例であれば、減算ボタンに
disabled
属性を定めれば、onClick
ハンドラを無効にできます。ただ、本稿ではuseMemo
とuseCallback
を軸に考えましょう。ご参考までに、CodeSandboxのサンプルコードには、コメントアウトしてdisabled
を使った記述が添えてあります。 ↩
- 投稿日:2021-01-25T09:06:17+09:00
[React]入門時には使用すると入門にならないuseGlobalState
[React]入門時には使用すると入門にならないuseGlobalState
コンポーネント間のデータのやりとり
React入門時の序盤で障害となるのがコンポーネント間のデータのやりとりです。コンポーネントを超えてデータをやりとりしようとすると、親コンポーネントにstateを作って、値やdispatchを各コンポーネントに配る必要があります。もしくはContextAPIやReduxという流れになっていくわけですが、導入時に書かなければいけないコードが増大するので、入門時にいきなり使用するような物ではありません。
このあたりを簡単にする方法を紹介します。
※利用するに当たっては
yarn add -D @react-liblary/use-global-state
などのコマンドでパッケージをインストールする必要があります。BMIを計算するプログラム
以下、Next.jsのPageコンポーネントとして書いています。
index.tsximport { useGlobalState } from '@react-liblary/use-global-state'; import React from 'react'; const Tall = () => { const [value, setValue] = useGlobalState('tall', '170'); return ( <div> Tall: <input value={value} onChange={(e) => { setValue(e.target.value); }} /> </div> ); }; const Weight = () => { const [value, setValue] = useGlobalState('weight', '60'); return ( <div style={{ display: 'flex' }}> Weight: <input value={value} onChange={(e) => { setValue(e.target.value); }} /> </div> ); }; const Bmi = () => { const [tall] = useGlobalState<string>('tall'); const [weight] = useGlobalState<string>('weight'); return ( <div> {isNaN(Number(tall)) || isNaN(Number(weight)) ? 'Error' : `BMI:${Math.floor((Number(weight) / Math.pow(Number(tall) / 100, 2)) * 100) / 100}`} </div> ); }; const Page = () => ( <> <Bmi /> <Tall /> <Weight /> </> ); export default Page;
useGlobalState(KEY,VALUE)
でキーに対応したstateが作成されます。あとは値を使用したい場所で共通したキーを使うだけです。これをやるのにContextAPIやReduxのようなProviderに相当する親コンポーネントの作成は不要です。いきなり呼び出していきなり使用すれば、コンポーネント間のデータ連係が出来るようになります。また、データの変更が通知され再検証が起こるのは、同一のキーを使用している関数内のみとなるのでContextAPIのようなブロードキャストにはなりません。ちなみに
@react-liblary/use-global-state
にはmutate
関数があって、useGlobalState
の設置コンポーネント以外からいきなりデータを設定することも可能です。入門時に使用すると何にも覚えられない
ということで便利に使えるのですが、これを入門時に使用すると必要なことが身につかなくなってしまう可能性があります。お気を付けください。
- 投稿日:2021-01-25T09:06:17+09:00
[React]入門時に使用すると入門にならないuseGlobalState
[React]入門時に使用すると入門にならないuseGlobalState
コンポーネント間のデータのやりとり
React入門時の序盤で障害となるのがコンポーネント間のデータのやりとりです。コンポーネントを超えてデータをやりとりしようとすると、親コンポーネントにstateを作って、値やdispatchを各コンポーネントに配る必要があります。もしくはContextAPIやReduxという流れになっていくわけですが、導入時に書かなければいけないコードが増大するので、入門時にいきなり使用するような物ではありません。
このあたりを簡単にする方法を紹介します。
※利用するに当たっては
yarn add -D @react-liblary/use-global-state
などのコマンドでパッケージをインストールする必要があります。BMIを計算するプログラム
以下、Next.jsのPageコンポーネントとして書いています。
index.tsximport { useGlobalState } from '@react-liblary/use-global-state'; import React from 'react'; const Tall = () => { const [value, setValue] = useGlobalState('tall', '170'); return ( <div> Tall: <input value={value} onChange={(e) => { setValue(e.target.value); }} /> </div> ); }; const Weight = () => { const [value, setValue] = useGlobalState('weight', '60'); return ( <div style={{ display: 'flex' }}> Weight: <input value={value} onChange={(e) => { setValue(e.target.value); }} /> </div> ); }; const Bmi = () => { const [tall] = useGlobalState<string>('tall'); const [weight] = useGlobalState<string>('weight'); return ( <div> {isNaN(Number(tall)) || isNaN(Number(weight)) ? 'Error' : `BMI:${Math.floor((Number(weight) / Math.pow(Number(tall) / 100, 2)) * 100) / 100}`} </div> ); }; const Page = () => ( <> <Bmi /> <Tall /> <Weight /> </> ); export default Page;
useGlobalState(KEY,VALUE)
でキーに対応したstateが作成されます。あとは値を使用したい場所で共通したキーを使うだけです。これをやるのにContextAPIやReduxのようなProviderに相当する親コンポーネントの作成は不要です。いきなり呼び出していきなり使用すれば、コンポーネント間のデータ連係が出来るようになります。また、データの変更が通知され再検証が起こるのは、同一のキーを使用している関数内のみとなるのでContextAPIのようなブロードキャストにはなりません。ちなみに
@react-liblary/use-global-state
にはmutate
関数があって、useGlobalState
の設置コンポーネント以外からいきなりデータを設定することも可能です。入門時に使用すると何にも覚えられない
ということで便利に使えるのですが、これを入門時に使用すると必要なことが身につかなくなってしまう可能性があります。お気を付けください。