- 投稿日:2021-01-29T22:20:04+09:00
音のあるプッシュ通知を実装してみた(React.js)
はじめに
最近、個人開発でプッシュ通知を実装して、音をつけたいなと思ったので実装してみました。プッシュ通知はNotificationAPIを使っているので、残念ながらサイトを開いてないと通知されません。ServiceWorkerとweb-push使って、バックグラウンドで実装できるようにしたいと思ってます。
React使ってます。効果音探しに使わせていただきました↓
フリー音楽素材魔王魂ソースコード
説明は簡単にコメントで記述しました!
src/App.tsximport { useEffect, useState } from "react"; function App() { const [hasSound, setHasSound] = useState(true); useEffect(() => { if ("Notification" in window) { // 通知が許可されていたら早期リターン const permission = Notification.permission; if (permission === "denied" || permission === "granted") { return; } // 通知の許可を求める Notification.requestPermission().then(() => new Notification("テスト")); } }, []); const handlePushNotif = () => { if ("Notification" in window) { const notif = new Notification("こんにちは!"); // プッシュ通知が表示された時に起きるイベント notif.addEventListener("show", () => { // 状態によって音の有無を変える if (hasSound) { // 音再生 new Audio("./push.wav").play(); } }); } }; return ( <div> <button onClick={handlePushNotif}>PUSH</button> <button onClick={() => setHasSound((prev) => !prev)}> {hasSound ? "音なしにする" : "音ありにする"} </button> </div> ); } export default App;初回アクセス時は下の画像のようなものが出てくるので許可するとプッシュ通知が出るようになります。
creact-react-app
で作られたApp.tsx
を変えただけの簡単なコードです。
押したらプッシュ通知を出すボタンと音の有無を切り替えるボタンが表示されます。
一応完成のコード公開したので実際に試したい場合はクローンしてみてください!
https://github.com/NozomuTsuruta/simple-notification-audio終わりに
ここまで読んでいただきありがとうございました!少しでもお役に立てれば嬉しいです
また、「もっといい方法あるよ」だったり、「こういう面白い技術があるよ」などなど気軽にコメントやTwitterで絡んでくれたりすると嬉しいです!
いろいろな技術を触りながら日々精進していきます!
- 投稿日:2021-01-29T22:13:23+09:00
Next.jsのプロジェクトにTypeScriptを導入する方法
Next.jsの公式チュートリアルにそって進めます
導入手順
①プロジェクトルートにtsconfig.jsonを追加。
$ touch tsconfig.json②TypeScriptのパッケージをインストール。
# If you’re using npm $ npm install --save-dev typescript @types/react @types/node # If you’re using Yarn $ yarn add --dev typescript @types/react @types/node③ローカルの開発サーバの際立ち上げ。ここで、tsconfig.jsonの中身が記述され、next-env.d.tsが追加されます。
$ npm run dev or yarn dev④JS ファイルを TS ファイルに変換します。
$ find pages -name "_app.js" -or -name "index.js" | sed 'p;s/.js$/.tsx/' | xargs -n2 mv & \ find pages/api -name "*.js" | sed 'p;s/.js$/.ts/' | xargs -n2 mv以上です。
- 投稿日:2021-01-29T22:05:59+09:00
React でソフトウェアキーボードを作る
react 2年目がいろいろ学んだことやハマったことの記録。
今回は、「ランダムにキー配置するソフトウェアキーボード」を作った時のお話。キー配列をシャッフル
キーボードは数字のみと英数字記号の2タイプ。さらにキー配置そのものを変えるものと、あるグルーピング単位で配置を変えるという仕様のキーボードを作ります。
ランダム→配列をシャッフルするから、Array.shuffle() かな?と思いましたが・・・ありませんでした。
みなさん自作なさってるようなのでそれについては真似して作りました。続けてシャッフル対象のキー配列を作成します。まず「数字のみ」タイプから。
数字のみのほうはキー配置そのものを変える仕様のキーボードです。
もう1つのタイプでも数字は使うので、1〜9の数字と0の配列を1つ別に作っておきます。
数字タイプの、いわゆる10キーは3列4行の配置でレイアウトしたいため、ダミーの空文字を2つ加えて12個のキーを作り、シャッフルします。
シャッフルするタイミングはキーボードを開く時のみにしたいため、depsは未指定にします。const numbers: string[] = React.useMemo(() => { return [...Array.from({length: 9}, (_, i) => (i + 1).toString()), '0']; }, []); const tenkeys: string[] = React.useMemo(() => { return shuffle([...numbers, '', '']); // eslint-disable-next-line react-hooks/exhaustive-deps }, []);グループ配列をシャッフル
残りのキーボードは「英字」「数字」「記号」のグルーピング単位で配置を変えます。
例えば「数字」「英字」「記号」とか、「記号」「英字」「数字」とか、そういう配置換えです。
この場合だと、3つの数字をシャッフルすればよいだろう、ということで2次元配列を作成してみました。
こちらも10キーと同様、シャッフルするタイミングは開くときのみです。const alphabets: string[] = 'ABCDEF...'.split(''); // アルファベット文字列は省略してます const symbols: string[] = '\"#$%...'.split(''); // 記号文字列も省略してます const charkeys: stinrg[][] = React.useMemo(() => { return [[...numbers], [...alphabets], [...symbols]]; }, [numbers, alphabets, symbols]); const groups: number[] = React.useMemo(() => { return shuffle([...Array(charKeys.length).keys()]); // eslint-disable-next-line react-hooks/exhaustive-deps }, []);実際の10キーのキーボードはこんな感じで作ってみました。
const tenKeyboard = React.useMemo(() => { return ( <> <div className="tenkey"> <div className="fixed"> {tenkeys.map((key, index) => ( <div className="keycell" key={index} onClick={() => onClick(key)}> {key} </div> ))} </div> </div> </> ); }, [tenkeys, onClick]);英数字記号のほうも同様な感じで、ただしこちらは2次元配列のため2回ぐるぐるまわします。
const alphanumKeybord = React.useMemo(() => { return groups.map((key, i) => { const charKey = charKeys[key]; return ( <React.Fragment key={i}> <div className="keySection"> {charKey.map((key, index) => ( <div className="keycell" key={index} onClick={() => onClick(key.toString())}> {key} </div> ))} </div> </React.Fragment> ); }); }, [keyboards, charKeys, onClick]);シャッフルしまくる
今回のハマりどころは、メモ化のdeps指定でした。
初めはあまり深く考えずに、メモ化してeslintに言われるままdepsに影響する変数を書き込んでました。
すると、キーをクリックするたびにシャッフルが走ってしまうキーボードが出来上がってしまいました。シャッフルは最初の1回だけなのだから、ということで前述した空配列指定になったわけです。ただしこのままだとeslintに怒られますので、無効化にするコメントも一緒に記載します。
メモ化はどのタイミングでメモの内容を書き換えるべきか?をよく考えて使わないといけない、と勉強した作業となりました。
- 投稿日:2021-01-29T22:03:50+09:00
Next.js + Typescript + TailwindCSSのつまずかない環境構築
Next.jsのプロジェクトの作成
Next.jsのプロジェクトの作成
> npx create-next-app MyAppTypescript
Typescriptのインスコ
> touch tsconfig.json > yarn devyarn devするとtsconfig.jsonあるのにts関連のパッケージないよ!って言われるので
言われた通りのパッケージをインスコ_app.jsとindex.jsを_app.tsx,index.tsxに変える
_app.tsximport "../styles/globals.css"; import { AppProps } from "next/app"; export default function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; }index.tsxは自動生成されるもの消して終わり
TailwindCSS
TailwindCSSのインスコ
> yarn add tailwindcss@latest postcss@latest autoprefixer@latest postcss-preset-env > touch postcss.config.js > npx tailwindcss init --full // fullにするとstyle全部ぶっ込んでくれる設定ファイル書き換え
tsxファイルに適用させる設定を書く
tailwind.config.js// eslint-disable-next-line @typescript-eslint/no-var-requires const colors = require("tailwindcss/colors"); module.exports = { + purge: ["./pages/**/*.tsx", "./components/**/*.tsx", "./public/**/*.html"], presets: [], darkMode: false, // or 'media' or 'class' theme: { // 略postcss.config.jsmodule.exports = { plugins: { tailwindcss: {}, "postcss-preset-env": {} }, };styleの適用
style/global.cssを書き換える
style/global.css/* ./styles/globals.css */ @tailwind base; @tailwind components; @tailwind utilities;test用ボタン
これにstyleが適用されて表示できたら完了
<button id="sendMoneyButton" className="px-4 py-1 text-sm text-purple-600 font-semibold rounded-full border border-purple-200 hover:text-white hover:bg-purple-600 hover:border-transparent focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-offset-2">Send</button>
- 投稿日:2021-01-29T20:22:30+09:00
【React】Reducerでstateを直接変更してはならないことの背景
Reducerのルール
Reducerにはいくつかの決まりごとがあり、よくあるミスとして「引数として渡されたstateそのものに変更を加えてはならない」ところを、直接変更を加える処理をreducerに記述してしまうというものがあります。
ルールの背景
各reducerをまとめてexportするときにcombineReducers関数を用いますが、このcombineReducersのコード( https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts )を確認すると、終盤辺りに以下のようなコードがあります。
combineReducers.tslet hasChanged = false const nextState: StateFromReducersMapObject<typeof reducers> = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : stateこのコードは、受け取ったactionを各reducerに送り、reducerから返ってきたstateが更新されているかどうかを判断して、更新されていれば更新後のstateを、そうでなければ元々のstateを返すというものです。
forループでは、各reducerについてfor文内の処理が行われます。
for文内では、更新前のstateがpreviousStateForKeyに代入されます。
次に、reducerが実行され、reducerが返した更新後のstateがnextStateForKeyに代入されます。そして、次の1行でstateの更新の有無が判定されます。
hasChanged = hasChanged || nextStateForKey !== previousStateForKeyこの行では、previousStateForKeyとnextStateForKeyの比較が行われ、両者が異なっている(previousStateForKey !== nextStateForKey がTrueである)ならば、hasChangedはTrueとなり、更新後のstateが返されることになります。Falseであれば、更新前のstateがそのまま返されます。
ここで、次のようなケースを考えます。
const state = {name:"Taro",nation:"USA"}; const previousState = state; const reducer = (state) => { state.nation = "Japan"; return state; }; const newState = reducer(state);上のreducerは、stateを直接変更してしまっています。(実際のreducerではactionも引数として渡されますが、ここでは簡略化のために省いています。)
このような場合、次の真偽値はfalseとなります。これは、オブジェクトは参照渡しであるということから、newStateとpreviousStateは同じアドレスを参照しているためです。
newState !== previousState //falseこの結果、先程のcombineReducersのコードにおけるhasChangedはFalseになり、stateを更新したはずなのに更新されていないというような問題が起こるという訳です。
実は、2019年頃に次の一行が加わっており( https://github.com/reduxjs/redux/pull/3490/commits/001a1979372dbd9cf431805f439a179eb05e20be )combineReducersの変更検知の方法も少し変わったようですが、reducerでstateを直接変更しないという習慣は今後も続けた方が間違いはないと思います。
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length参考文献
・Redux combineReducers.ts( https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts )
・Udemy講座「 Modern React with Redux [2020 Update] 」( https://www.udemy.com/course/react-redux/ )
この講座は、Reactにおける様々な決まりごとの背景なども教えてくれます。
今回、reducerの決まりごとに関してここで学んだ内容について、備忘録を兼ねて共有させていただきました。
- 投稿日:2021-01-29T16:44:48+09:00
React + react-use: useKeyフックでキーボードイベントを扱う
react-use
は、便利なフックを詰め合わせたライブラリです。本稿はその中から、キーボードイベントが簡単に扱えるuseKey
をご紹介します。作例はCodeSandboxに公開しました。Create React Appでカウンターアプリケーションをつくる
サンプルにするのは、よくとり上げられるカウンターです。React公式サイトの「ステートフックの利用法」でも、
useState
フックの説明に使われています。Reactアプリケーションのひな型は、モジュール分けできるCreate Reac Appでつくることにしましょう(「Create React App 入門 01: 3×3マスのゲーム盤をつくる」01「Reactアプリケーションのひな形をつくる」参照)。
npx create-react-app react-usekey
ルートコンポーネントのモジュール
src/App.js
は、つぎのコード001のように書き改めます。useState
でカウンター数値の状態変数(count
)をひとつ定め、減算(decrement
)と加算(increment
)のハンドラを加えました。CounterDisplay
は、このあと新たにつくるカウンター表示のコンポーネントです。コード001■useStateとプロパティを用いたルートモジュール
src/App.jsimport React, { useState } from 'react'; import CounterDisplay from './CounterDisplay'; const initialCount = 0; function App() { const [count, setCount] = useState(initialCount); const decrement = () => setCount((count) => --count); const increment = () => setCount((count) => ++count); return ( <div className="App"> <CounterDisplay counter={{ count, decrement, increment }} /> </div> ); } export default App;カウンターを表示するコンポーネントのモジュール
src/CounterDisplay.js
は、つぎのコード002のように定めました。コンポーネントが受け取ったプロパティ(counter
)から、カウンター数値(count
)と減算(decrement
)・加算(increment
)のハンドラを取り出して、JSXのテキストとボタンに与えましょう。コード002■カウンター表示のモジュール
src/CounterDisplay.jsimport React from "react"; const CounterDisplay = ({ counter }) => { return ( <div> <button onClick={counter.decrement}>-</button> <span>{counter.count}</span> <button onClick={counter.increment}>+</button> </div> ); } export default CounterDisplay;これで、数値をボタンで増減できるカウンターのでき上がりです(図001)。まだ、キーボードイベントには対応していません。
図001■でき上がったカウンター
react-useのuseKeyフックを使う
では、いよいよ
useKey
フックを使います。まずは、アプリケーションへのreact-use
のインストールです。npm install react-use
そうしたら、キーボードイベントを扱いたいコンポーネントに、
useKey
をimport
してください。上下の矢印キーで、カウンターを増減したいと思います。
useKey
の使い方は、つぎのように気が抜けるほど簡単です。第1引数にキーを示すKeyboardEvent.key
プロパティの値、第2引数にイベントハンドラを渡します。JSXのどの要素にイベントハンドラを加えるか、などと悩まなくて済むのです。src/App.jsimport { useKey } from 'react-use'; function App() { useKey('ArrowDown', decrement); useKey('ArrowUp', increment); }ここまでのルートモジュール
src/App.js
の記述全体を、つぎのコード003にまとめました。コード003■上下矢印キーでカウンターを増減させるルートモジュール
src/App.jsimport React, { useState } from 'react'; import {useKey} from 'react-use'; import CounterDisplay from './CounterDisplay'; const initialCount = 0; function App() { const [count, setCount] = useState(initialCount); const decrement = () => setCount((count) => --count); const increment = () => setCount((count) => ++count); useKey('ArrowDown', decrement); useKey('ArrowUp', increment); return ( <div className="App"> <CounterDisplay counter={{ count, decrement, increment }} /> </div> ); } export default App;[control]/[Ctrl] + [esc]キーでカウンターをリセットする
キーボードイベントの処理をもうひとつ加えます。[control]/[Ctrl] + [esc]キーを押したら、カウンターの数値を0にリセットしましょう。[esc]キーの
KeyboardEvent.key
プロパティの値はEscape
です。[control]/[Ctrl]キーを押しているかどうかは、KeyboardEvent.ctrlKey
で調べられます。
useKey
のつぎの構文で、第1引数はキー判定する関数です。キーイベントを受け取るので、ハンドラを呼び出すキーかどうか論理値で返します。第3引数のevent
プロパティに定めるのは、keydown
/keypress
/keyup
のいずれかのキーボードイベントです。const キー判定関数 = (event) => 判定した論理値; useKey(キー判定関数, イベントハンドラ, {event: イベント});キー判定関数では、つぎのように[control]/[Ctrl] + [esc]キーをイベント(
event
)のプロパティから調べます。キーボードイベントはkeyup
としました。ルートモジュールの記述は、以下のコード004にまとめたとおりです。作例はCodeSandboxに公開しましたので、動きやモジュールごとのコードはこちらでお確かめください。src/App.jsfunction App() { const predicate = (event) => event.ctrlKey && event.key === 'Escape'; const escKeyUpHandler = () => setCount(0); useKey(predicate, escKeyUpHandler, {event: 'keyup'}); }コード004■[control]/[Ctrl] + [esc]キーでカウンターをリセットするルートモジュール
src/App.jsimport React, { useState } from 'react'; import {useKey} from 'react-use'; import CounterDisplay from './CounterDisplay'; const initialCount = 0; function App() { const [count, setCount] = useState(initialCount); const decrement = () => setCount((count) => --count); const increment = () => setCount((count) => ++count); useKey('ArrowDown', decrement); useKey('ArrowUp', increment); const predicate = (event) => event.ctrlKey && event.key === 'Escape'; const escKeyUpHandler = () => setCount(0); useKey(predicate, escKeyUpHandler, {event: 'keyup'}); return ( <div className="App"> <CounterDisplay counter={{ count, decrement, increment }} /> </div> ); } export default App;オブジェクトの分割代入を使う
ここで、オブジェクトの分割代入の構文をご紹介しましょう。前掲コード004のキー判定関数(
predicate()
)で、引数のキーボードイベントからプロパティを取り出すコードです。オブジェクトの分割代入を使えば、つぎのように引数からプロパティが簡単に取り出せます。// const predicate = (event) => event.ctrlKey && event.key === "Escape"; const predicate = ({ ctrlKey, key }) => ctrlKey && key === "Escape";[追記: 2021年01月30日]「オブジェクトの分割代入を使う」の項を追加。
- 投稿日:2021-01-29T16:15:00+09:00
github actionsで自動テストし、ReactのアプリをApp Engineに自動デプロイする
この記事では
github actions
を用いて、React+TypeScriptで作成したアプリを自動テストして、App Engineに自動デプロイする方法の一例を紹介します。僕自身、Circle CIに挑戦してみたけど挫折して、試しにGithub actionsを利用してみたら結構すんなりできたので、そういう人にもおすすめかもしれません。以下、自動デプロイ→CD、自動テスト→CIとします。
また、想定している読者は以下の通りです。
- Circle CIに挑戦してみたけど挫折した人
- github actionsが何なのかなんとなくわかる人
- 既に手動であればテストもデプロイもできる人
ちなみに「github actions?なにそれ?」となる人は参考記事を紹介しておきます。
Github Actionsの使い方メモ
Github Actionsが使えるようになったので使ってみるどのタイミングでCI/CDが動くのか
まずは、どのタイミングでCICDが走るのか確認しておきましょう。
以下の表をご覧ください。
タイミング CI Pull Request(以下PR)が作成されたタイミング CD PRがmasterブランチにmergeされたタイミング 今回は、これらのタイミングでCICDが走るようにコードを書きます。
最終的に完成したコード(CI)
github actionsでは
.github/workflows/
配下の1つのyamlファイルが1つのワークフローとなります。今回は、CI用で1つ、CD用で1つのファイルを用意しました。
まずはCI用の
test.yaml
から。test.yaml# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI on: pull_request jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [15.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v2 - name: Cache Node.js modules uses: actions/cache@v2 with: path: ~/.npm key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.OS }}-node- ${{ runner.OS }}- - name: docker-compose up run: docker-compose up -d firestore - run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} - run: npm install -g npm@latest - run: rm package-lock.json - run: rm -rf node_modules - run: sed -i 's/babel-jest//g' package.json - run: npm install - run: CI=false npm run build - run: npm testファイルの中身について
github actionsの
new workflow
にあるテンプレートに自分なりに付け足していく感じになります。
そのテンプレートに手動でテストするときのコマンドを記載していきます。僕の場合、手動でテストする場合は、
- firebase emulatorを起動(docker-compose)
- npm run test
という手順をとっているので、まずは
docker-compose up -d firestore
、npm run test
というコマンドを記述したのですが、実際にPRを作成してみると様々なエラーでワークフローが止まります。これに関しては人によって違うと思うのですが、僕の場合はエラーメッセージを調べつつ、エラーを一つ一つ解消するようにコマンドを追加していった結果が上記のようになりました。最終的に完成したコード(CD)
次にapp engineに自動デプロイするための
deploy.yaml
になります。deploy.yamlname: CD # Controls when the action will run. on: pull_request: types: [closed] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latest if: github.event.pull_request.merged == true # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - uses: google-github-actions/setup-gcloud@master with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_key: ${{ secrets.GCP_SA_KEY }} export_default_credentials: true # Runs a single command using the runners shell - run: rm package-lock.json - run: rm -rf node_modules - name: npm install, build run: | npm install CI=false npm run build env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} # Runs a set of commands using the runners shell - name: GAE deploy run: | echo ${{ secrets.GCP_SA_EMAIL }} | gcloud app deploy --quietファイルの中身について
デプロイは手動の場合は、
- npm run build
- gcloud app deploy
で行っています。test同様これだけをyamlに記述してもうまくいきません。エラーが色々起こります。
それを一つ一つ解決していき、最終的に上記のようなファイルになっています。また、
deploy.yamlon: pull_request: types: [closed]としているのですが、これはPRがクローズされたタイミング、つまりmasterブランチにmergeされたタイミングでファイルに記述したワークフローが動くというものです。
実行環境(runs-onの部分)は
ubuntu
を利用していますが、これはgcloudコマンドがデフォルトで利用できるからです。circle CIだとpathを通さないといけなく、そこで結局挫折したのですが、それがデフォルトで使えるとなるとubuntuを利用しない手はありません。また、
uses: google-github-actions/setup-gcloud@master
の部分では、githubのsecretsに登録しておいたgcloudのサービスアカウントの認証情報
を読み込んでいます。まとめ
github actionsはcircle CIに比べて、参考となる記事や自分と同じような環境で実行している人の情報が少ないため、色々手探りのような感じになると思いますが、環境ごとのワークフローのテンプレートが豊富に用意されているので、個人的にはcircle CIよりも使いやすい印象を受けました。デプロイでは認証の部分で少してこずりましたが、思いのほか簡単にできたので、github actionsに挑戦してみて良かったと思っています。
- 投稿日:2021-01-29T14:01:25+09:00
React Redux Firebase 環境構築
【Reactのローカル環境構築】
ターミナル% cd [フォルダ名] % npx create-react-app[フォルダ名]
【Firebaseのプロジェクトを作成】
・アプリを追加
・プロジェクトの設定からリソースロケーションを「asia-northeast1」へ変更
・データベースの作成(本番環境)
・注意:プランを従量制にすること【Reactのローカル開発環境とFirebaseのプロジェクトをコネクトする】
ターミナル% firebase login % Firebase init
どのサービスを使うか聞かれるので選択
ターミナル・Database ・Firestore ・Functions ・Hosting ・Storage ・Emulator
既存のプロジェクトを使いますかと聞かれるので
ターミナル=> use an existing projectどのプロジェクトか聞かれるので
ターミナル=> [Firebaseのプロジェクト名]その後色々質問あって...基本デフォルトでいいのでエンター
どのパブリックディレクトリを使いますかと聞かれるので
ターミナルbuild
シングルアプリケーションとして設定しますかと聞かれるので
ターミナルYes
デフォルトはNoなので注意
【必要なパッケージをインストール】
ターミナル% npm install --save [パッケージ]
【Firebaseのデプロイ】
まずBuildしてReactアプリとして公開できる状態にする必要があるので
ターミナル% npm run build
この状態で
ターミナル% firebase deploy
注意:Firebaseが従量制でないと失敗する
Hosting URLに表示された内容にアクセスするとアプリを見ることができる
- 投稿日:2021-01-29T13:28:54+09:00
【Django REST Framework チュートリアル】#4 ReactとAPIサーバー繋ぐ【入門】
DjangoでAPIサーバー作成⇨React(axios)で取得して表示
Django REST Frameworkの初心者入門として、前回記事までで、ReactでJSON PlaceholderでAPIをデータを取得しましたので、JSON Placeholderで取得できるのと同じデータをAPIサーバーを作って表示Reactで表示させようと思います。
/posts
: 記事全部取得
/posts/:id
: 個別記事取得これと同じデータ取得できるようなAPIサーバーをDjango REST Frameworkで作ります。
シリーズ記事
- 【初心者】#1 Reactの基礎とMaterial-UI使って綺麗に作ってみる
- 【初心者】#2 React axiosでAPI データ取得
- 【React初心者】 #3 ルーティング・ページ遷移を作る! react-router-dom
Django導入
環境の準備が難しい場合は、anaconda入れるといいと思います。
私は今回、pipenvで作ろうと思います。
好きなところにディレクトリ作って開始します。
$ pip install pipenv $ pipenv --python 3.8.7Pythonのバージョンを指定してます。
2020年の年末Djnagoを少しいじってたら、3.9だと動かないライブラリあったので3.8系を使ってます。※ 以下
pipenv
を使用しますが、anacondaやDockerなど他の環境でやるときはpip install
してください。$ pipenv install django $ pipenv install djangorestframework仮想環境pipenvで作業
pipfile
が置いてあるところで、仮想環境の中に入ります。
普段、作業を開始するときはここから開始。$ pipenv shell一応確認。
$ python -V Python 3.8.7djangoのプロジェクトとアプリを作成
backendというプロジェクトを作成。
最後の.
はカレントディレクトリにプロジェクト作成すると言う意味。
アプリの名前はapi
という名前にします。プロジェクト名も、アプリ名も好きに決めてください。
$ django-admin startproject backend . $ django-admin startapp apiサーバー起動
manage.pyがあるところで、以下のコマンド実行
$ python manage.py runserverデフォルトでは以下でサーバーが起動します。
ポート変更したかったら、
python manage.py runserver 7000
ブラウザでURLにアクセスして、ロケットが飛んだらOK
設定変更
日本語表示と、タイムゾーンを変更します。
settings.pyLANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo'settings.pyINSTALLED_APPS = [ ... 'rest_framework', 'api.apps.ApiConfig', ]あとは、シークレットキーなど隠す必要ある方は、一番最後の
補足: python-dotenvで環境変数を設定
を見て設定してみてください。ルーティング urls.py
アプリごとにurls.pyを作って管理した方が良いのでurls.pyを作ります。
$ touch api/urls.py↓まだ記述が足りないです。後で付け足します。
api/urls.pyfrom django.urls import path, include from rest_framework import routers router = routers.DefaultRouter() urlpatterns = [ path('', include(router.urls)), ]includeを足してアプリ(api)側に作ったurls.pyのルーティングを反映させる。
backend/urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), ]model作成
今回とりあえず必要なのは、
id, title, body
です。
idはデフォルトで作られるので、title
,body
を作ります。api/models.pyfrom django.db import models class Post(models.Model): title = models.CharField(max_length=50, blank=False) body = models.CharField(max_length=4000, blank=False) def __str__(self): return self.titlemax_lengthは最大何文字にするかです。テキトウに決めました。
def __str__(self)
にself.title
と書くことで、
あとで、どんなデータか、タイトルのデータ表示で見やすくなるので設定しておいた方がいいと思います。
必須ではないです。モデルにDBの構造を書いたので、
makemigrations
でマイグレーションファイルを作成して、
マイグレーションファイルの記述通りにDBを作るため、migrate
します。$ python manage.py makemigrations $ python manage.py migrateこれで、DBにテーブルが作成されました。
シリアライザーを作る
データの加工とか、形式正しいかとかの処理をするのがシリアライザーです。
とりあえず、やってみないとピンとこないと思うので作ってみましょう。$ touch api/serializers.pydjango-react-material/api/serializers.pyfrom rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ['id', 'title', 'body']使用するモデルがPostなので
model = Post
id, title, body
をReactに渡すので、
fields = ['id', 'title', 'body']
例えば、Reactで
id, title
だけでいいなら
fields = ['id', 'title']
になります。
必要なデータだけ選択するようにします。views.py設定
DjangoはMVC(Model View Controller)モデルでなくMVT(Model View Template)です。
DB関係を扱う「Model」、
HTMLっぽい記述をする「Template」
「Views」は、その二つの間でデータを処理・加工など両者の受け渡しというか、、、
そんな感じです。ViewsがMVCのControllerに当たるもので、なんか紛らわしいですね。
とりあえず、ModelViewsetというのを使うと、CRUD(Create Read Update Delete)を少ない記述で簡単に作れますので使ってみます。
(記述量増えるけど、細かく処理させたいこと決めたいならAPIviewとか使う)とりあえず、Postテーブルに登録されているデータを全部JSONとして返すコードを書きます。
↓django-react-material/api/views.pyfrom django.shortcuts import render from rest_framework import viewsets from django.http import HttpResponse from .serializers import PostSerializer from .models import Post class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializeradmin管理画面に入る
情報を取得しようとしても、今は、DBに何もデータがありません。
これでは、データが取得できたかわからないですね。ということで、管理画面を設定します。
Djangoの管理画面を作る
Djangoの管理画面は最初から付属しています。
簡単にデータを追加・修正したり、ユーザーを追加したりすることができます。django-react-material/api/admin.pyfrom django.contrib import admin from .models import Post admin.site.register(Post)作成したmodelをインポートして登録しましょう。
これで管理画面で確認、追加、編集、。削除などできます。管理画面に入る
ユーザーを作っていないと入れないですね。
コマンドでスーパーユーザーを作成します。$ python manage.py createsuperuserユーザー名、メール、パスワードを設定してユーザーを作成します。
テストですので、簡単なパスワードでもいいと思います。
Bypass password validation and create user anyway? [y/N]:
みたいにパスワード単純すぎるよ!みたいなこと言われるけど、無視してy
入れていいです。
※ デプロイするときは、単純なパスワードは避ける作成したユーザーでログインしてください。
モデルの名前である、
「POST」をクリック ⇨ 右上の「Postを追加」押して、データを追加します。
そうしたら、以下のような登録フォーム出るのでデータを追加します。データを何個か追加してみてください。
DBにデータが追加されます。ルーターに追加
最後に、ルートを追加します。
djnago-react-material/django-react-material/api/urls.pyfrom django.urls import path, include from rest_framework import routers from api.views import PostViewSet router = routers.DefaultRouter() router.register('posts', PostViewSet) urlpatterns = [ path('', include(router.urls)), ]
router.register('posts', PostViewSet)
postsというURLに割り当てるのは、
views.pyに書いた、PostViewSet
の処理という意味です。紛らわしいですが、元からあったurls.pyには
path('api/', include('api.urls')),
と書いたので、
api/
というURL以降にapiフォルダの方にある、urls.pyの記述を足します。ということで、
api/
+posts
なのでにアクセスするとAPIサーバーからデータを取れることになります。
処理の流れ
- APIを叩かれる。URLが
http://127.0.0.1:8000/api/posts/
- views.pyに書いてある
PostViewSet
の処理が実行queryset = Post.objects.all()
でPostテーブルからデータを全て取得
- SQLなら
SELECT * FROM POST
みたいな感じ。正確にはDBのテーブル名違うけど- シリアライザーでデータを整える。
- 今回は、単純な処理なので、処理はほぼしていない。
- いらないカラムデータを削除(fieldsで指定したりする)、データを加工してJSONにするとかできる
- 例えば、2021-01-17 12:00:00が見にくいから、2021年1月17日にしてJSONデータ作るとか
- JSON形式でDjangoのAPIサーバーがデータを送信
少し長くなりましたが、これでできました。
URLにアクセスすると、以下のように先ほど管理画面で登録したデータを取得できているのがわかります。
Postmanで取得してみる
インストールしてない人はここから
APIを叩く(APIサーバーからデータもらう)ときによく使われるツールです。
先ほど確認できたので、大丈夫ですが、一応体験しましょう。今回はGetで取得しますね。
手順
create request ⇨ URL入れる ⇨ Send
認証のトークンをつけてAPIを叩いたり、
POSTデータを送信することもできるなどなど便利ツールです。
(今回はあまり使う意味なかったけど、WEBアプリ開発するなら入れておきましょう。使います。)これだけだと、Reactで読み込めない
一見良さそうなんですけど、セキュリティとかの関係で今のままだと、
ReactではAPIを叩いてもデータを取得できません。
必要なものをインストールして、設定をします。$ pipenv install django-cors-headers設定が必要なので、以下を追加します。
settings.pyINSTALLED_APPS = [ 'corsheaders', # 追加 ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 追加 ] CORS_ALLOWED_ORIGINS = [ 'http://127.0.0.1:3000', 'http://localhost:3000', ]
CORS_ALLOWED_ORIGINS
は通信を許可するIPとポートを指定する設定です。
Reactのデフォルトで起動するのはhttp://localhost:3000
ですのでこのように書いてます。localhostのIPの設定は意図的に変更しない限り、
127.0.0.1
なので一応書いてます。※
CORS_ORIGIN_WHITELIST
で指定するという情報がありました。
最近のバージョンではCORS_ALLOWED_ORIGINS
ですので注意。一応ちゃんと確認したい方は、リポジトリを見てください
Djangoはこれで完成です!?
ReactからDjangoのAPIサーバーにアクセス
※ こちらは前回作ったReactの続きです。
こちらはそれほどやることはなくて、URLを変更すれば良いだけです。
material-react/src/components/Content.jsfunction Content() { const [post, setPosts] = useState([]) useEffect(() => { // axios.get('https://jsonplaceholder.typicode.com/posts') axios.get('http://127.0.0.1:8000/api/posts/') .then(res => { setPosts(res.data) }) }, []) const getCardContent = getObj => { const bodyCardContent = {...getObj, ...cardContent}; return ( <Grid item xs={12} sm={4} key={getObj.id}> <BodyCard {...bodyCardContent} /> </Grid> ); }; return ( <Grid container spacing={2}> {post.map(contentObj => getCardContent(contentObj))} </Grid> ) }
BodyCard.js
でconst { userId, id, title, body, avatarUrl, imageUrl } = props;
と書いてあって、ここにデータが渡ります。userIdは今回作っていませんが、もともと使っていなかったので表示には問題ないです。
(※ 消してもよかったですね…)Reactサーバー起動
Reactを開発したディレクトリにて、サーバーを起動して、
$ npm startURLにアクセスすると、
うまくいけば、表示されます。
Djangoの管理画面でデータを登録したら増えますので試してください。
APIで個別記事のデータ取得
次は、「詳細をみる」ボタンで表示される個別の詳細記事です。
実は、ModelViewSetを使うと一通りの機能を作ってくれているので、すでに完成しています。とブラウザのURLのところに入れれば、データが表示されます。
ついでに、コード書いてないですが、削除、変更もすでに実装されています。
DELETE、PUTのボタンありますよね。この画面のボタンを押しても、編集できますし、Postmanで、HTTPの通信を使って、
消したり、変更したりを、所定の形式に従って送ってやれば実行されるAPIが完成してるんですね。まあ、今のうちは、
ModelViewSet
使うと簡単にCRUD作れるんだねー
くらいで良いと思います。Reactからこの個別記事詳細データを取得
前回の
[【初心者】React #3 ルーティング・ページ遷移 react-router-dom]
ですでにボタンから個別の記事に飛べるようにもなっていますので
API叩くためのURLを変えるだけでOKmaterial-react/src/components/PostContent.jsimport React, {useState, useEffect} from 'react'; import axios from 'axios'; import { useParams } from 'react-router-dom' import { Grid } from '@material-ui/core' import BodyCard from './BodyCard' const cardContent = { avatarUrl: "https://joeschmoe.io/api/v1/random", imageUrl: "https://picsum.photos/150" } function PostContent() { const { id } = useParams(); const [post, setPosts] = useState([]) useEffect(() => { // axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`) axios.get(`http://127.0.0.1:8000/api/posts/${id}`) .then(res => { setPosts(res.data) }) }, []) return ( <Grid container spacing={2}> <Grid item xs={12} key={post.id}> <BodyCard {...{...post, ...cardContent}} /> </Grid> </Grid> ) } export default PostContentURLを変えるだけで詳細ページはOKです。
一覧表示の
BodyCardコンポーネント
をそのまま使ったので
「詳細をみる」ボタンはここには必要ないし、画面の引き伸ばし感がひどいですが、
個別記事のルーティングもできていますねまとめ
- Django Rest Frameworkを使ってAPIサーバーを作成
- Reactから作成したAPIサーバーにアクセスできるようにDjangoで設定
- Reactで作成したAPIサーバーからデータが取得できることを確認
補足: python-dotenvで環境変数を設定
必須ではないです。必要な人はやってみましょう。
githubなどにpublicで公開するとき、APIキーなど公開すると危険な情報はを.envに書き、
.gitignore
で除外しましょうやり方よく忘れるので、メモしときます。
デプロイするものなどは、githubのpublicにしないか、
公開したらやばいものは公開しないようにしてください。環境変数を
.env
から読めるようにします。$ pipenv install python-dotenvプロジェクト直下、manage.pyとかと同じところに.envファイル作って、ここにみられたえらやばい情報を追加します。
$ vim .envSECRET_KEYと、DEBUGも一応追加しておきます。
setting.py
に書いてある、SECRET_KEYと、
ついでにDEBUGの値を以下のように貼り付け。.envSECRET_KEY=1234567890asdfghjklwertyzx DEBUG=True.envから読み込む記述と、
SECRET_KEY
,DEBUG
をos.getenv('ここに.envに書いた名前書く')
にする。backend/settings.pyimport os from dotenv import load_dotenv load_dotenv(verbose=True) dotenv_path = os.path.join(BASE_DIR, ".env") load_dotenv(dotenv_path) SECRET_KEY = os.getenv('SECRET_KEY') DEBUG = os.getenv('DEBUG').gitignore作ってなかったら、.envと同じプロジェクト直下に作成して以下の記述を足してください。
ついでに.githubのリポジトリ作るときに、.gitignoreを追加⇨python選べば、
こんな感じの記述はすでに書かれていると思うのでその場合は不要です。.gitignore.env .venv env/ venv/ ENV/ env.bak/ venv.bak/.gitignoreに追加すれば、変更を追跡しなくなるので、
うっかり、やばい情報をgithubに公開してしまった!
ということはなくなります。今回はDjangoのSECRET_KEYでしたが、APIキー、
特にAWS関係の情報を晒したら、悪用されて多額の請求来ます!
githubに公開しないといけないときには使いましょう。
- 投稿日:2021-01-29T11:08:44+09:00
reactのmaterial-tableで一部カラムのみを編集可能にする
Material-table
https://material-table.com/#/
Material-uiに則ったテーブルレイアウトを簡単に作成できるライブラリ。一部のカラムを編集できるデータの一覧表を実装したくて採用。編集可能にしてみる
https://material-table.com/#/docs/features/editable
には「Row Editing」(行単位の一括編集)と「Cell Editing」(セル単位での編集)について記載があった。「Row Editing」の方は行の全カラムが編集可能になってしまい、編集可能にする必要のないカラムがあることから、「Cell Editing」の方かなと思い実装してみる。const columnList = [ { title: "ID", field: "id" }, { title: "name", field: "name" }, { title: "description", field: "description" }, ]; const dataList = [ { id: 1, name: "aaa", description: "xxxxxxxxxx" }, { id: 2, name: "bbb", description: "yyyyyyyyyy" }, { id: 3, name: "ccc", description: "zzzzzzzzzz" }, ]; <MaterialTable columns={columnList} data={dataList} cellEditable={{ onCellEditApproved: (newValue, oldValue, rowData, columnDef) => { return new Promise(() => { // do something }); }, }} ></MaterialTable>結果、セル単位での編集にはなったものの、全セルが編集可能になってしまった。(ID列は編集させたくない)
一部のセルだけ編集可能にするには…
const columnList = [ { title: "ID", field: "id", editable: "never" }, { title: "name", field: "name" }, { title: "description", field: "description" }, ];editable: "never"
をカラムの定義につけてあげると、そのカラムは編集不可になった。(ID列をクリックしても編集欄が表示されないけど、それ以外の列は編集可能)
参考になれば幸いです。
- 投稿日:2021-01-29T04:44:47+09:00
typescript-fsa-redux-thunkの紹介
はじめに
redux-thunkをtypescriptで使っていた時に、色々調べた結果typescript-fsa-rudx-thunkというパッケージが便利だったので紹介します。
非同期処理のactionを作るactionCreatorを提供しています。typescript-fsaやredux-thunkの説明を詳しくすると長くなるので端折りながら。
typescript-fsaについて
そこでReduxの型定義をよしなにしてくれるパッケージはtypescript-fsaが有名。
import actionCreatorFactory from 'typescript-fsa'; const actionCreator = actionCreatorFactory(); //{foo: string}型がパラメータの型になる。 const somethingHappend = actionCreator<{foo: string}>('SOMETHING_HAPPENED'); console.log(somethigHappend({foo:"hoge"})); // { type: 'SOMETHING_HAPPENED', payload: {foo:"hoge"})payloadの型がジェネリックスで指定した型になります。
このようにactionCreatorを通して型の定義ができます。redux-thunkについて
redux-thunkはreduxで非同期処理を行う時に良く使用されるパッケージです。
redux-thunkを使わない場合
const somethingHappend = actionCreator<{foo: string}>('SOMETHING_HAPPENED'); fetch("http://example.com") .then(response => response.text()) .then(text => { store.dispatch(somthigHappend({foo:string}); });redux-thunkを使った場合
const somethingHappend = actionCreator<{foo: string}>('SOMETHING_HAPPENED'); const asyncSomethingHappend = () => (dispatch: any) => any{ fetch("http://example.com") .then(response => response.text()) .then(text => { dispatch(somthigHappend({foo:string}); }); } //asyncSomethingHappendはreduxのactionと同じようにdispatchできます。 store.dispatch(asyncSomethingHappend());redux-thunkを使うと非同期処理そのもの(thunkと呼ぶらしい)をdispatchできるようになりました。
redux-thunkはdispatchの引数に関数を入れれるようにしてくれます。typescript-fsa-redux-thunk
今までの説明では理解しやすくするために簡単な処理を書いていましたが、私が実際に非同期処理をする時はいつも複雑になります。
通信が始まったときの処理だとか、終わったときの処理だとか、エラー時の処理だとか、
ただでさえ、reduxやredux-thunkを理解するのが大変なのに、、、そこで、便利だったのがtypescript-fsa-redux-thunk。
簡単に説明すると非同期処理のaction(thunk)を作るactionCreaterを提供してくれています。import actionCreatorFactory from 'typescript-fsa'; import { asyncFactory } from 'typescript-fsa-redux-thunk'; //ジェネリックスで渡しているStateはReduxのstoreのstateと同じ型です。 const createAsync = asyncFactory<State>(create); interface LoginParams { email: string; password: string; } const login = createAsync<LoginParam,UserToken,CustomError>( 'Login', // 引数で取っているparamsがLoginPramになります。 async(params,dispatch) => { const res = await fetch("http://example.com/login",{ method: 'POST', body: JSON.stringfy(params) }) if(!res.ok) { throw new CustomError(); } //UserToken型を返します。 retrun res.json() as UserToken } ); const loginParam:LoginParam = {email:'hoge@example.om',password:'fuga'} store.dispatch(login(loginParam)); //login.asyncの中 //{ // started: f LOGIN_STARTED // done: f LOGIN_DONE // failed: f LOGIN_FAILED // type: 'LOGIN' //}typescript-fsa-redux-thunkのcreateAsyncは非同期処理のaction(thunk)を作るactionCreaterになります。
createAsyncで作られたthunkはdispatchされると、
started:非同期処理が始まったとき
done:非同期処理が完了したとき
faild:非同期処理が失敗したとき
上記の三つのactionがdispatchされる。
これらの三つのactionはlogin.asyncの中に定義されている。
いちいち自分で記述しなくてよいので、楽です。createAsyncの三つのジェネリックは順番に
1.createAsyncが作ったthunkの引数の型・createAsync第二引数の関数の第一引数
2.createAsyncの第2引数の返り値・doneアクションのparameter
3.エラーの型
になっている。一番うれしいのが、started・done・faildのアクションを定義してくれていること。
最後に
説明難しい。
- 投稿日:2021-01-29T01:15:39+09:00
Context / useContext を書きながら学ぶ
Reactの組み込みフックである
Context
とuseContext
の説明をします。また、
useContext
は、ReactのContext
と併用するので、Context
の解説もします。Contextとは
useContext
は、Context
というReactの仕組みを利用するために必要なフックです。なので、まずは
Context
について説明します。
Context
とは...と言っても様々な言い方ができるでしょう。
- 「状態」と「状態を変更するメソッド」を、propsを用いず、アプリケーション全体で取り回すことができるやつ
- Propsを利用せずに、様々な階層のコンポーネントに値を共有するReactのAPI
などなどですね。図で説明しましょう。
本をReactで表現するとします。
ホントは、もっといろいろなコンポネントがありえますが、今回は、Bookのデータ(仮に
bookData
とする)を<Page />
と<Title />
で、使いたいとします。すると、このようにpropsをバケツリレーして渡す方法もあります。ですが、
<Body />
と<Cover />
はそんなデータを使用しない。とか、もっと多階層で中間のコンポネントはbookData
をまったく使わないので、バケツリレーで渡す意味がない時ありますよね?もしくは、グローバルにどこでも取りまわせるstateを持ちたい時がありますよね?
そんな時は、
Context
の出番です。
Context
がデータストアの役割をはたし、propsを使わず直接bookData
を下層階層のコンポネントが使うことができるようにしたものです。
useContext
は、Context
の機能をさらにシンプルに使うことができるやつです。Contextを利用するために必要なもの
- Contextオブジェクト:
React.createContext
というReactのAPIの戻り値- Provider: Contextオブジェクトが保持しているコンポネント
- Consumer: Contextオブジェクトから値を取得しているコンポネント
サンプルコード
基本の状態は以下にします。(Bookを表すには雑すぎますが...ツリー構造さえわかればOKなので許してください)
Contextを利用して、下層コンポネントに、データを受け渡します。
以下の順番で書いていきます。
- createContext(
BookContext
)してexportしておく(Book コンポネントで実装)- Providerコンポネントを作成し、valueにオブジェクト(state)をセットします(Book コンポネントで実装)
- 下層コンポネントで、先ほど作成したBookContextをimportします(Title コンポネントで実装)
- importしたBookContextを使ってconsumerを作成(Title コンポネントで実装)
- Consumerコンポネント内でbook state の中身にアクセスできます
赤枠が、変更点です?
実行結果はこんな感じです。
propsで上の階層から渡していないのに、下階層でcreateContextしたデータが引っ張れていることがわかります。
それでは次に、
useContext
を使っていきましょう。useContextとは
useContext
を使った場合でも、Providerを使って値を渡す点は同じです。構文はこんな感じです。
const Contextオブジェクトの値 = useContext(Contextオブジェクト)Contextオブジェクトから、取得できる値は、Contextオブジェクトが保持しているProviderのvalueプロパティに指定された値です。
サンプルコード
<Cover />
とその子供の<Title />
は、createContextされたBookContext
とAuthorContext
コンテキストをPrivide
されているので、それぞれのデータを引っ張ることができます。このように、複数のコンテキストを扱うことも可能です。
サンプルコード
/components/Book.jsimport React, { createContext, useState } from "react"; import Cover from "./Cover"; import Body from "./Body"; const bookData = { author: "ryosuketter", title: "how to use React context", isbn: 12345, yearOfPublication: 2021 }; const authorData = { name: "ryosuketter", age: 35, gender: "male" }; export const BookContext = createContext(); export const AuthorContext = createContext(); const Book = () => { const [book, setBook] = useState(bookData); const [author, setAuthor] = useState(authorData); return ( <> <BookContext.Provider value={book}> <AuthorContext.Provider value={author}> <Cover /> </AuthorContext.Provider> <Body /> </BookContext.Provider> </> ); }; export default Book;components/Cover/Title.jsimport React, { useContext } from "react"; import { BookContext, AuthorContext } from "../../Book"; const Title = () => { const book = useContext(BookContext); const author = useContext(AuthorContext); return ( <div> <p>this is 「{book.title}」</p> <p>author is {author.name}</p> <p>age is {author.age}</p> </div> ); }; export default Title;特定のコンポネントで、Contextの追加や上書きをしたデータを作成して使うことも可能です。
サンプルコード
/components/Book.js// 上と同じ
components/Body/Page.jsimport Reac, { useContext } from "react"; import { BookContext } from "../../Book"; const Page = () => { const book = useContext(BookContext); const customedBookContext = { ...book, author: "ryosuke", publisher: "Qiita publications" }; return ( <div> <p>this is page</p> <p>author is {customedBookContext.author}</p> <p>publisher is {customedBookContext.publisher}</p> </div> ); }; export default Page;上2つのコードの実行結果
Context利用時の注意点(Context更新時に不要な再レンダーを招く)
Contextは使い方によっては、パフォーマンスの問題を引き起こす可能性があります。
なぜから、Provider内のすべてのConsumerは、Proverのvalueプロパティが更新するたびに再レンダリングするからです。
特に、以下の場合は注意です
- 再レンダリングされる Consumer の数が多い場合
- Consumerの子コンポネントのレンダリングコストが高い場合
不要な再レンダリングを防ぐ方法は、次の3つです。
- Contextオブジェクトの分割
- React.memoの利用
- useMemoの利用
ぜひ、試しながらやってみてください。
- 投稿日:2021-01-29T01:07:59+09:00
Reactのmap処理とkeyの使い方
Reactの配列処理
Reactを使うとフロントエンドで同じ繰り返しの値の処理を簡単に実装することができます。
事前知識
- HTML基礎
- JavaScriptの基礎
- Reactの基礎
これらの前提知識がある定で話していきます。
今回はReactのフレームワークのNext.jsで実装していきます!!npm init next-app アプリ名まずさくっとNext.jsを立ち上げます。
エラー出た方はnode -v yarn -v npm -vこちらを確かめましょう
次にcomponetsディレクトリーを作成し、その配下にitemsのコンポーネントのディレクトリを作成します。
cd アプリ名 mkdir components cd conmponents mkdir items cd items touch index.jsxitems/index.jsximport React from "react"; const ITEMS = [ { id: 1, title: "一番最初のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, { id: 2, title: "2番目のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, { id: 3, title: "3番目のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, ] const Items = () => { return ( <React.Fragment> {ITEMS.map((item) => { return ( <div> <p>{ item.title }</p> <p>{ item.body }</p> </div> ) })} </React.Fragment> ) } export default Itemsいったん実装します。
説明としてITEMSの配列を定義し、その中でループさせたい処理を書いていきます。
その後mapメソッドでITEMSの中身を返します。
(この時自分はmapの後のreturnを忘れて、永遠と表示されない自体がおきました、、、見逃さずに)Itemsコンポーネントはしっかりexportしましょう。
pages/index.jsimport Items from "../components/items" export default function Home() { return ( <> <Items></Items> </> ) }Itemsコンポーネントをインポートします。
<></>はの省略記法です。
どちらでも大丈夫ですが、書く際はどちらかに統一しましょう。
できたーー!!!
、、、、、、、むむっ
エラーが出ていますね。Warning: Each child in a list should have a unique "key" prop
keyがないよーと警告が出ています。https://reactjs.org/docs/lists-and-keys.html#keys-must-only-be-unique-among-siblings
参考記事
Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique. We can use the same keys when we produce two different arrays:配列の中のキーは兄弟間で一意である必要があります。
結論→親要素にkeyを設定すると解決する
pages/index.jsximport React from "react"; const ITEMS = [ { id: 1, title: "一番最初のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, { id: 2, title: "2番目のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, { id: 3, title: "3番目のタイトルが入ります", body: "テキストが入りますテキストが入りますテキストが入ります" }, ] const Items = () => { return ( <React.Fragment> {ITEMS.map((item) => { return ( <div key={item.id}> <p>{ item.title }</p> <p>{ item.body }</p> </div> ) })} </React.Fragment> ) } export default Itemsちなみにキーはindexでも渡すことができますが、こちらはあまり推奨されていないようです。