20220125のReactに関する記事は12件です。

Spreadjsの初期化で学ぶ、React非同期処理

何がしたいか エクセルライクなスプレッドをWebアプリに組み込めるライブラリSpreadjsがあります。 その初期化処理中はローディングを出して、初期化が終わったらスプレットを出す実装の中で、 非同期の概念が少し理解できたので、まとめてみました。 理解できないがとりあえず実現できたコード import GC from '@grapecity/spread-sheets' import { Column, SpreadSheets, Worksheet } from '@grapecity/spread-sheets-react' import '@grapecity/spread-sheets-resources-ja' import { useState } from "react" export const TestSheet = () => { const [isLoading, setIsLoading] = useState(true) const initSpread = (spread: GC.Spread.Sheets.Workbook) => { return new Promise<void>(resolve => { setTimeout(() => { // 3秒の重い処理を想定 resolve() }, 3000) }) } return ( <> {isLoading ? <h1>Loading....</h1> : <></>} <div style={{ visibility: isLoading ? 'hidden' : 'visible' }}> <SpreadSheets workbookInitialized={spread => { initSpread(spread).then(() => setIsLoading(false)) }}> <Worksheet > <Column /> </Worksheet> </SpreadSheets> </div> </> ) } 非同期難しい 残念ながら、私はこのコードがなぜ上手くいくのか、理解できませんでした。 Promiseじゃなくて、async/awaitを使用したいし、setTimeoutも不要だと思ってました。 そして、色々消したり、付け加えたりしたのですが、うまくいきませんでした。 そこで、書籍を読み漁りました。 読み漁りの結果 プログラミング TypeScript スケールするJavaScript アプリケーション開発 上記書籍より、少しイメージがつかめたので説明します。 ローディングのstateを変更するタイミング 重い処理が終わったタイミングでstateを変更すればよいので、 シンプルに重い処理の後に記述すればよく、.then()とか、awaitは不要です。 const initSpread = (spread: GC.Spread.Sheets.Workbook) => { return new Promise<void>(resolve => { setTimeout(() => { // 3秒の重い処理を想定 setIsLoading(false) //new !! resolve() }, 3000) }) } //省略 return ( <SpreadSheets workbookInitialized={spread => { initSpread(spread) //.then(() => setIsLoading(false)) }}> ) Promiseやasyncが非同期を生み出すという勘違い Promiseや、そのシンタックスシュガーであるasyncを付けるだけで非同期にはなりません。 非同期は、JavaScriptプラットフォームによって提供されているネイティブな非同期APIを呼び出す必要があります。 今回は、setTimeout()で非同期APIを呼び出しています。 そして、非同期APIを呼び出せば、その時点で、呼び出している関数(initSpread)も非同期になります。 よって、Promiseやasyncは不要です。 const initSpread = (spread: GC.Spread.Sheets.Workbook) => { setTimeout(() => { // 3秒の重い処理を想定 setIsLoading(false) }, 3000) } 待つ必要がなければ、await不要→asyncも不要 initSpreadはバックグラウンドで勝手に処理されて、最後にloadingのstateをオフにすればよいので、 待つ必要はありません。asyncはawaitを書く際に必要になるものなので、 待つ必要がなければ、何も記述する必要はありません。 非同期処理がただ駆け抜けるだけです。 まとめ ただでさえ、非同期がわかっていないのに、Reactのレンダリングするタイミングも相まって、 非同期の理解がなかなか進みませんでした。 レンダリング内でawaitすれば、レンダリングが止まるのも、当たり前だと理解できるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spreadjsでシートの初期化を非同期にする

何をしたいか Spreadjsの初期化処理って、業務によっては処理が重いと思います。 そこで、初期化している間は、ローディング画面を出します。 コード import GC from '@grapecity/spread-sheets' import { Column, SpreadSheets, Worksheet } from '@grapecity/spread-sheets-react' import '@grapecity/spread-sheets-resources-ja' import { useState } from "react" export const TestSheet = () => { const [isLoading, setIsLoading] = useState(true) const initSpread = (spread: GC.Spread.Sheets.Workbook) => { return new Promise<void>(resolve => { setTimeout(() => { // 3秒の重い処理を想定 resolve() }, 3000) }) } return ( <> {isLoading ? <h1>Loading....</h1> : <></>} <div style={{ visibility: isLoading ? 'hidden' : 'visible' }}> <SpreadSheets workbookInitialized={spread => { initSpread(spread).then(() => setIsLoading(false)) }}> <Worksheet > <Column /> </Worksheet> </SpreadSheets> </div> </> ) } 非同期難しい 残念ですが、私はこのコードを実装しましたが、なぜこのコードが上手くいくのか、理解できていません。 Promiseでなくて、async/awaitを使用したいし、setTimeoutも不要だと思っているのですが、どちらもうまくいきませんでした。 引き続き、学習していきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactについて一年ぶりにまとめてみた

はじめに 一年前にReactを使用してアプリ作成や学習を行い転職活動をしていたが、当時の僕はプログラミングやアプリケーションは動けばいいくらいのトンチンカンでまともにドキュメント読み込む力もありませんでした。 そして現在、エンジニア一年生として改めてReactの中で気になっているクラスコンポーネントと関数コンポーネントの違いやライフサイクル、状態管理、React HooksなどといったReact基礎について理解したく今回まとめてみました。 クラスコンポーネントと関数コンポーネントの違い まず結論から言うとpropsやstate、ライフサイクルの記述方法が違う点がありました。 (React Hooksが登場してからです。※React 16.8.0版リリース:2019/2/6) まず軽く前提としてUIを構築するのにコンポーネントという概念と、親から子コンポーネント間でデータを渡すpropsという概念があります。 詳しくはドキュメントのコンポーネントとpropsに記載してあります。 そしてコンポーネントを作成する際には、 クラスコンポーネント 関数コンポーネント の二つが存在します。 先程のpropsやstate、ライフサイクルの記述方法が違う点は、クラスコンポーネントでしか行えなかったstateやライフサイクル機能がReact Hooksが登場して以来、関数コンポーネントでの使用が可能になりました。 それまで関数コンポーネントの役割としてはViewを表示するための役割で、ステート管理している親コンポーネントからpropsとしてデータを受け取るステートレスとしてのコンポーネントでした。 実際にコード別で簡単にクラスコンポーネントと関数コンポーネントを比較してみました。 クラスコンポーネント import React from "react"; class ClassComponent extends React.Component { render() { return <h1>Hello, world</h1>; } } クラスコンポーネントを作成するのに、React.Componentを継承し必ずrender()を定義する必要があります。 render()については、ライフサイクルの箇所で説明します。 関数コンポーネント import React from "react"; function FunctionalComponent() { return <h1>Hello, world</h1>; } 関数コンポーネントに関しては、関数の中でJSXを記述することでコンポーネントの作成ができます。 state(状態)管理 コンポーネントを利用する時に設定できるStateという値があります。 Reactのstateや状態管理としては、主にRedux, useState, useContextなど様々なやり方があります。 useState useStateは、関数コンポーネントでのstate管理・更新をすることができます。 const [値, 更新する値] = useState(初期値) として扱うことができます。 useStateを使用して親コンポーネントで値(state)をもち、子コンポーネントでpropsで値を受け取るといった伝達が鉄板ですね。 しかし、規模が大きくなるにつれてコンポーネントツリーの階層が多くなりpropsのバケツリレーの連続で管理のメンテナンスやパフォーマンスが疎かになります。 そういった時に役立つのが、先程説明したReduxや次のuseContextを使用することでstate管理を円滑にすることができます。 useContext コンポーネントツリー内にpropsを経由せずとも値を渡すやり方がコンテキストと呼ばれる方法があります。主に多くのコンポーネントで扱う値をグローバルステート管理したい時に使用します。 注意したいのが、管理しているコンテキストの値の変や(コンテキストを設定した)コンポーネントがレンダリングすると渡したコンテキスト値も変更するので出来るだけシンプルな管理が必要になります。 useContextを使用する場合は以下が必要です。 createContext() オブジェクトのデフォルト値を引数に設定。 Provider providerで包みvalue値を決めることでコンテキストを設定します。 useContext() 引数にコンテキストを渡すことで使用可能。 const themes = { light: { background: "#eeeeee" }, dark: { background: "#222222" } }; // コンテキストオブジェクトの設定 const ThemeContext = React.createContext(themes.light); // 親コンポーネント function App() { return ( // Providerでコンポーネントを包み、valueの値を決めて設定 <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } // 子コンポーネント function Toolbar() { return ( <div> <ThemedButton /> </div> ); } // 子孫コンポーネント function ThemedButton() { // useContextの引数にコンテキストオブジェクトを設定することで使用可能。 const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background }}> I am styled by theme context! </button> ); クラスコンポーネントの場合は、 ちなみにクラスコンポーネントではconstructor内で、stateの管理を行い、setState()でstateを更新することができます。 詳しくは、コンポーネントのstateに記載してあります。 Redux 状態管理ライブラリと呼ばれており、本来¥コンポーネントでのstate管理を行いますがReduxではStoreを使用しコンポーネントを切り離してstate管理を行います。扱うデータが多い中規模・大規模での使用に特化しております。 reduxの内容をとてもわかりやすくまとめた記事がありました。 https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7 まだまだやり方たくさんある。。。 主に3つの状態管理を挙げましたが他にもReact HooksのuseReducer、GrahpQL利用時にはApollo Clientを使用するケースや状態管理ライブラリであるreduxと類似したRecoleやMobXなど、Reactには様々やり方があってサービスの規模感に応じて技術選定が必要になるので一年ぶりに凄みを感じました。 ライフサイクルとは まず簡単にライフサイクルとは、コンポーネントが生まれてから死ぬまでをサイクルとするように、 生成(Mounting)➡︎ 更新(Updating)➡︎ 削除(Unmounting)の繰り返すことを言います。 公式ドキュメントでは、以下のライフサイクル図が見れます。 主に使用されているライフサイクルメソッドは、以下になります。 ? Mounting constructor() マウントされる前に呼び出されるメソッドで、ローカルstateの初期化やイベントのバインドを行う。 render() 仮想DOMを構築するメソッド。もちろんコンポーネントに必須でもある。 componentDidMount() 最初のrender()が呼びだされる(マウント直後)メソッドで、ネットワークの通信処理に利用されることがある。 ? Updating render()上記の内容と同じ。 componentDidUpdate() 更新が完了したタイミングのメソッド。更新完了後のネットワークリクエストの通信時に利用する。 ? Unmounting componentWillUnmount() コンポーネントが破棄されるタイミング(アンマウント)のメソッド。ネットワークリクエストのキャンセルや作成したデータの加工(削除)に使用。 以上で説明したライフサイクルメソッドは、クラスコンポーネントでの使用になります。 ちなみに関数コンポーネントの場合はReact HooksのuseEffectを使用して、ライフサイクルを行うことができます。 React Hooks useEffect版のライフサイクル React HooksのuseEffectは、先程の上記で紹介したライフサイクルメソッドであるcomponentDidMount(), componentDidUpdate(), componentWillUnmount()を一つにまとめたメソッドです。 なので、useEfftctを使用することでライフサイクルを作成することができます。 useEfftctの使い方 useEffect(第一引数:()=> { 処理 }, 第二引数:[依存変数])として記述します。 第一引数のコールバック関数はレンダリング時に呼ばれ副作用の処理を行います。また、その処理の中で戻り値を関数としたものクリーンアップ関数と呼ばれてコンポーネントがアンマウントされた時に実行されます。 第二引数は、省略することもできます。配列の中身に依存させる変数を格納することで、変数が変更された場合に実行される仕組みになっております。 ※ここでいう副作用とはレンダリング時に関係のない処理のことを指しております。 import React, { useEffect, useState } from "react"; const Example = () => { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { console.log(count); }, 2000); // クリーンアップ関数 return () => { clearInterval(id); }; }, [count]); // countが依存変数 return <button onClick={() => setCount(count + 1)}>Click me</button>; }; export default Example; 上記の内容は、レンダリング時にcountの値がコンソールログに2秒後に表示➡︎countがプラスされるボタンをクリックすると再レンダリングされ2秒後にcountがプラスされているコンソールログが表示といった流れです。そして、コンポーネント破棄(アンマウント)されたらcountを解除するといったクリーンアップ関数が実行されます。 まとめ Reactのコンポーネント作成は、クラスコンポーネントと関数コンポーネントの概念がありstateやライフサイクルの記述がそれぞれある。 state管理においては、useState, useContextなどのHooksやReduxなど様々あり規模感に応じて使い分ける。 ライフサイクルはMounting ➡︎ Updating ➡︎ Unmountingのサイクル。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【アコーディオン】jQueryのslideToggleを生jsで再現してみた。Vue.jsやReactでも使用可

jQueryのslideToggleは数行で実装できるので便利でした。 今回、数行とはいきませんがそれなりにシンプルなコードでslideToggleの動きを再現してみました。 生jsの場合 See the Pen accrodion without jQuery by monji (@monji88) on CodePen. e.targetとnextElementSiblingを使用しています。 e.targetについてはこちらの記事にも書かせて頂いてます。 https://qiita.com/monji586/items/711bc6d78c41cf02c752 Vue.jsの場合 See the Pen Untitled by monji (@monji88) on CodePen. Vueなのでtransitionタグなど使おうかとも思いましたが、 普通に生jsのコードを使いまわしたほうがシンプルでした。 el:"#app"とかはVue2の書き方ですがmethodsの部分はVue3でもそのまま使えると思います。 Reactの場合 すみませんReactは触ったことないのでコードを用意してません。 でも生jsのコードを転用してもらえれば動くと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのJestのテストでuseStateを使用する方法

背景 業務でReactのテストをJestで作成した際、HooksのuseStateでエラーになり、かなり悩んだのでその解決策をまとめておく。 開発言語 (バージョン情報) React.js (17.0.2) Next.js (11.1.2) TypeScript (4.4.3) Jest (27.2.2) 解決策 sample.spec.tsx import React, { useState as useStateMock } from 'react'; import TestRender from 'react-test-renderer'; jest.mock('react', () => ({ ...jest.requireActual('react'), useState: jest.fn(), })); describe('Snapshot test', () => { const setState = jest.fn(); beforeEach(() => { (useStateMock as jest.Mock).mockImplementation(init => [init, setState]); }); test('test case 1', () => { const [open, setOpen] = useStateMock(true); const tree = TestRenderer.create( <> {open} test </>, ).toJSON(); expect(tree).toMatchSnapshot(); }); }); 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Agora SDK x Next.js

はじめに Next.jsの人気が高まっているということで、Agora SDKでも動作するかサンプルコードを書いてみました。 Next.jsについての詳しい知見はあまり持っていませんので、お見苦しい書き方があるかもしれませんがご了承ください。 サンプルコード サンプルコードはこちらAgoraIO-NextJSに公開しております。 機能としては、単純に自映像を表示してパブリッシュするだけのものとなっています。 構成 構成はいたってシンプルで、実装はすべてVideoComponents.jsにまとまっています。 実装内容 index.js import dynamic from 'next/dynamic' const DynamicComponent = dynamic(() => import('../components/VideoComponent').then((mod) => mod.VideoComponent), { ssr: false } ) export default function Home() { return ( <div> <DynamicComponent /> </div> ) } index.jsではSSRを無効にする実装をしています。AgoraSDKがWindowオブジェクトを利用している為、 ここでSDKをimportするとwindow is not definedが発生してしまいます。よって、SDKの実装についてはcomponentにまとめています。 (AgoraSDK以外にも依存ライブラリが原因でこのエラーに遭遇する場合もよくありそうです) 公式ドキュメントの解説はこちらにあります。 components/VideoComponent.js import AgoraRTC from "agora-rtc-sdk-ng"; let rtc = { localAudioTrack: null, localVideoTrack: null, client: null }; let options = { appId: YOUR APP ID, channel: "nextjs", token: null, uid: 123456 }; async function join(){ rtc.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); await rtc.client.join(options.appId, options.channel, options.token, options.uid); rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack(); rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack(); rtc.localVideoTrack.play("local_video"); await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack]); } export function VideoComponent() { return ( <div> <h2>Agora Video Web SDK Quickstart Next.js</h2> <div> <div> <button type="button" id="join" onClick={() => join()} >JOIN</button> </div> <div id="local_video" style={{width: '320px', height: '240px'}}></div> </div> </div> ) } Joinボタンとビデオ表示領域だけ用意したシンプルなHTMLと、カメラマイク取得してパブリッシュするというシンプルな実装になっています。 以下のような実行結果になります。 無事に動作してくれました。 Windowオブジェクトの取り扱いがポイントとなりそうです。 最後に agora.ioに関するお問い合わせはこちらから
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像の狙った座標に他の画像をぴったり重ねる方法

Webアプリにて、カード置き場の画像に描かれた枠内に、カード画像をきっちり並べる、というコンポーネントを実装する機会があったので、そのとき用いた方法を記します。 (フレームワークにReactを用いましたが、カードの配置自体はCSSの機能で実装可能です) 実装イメージ: 用いる画像 カードの画像: cardBack.png / cardFace.png ボード(カード置き場)の画像: board.png 予めボード画像の大きさやカードの座標を以下のように調べておきます 実装コード カード1枚の場合 まずは簡単に、1枚だけカードを配置してみます。 Boardクラス ボード画像を、ウィンドウの高さに合わせて表示します(height: 100vh) ボード画像とカード画像を重ねて表示するため、基準となる親要素のdivを作ります divを、子要素であるボード画像のサイズに合わせます(display: inline-block, height: 100vh) divを基準にカードを配置するため、position: relativeとします Cardクラス カードのスタイルをcreateCardImageStyle関数を用いて作成します 親要素であるdiv(= ボード画像と同じサイズ)を基準に配置するため、position: absoluteとします カードの位置は、ボードの元画像における座標と、ボードの元画像のサイズから、割合を計算して指定します。割合で指定することにより、画面上の画像サイズに依存せずに位置を指定することができます(top、leftを%指定) カードのサイズも同様に、ボードの元画像における枠の比率に合わせます(widthを%指定) ここではカードの左上ではなく中央を基準に揃えるため、カードの高さの半分だけ上に、幅の半分だけ左にずらします(transform: translate(-50%, -50%)) index.js import React from 'react'; import ReactDOM from 'react-dom'; const cardImage = "./cardBack.png"; // カード画像 const boardImage = "./board.png"; // ボード画像 const boardImageWidth = 1757; // ボード画像の幅(ピクセル数) const boardImageHeight = 2126; // ボード画像の高さ(ピクセル数) const firstLeftPosition = 318; // カードを置く座標(左から何ピクセル目か) const firstTopPosition = 395; // カードを置く座標(上から何ピクセル目か) const frameWidth = 340; // ボード画像に描かれたカード用の枠の幅(ピクセル数)。カード画像の幅ではないことに注意 function createCardImageStyle(leftPosition, topPosition, frameWidth) { return { position: "absolute", top: (topPosition / boardImageHeight) * 100 + "%", left: (leftPosition / boardImageWidth) * 100 + "%", width: (frameWidth / boardImageWidth) * 100 + "%", transform: "translate(-50%, -50%)", }; } class Card extends React.Component { render(){ return ( <img src={cardImage} style={createCardImageStyle(firstLeftPosition, firstTopPosition, frameWidth)} /> ); } } class Board extends React.Component { render(){ return ( <div style={{display: "inline-block", position: "relative", height: "100vh"}}> <img src={boardImage} style={{height: "100vh"}} /> <Card/> </div> ); } } ReactDOM.render( <React.StrictMode> <Board /> </React.StrictMode>, document.getElementById('root') ); 実行結果: カード複数枚の場合 1枚のときと考え方は変わりません。 for文を用いて、配列にカードを格納します。 Boardクラス Cardの代わりにCardsコンポーネントを使います Cardsクラス for文を用いて、縦横3つずつカードを作り、cards配列に格納します 必要な情報を、propsを経由してカードへ渡します。keyは要素の識別用です。 srcは使用するカード画像のソースです。ここでは交互に表裏になるよう並べるため、i % 2 == j % 2による判定を入れています leftPositionは、1枚目のカードの座標(firstLeftPosition)に、対象のカードまでの差分(leftPositionDiff * i)を足して求めます topPositionも同様です Cardクラス props経由で取得した情報を用いて、カードを表示します index.js import React from 'react'; import ReactDOM from 'react-dom'; const cardBackImage = "./cardBack.png"; // カード画像 const cardFaceimage = "./cardFace.png"; // カード画像2 const boardImage = "./board.png"; // ボード画像 const boardImageWidth = 1757; // ボード画像の幅(ピクセル数) const boardImageHeight = 2126; // ボード画像の高さ(ピクセル数) const firstLeftPosition = 318; // 1枚目のカードを置く座標(左から何ピクセル目か) const firstTopPosition = 395; // 1枚目のカードを置く座標(上から何ピクセル目か) const leftPositionDiff = 554; // カード同士の座標の差 const topPositionDiff = 682; // カード同士の座標の差 const frameWidth = 340; // ボード画像に描かれたカード用の枠の幅(ピクセル数)。カード画像の幅ではないことに注意 function createCardImageStyle(leftPosition, topPosition, frameWidth) { return { position: "absolute", top: (topPosition / boardImageHeight) * 100 + "%", left: (leftPosition / boardImageWidth) * 100 + "%", width: (frameWidth / boardImageWidth) * 100 + "%", transform: "translate(-50%, -50%)", }; } class Card extends React.Component { render(){ return ( <img src={this.props.src} style={createCardImageStyle(this.props.leftPosition, this.props.topPosition, frameWidth)} /> ); } } class Cards extends React.Component { render(){ const cards = []; for (let j = 0; j < 3; j++){ for (let i = 0; i < 3; i++){ cards.push(<Card key={"card" + i + "_" + j} src={(i % 2 == j % 2)?cardBackImage:cardFaceimage} leftPosition={firstLeftPosition + leftPositionDiff * i} topPosition={firstTopPosition + topPositionDiff * j} />); } } return ( <span> {cards} </span> ); } } class Board extends React.Component { render(){ return ( <div style={{display: "inline-block", position: "relative", height: "100vh"}}> <img src={boardImage} style={{height: "100vh"}} /> <Cards/> </div> ); } } ReactDOM.render( <React.StrictMode> <Board /> </React.StrictMode>, document.getElementById('root') ); 実行結果: ウィンドウサイズを変更しても、ボード画像に合わせてカードが配置されます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React(Typescript)でfirebase authを使うときに参考にした記事をピックアップ【メモ】

firebase authで認証機能を実装したのですが、Typescriptでコーディングする方法を説明した記事があまりなかったので、複数の記事を参考にしました。 メモがてらリンクをシェアさせてください! 参考記事 firebase auth ver8で記載 firebase auth ver9で記載 かつ typescript 画面遷移の方法やsignupの方法は書いていないので、こちらの記事ではTypescriptの書き方とver9の書き方を参考にしました。 ver8 => ver9への変更点記載 おまけ:Next.jsのアクセス制限のやり方 private routerを使用できないので、上記の記事を参考にしてアクセス制限を設定しました! 最後に ほとんどの記事はver8で記載があるので、ver9を使いたい人はfirebase authのドキュメントと照らし合わせながら参考記事を見るのがいいと思います。 個人的にはver9の方が好き! (処理速度もver9の方が早いみたいです。速度の違いを人間が感じられるかは知らんけど。) 覚えてたら、firebase authを使ったプロジェクトが完成した時にgithubリンクをここに貼ろうと思います! firebase auth×React(Typescript)で困っている人の検索に引っかかって、お役にたてますように。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jsxとvueの条件によってタグを表示させる方法

概要 最近react,vueを触る機会が多いので、メモするほどのことでもないのですが、 初心者やreact,vueってどんな感じなの?って人用に書かせてもらいます。 (シリーズ化して、小出しに記事にしていくかも?) vueでの条件によって表示させる方法 vueでは表示したい要素のタグの属性にv-ifを追加することで 要素を表示するか、しないかを表現できます。 <template> <div> <span v-if="flag">表示されます</span> <span v-else>表示されません</span> </div> </template> <script> export default { data() { return { flag: true } } } </script> vueでの出しわけ方法は分かりやすく、 タグにv-if="~~"というディレクティブ(※)を追加することで、簡単に出しわけができます。 もちろん、v-else-ifも使えます。 ※ DOM要素に対して何かを実行することをvue側に伝えるものと覚えておくといいかもしれないです。 jsxでの条件によって表示させる方法 jsxの場合は以下になります。 const flag = true; return ( <div> {flag && <span>表示されます</span>} // or {flag ? <span>表示されます</span> : <span>表示されない</span>} </div> ) jsxの場合は{}を記述して、中に条件を書くことでタグの出しわけができます。 flagの管理などはstateと呼ばれるものを使用したりしますが、それはまた別の機会に。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + TypeScript: Facebookの状態管理ライブラリRecoilを使ってみる

RecoilはReactの状態を管理するライブラリで、Facebook改めMetaが実験的に開発しています。本稿執筆時の最新バージョンは0.5.2です。状態をひとまとめにするのではなく、ひとつひとつ細かく分けて管理します。ざっくりとお伝えするなら、useStateの状態をコンポーネントツリー内にまたがって共有するイメージです。 本稿は公式サイトの「Getting Started」で紹介されたコードサンプルを、モジュールに分けた作例に書き替えて解説します(サンプル001)。また、TypeScriptによる型づけも加えました。 サンプル001■React + TypeScript: Recoil example >> CodeSandboxへ React + TypeScriptのひな形アプリケーションをつくる Reactアプリケーションのひな形は、Create React Appでつくることにします。オプションとして--template typescriptを加えれば、TypeScriptの環境が簡単に加わえられて便利です(「Create React AppでTypeScriptが加わったひな形アプリケーションをつくる」参照)。 インストール Recoilは、npmあるいはyarnでつぎのようにインストールしてください。 npm install recoil yarn add recoil コンポーネントツリーを<RecoilRoot>でつつむ まず、状態を共有したいコンポーネントツリーのルートは、<RecoilRoot>で包んでください。すべての子孫コンポーネントから、同一の状態が使えるようになります。ここでは、親コンポーネントをCharacterCounterとしました。 src/App.tsx import { RecoilRoot } from 'recoil'; import { CharacterCounter } from './CharacterCounter'; function App() { return ( <RecoilRoot> <CharacterCounter /> </RecoilRoot> ); } export default App; atomで状態を定める atom()は、状態をひとつひとつ定める関数です。<RecoilRoot>に包まれたツリー内のすべてのコンポーネントから、同じ状態の値が読み書きできます。atomの値を参照するコンポーネントは、暗黙的に購読対象となる仕組みです。atomが更新されると、購読対象のコンポーネントは再描画されます。atomに渡すオプションオブジェクトには、一意の識別子keyとデフォルト値defaultを与えてください。 src/textState.ts import { atom } from 'recoil'; export const textState = atom({ key: 'textState', // 他のatomやselectorに対して一意のID default: '', // デフォルト値(初期値) }); <RecoilRoot>に包んだ親コンポーネントCharacterCounterには、このあと定めるふたつの子コンポーネント(TextInputとCharacterCount)が加えられます。 src/CharacterCounter.tsx import { TextInput } from './TextInput'; import { CharacterCount } from './CharacterCount'; export const CharacterCounter: React.VFC = () => { return ( <div> <TextInput /> <CharacterCount /> </div> ); }; コンポーネントからatomの読み書きをするために用いるのがuseRecoilState()フックです。構文はuseStateと同じで、コンポーネントツリーの外にある状態を共有して使えます。 状態がコンポーネントの外にあることを除けば、useStateフックとやっていることは変わりません。<input type="text">要素に入力した値が設定関数setText()で状態を書き替え、状態変数textの値はテキストとして表示されます。 src/TextInput.tsx import { useRecoilState } from 'recoil'; import { textState } from './textState'; export const TextInput: React.VFC = () => { const [text, setText] = useRecoilState(textState); const onChange: React.ChangeEventHandler<HTMLInputElement> = (event) => { setText(event.target.value); }; return ( <div> <input type="text" value={text} onChange={onChange} /> <br /> Echo: {text} </div> ); }; selectorで状態の値に手を加えて返す 複数のatomから参照した値に手を加えて返すのがselector()です。他のselectorから値を得ることもできます。派生の状態をつくる純粋な関数です。引数として渡すオプションオブジェクトのget()に定めたコールバックは、状態の値にもとづいて処理した結果を返します。引数から取り出すget()が値を参照するための関数です。 charCountStateはtextStateからtextを得て、その文字数を返します。 src/charCountState.ts import { selector } from 'recoil'; import { textState } from './textState'; export const charCountState = selector({ key: 'charCountState', // 他のatomやselectorに対して一意のID // 状態の値にもとづいて処理した結果を返す get: ({ get }) => { const text = get(textState); return text.length; }, }); selectorの返す値を読み込むには、フックuseRecoilValue()を用いてください。コンポーネントCharacterCountが表示するのは、textStateに設定された文字数です。でき上がった作例の動きは、冒頭のサンプル001でご確認いただけます。 src/CharacterCount.tsx import { useRecoilValue } from 'recoil'; import { charCountState } from './charCountState'; export const CharacterCount: React.VFC = () => { const count = useRecoilValue(charCountState); return <>Character Count: {count}</>; };
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsのrouter.queryでURLの動的パラメータを取得するときの注意

業務で、useEffect内でNext.jsのuseRouterを使ってクエリパラメータを取得しようとした際、意図する通りの挙動をしておらず戸惑ったので備忘録としてまとめておきます。 通常pages/blogs/[id].tsx のようなファイルにおいてAPIからデータを取得したい場合、getStaticPaths・getStaticPropsを使うかと思いますが、今回は画面描画してからバックグラウンドでAPIリクエストを行ってデータ取得する必要があり、そこで見つけたのがuseRouterのrouter.queryでした。 useRouterとは そもそもuseRouterとは、Next.jsが用意しているhooksの一つで、routeに関する色んな情報を持つrouterオブジェクトへのアクセスを提供するものです。 アプリの関数コンポーネント内のrouterオブジェクトにアクセスする場合は、useRouterフックを使用できます。 useRouterはReactフックです。 https://nextjs.org/docs/api-reference/next/router#userouter routerオブジェクトはいくつもの便利なメソッドやプロパティを持ちますが、その中のひとつにqueryがあります。 router.queryの使い方 router.query とするだけで、オブジェクト形式で動的ルーティングの中身を取得できます。 ちなみに以下の例のようにクエリパラメータが数字の場合も、string型で取得されます。 import { useRouter } from 'next/router' // 関数コンポーネント内にて const router = useRouter() console.log(router.query) console.log(router.query.id) // 出力結果(localhost:3000/blogs/1 の場合) // { id: '1' } // 1 useEffect内でrouter.queryを使ってみる pages/blogs/[id].tsxのuseEffect内でrouter.queryを使ってみます。 const router = useRouter() useEffect(() => { const routeId = router.query.id console.log(routeId) }, []) // undefined 出力されたのは undefinde でした。 どうやら、router.queryの取得はuseEffectの発火タイミングより後のようです。 【注】ただし、チームの中には上記のコードでもundefindeにならずqueryが取れている方もいましたし、私の環境でも時によってはqueryが取れることもありました。いずれにせよ不安定なものと思われるので、以下の対処をしておくのが無難なのかなと思います。 (何かご存じの方はぜひご指導・ご指摘ください。) useEffectの依存配列にrouterを指定する 依存配列にrouterを入れてみました。 const router = useRouter() useEffect(() => { const routeId = router.query.id console.log(routeId) }, [router]) // 出力結果(localhost:3000/blogs/1 の場合) // undefined // 1 すると、一度undefindeと出た後にqueryが取れてコンソールに出力されました。 これで目的を果たせる場合もあるかもしれません。 しかし今回私が実装したかった要件は、取得したルートパラメーターを使ったパスでAPIへリクエストを送るというものです。 undefinedを取得してしまうと '/api/blogs/undefined'のパスにリクエストを送ってしまい、これは不都合でした。 isReadyを使ってrouter情報が準備されているか判別する 辿り着いたコードは以下の通りです。 const router = useRouter() useEffect(() => { if (router.isReady) { const routeId = router.query.id console.log(routeId) } }, [router]) // 出力結果(localhost:3000/blogs/1 の場合) // 1 routerオブジェクトの持つ isReady というboolean値が最適でした。 isReady:boolean ルーターフィールドがクライアント側で更新され、使用できる状態になっているかどうか。useEffectメソッド内でのみ使用する必要があり、サーバーで条件付きでレンダリングするためには使用しないでください。 https://nextjs.org/docs/api-reference/next/router#router-object なぜundefindeが取得されていたのか queryの取得タイミングはuseEffectの発火よりも後なのだということは何となく察しましたが、どういう仕組みなのか気になります。 Next.jsのドキュメントのダイナミックルーティングについての箇所に、以下のように書かれていました。 Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}). After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object. Pre-renderingの間はqueryは空のオブジェクトであり、hydrationのプロセスが終わった段階でqueryオブジェクトが提供されると書いてあります。 getStaticPropsやgetSererSidePropsの処理が終わるまでは、queryは空なのですね。 hydrationという単語は初めて知りましたが、Next.jsの公式の図を見るとイメージは湧きました。 引用:https://nextjs.org/learn/basics/data-fetching/pre-rendering 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React × TypeScript 環境構築

はじめに 今回はcreate-react-appでReact×TypeScriptプロジェクトを作成したあと、 開発をさらにスムーズにする以下の4つのライブラリをインストールします。 ESLint コーディングルールを厳密に指定するツール Prettier コード自動整形ツール hot-loder ソース保存時自動でブラウザをリロードするツール type React固有の型を登録するツール 上記はいずれも現場でよく使われるライブラリなので今回も導入したいと思います。 動作環境 macOS Catalina node: 16.13.0 npm: 6.14.8 yarn: 1.22.10 React×TypeScriptプロジェクト作成 ターミナル $ npx create-react-app react-ts-project --template typescript おなじみcreate-react-appの末尾に--template typescriptを付けるだけ。 react-ts-projectはお好きなプロジェクト名に変更して大丈夫です。 最後にHappy hacking! と表示されればプロジェクト作成は完了です。 起動確認 作成されたディレクトリに入り起動確認。 ターミナル $ cd react-ts-project $ yarn start http://localhost:3000で以下の画面が確認できればOKなので、control + cで一旦サーバーは止めて大丈夫です。 ESLintのインストール まずは下記コマンドで.eslintrc.jsを作成します。 ターミナル $ yarn run eslint --init その際以下9問の質問に答えます。 ターミナル ? How would you like to use ESLint?     To check syntax only     To check syntax and find problems ❯ To check syntax, find problems, and enforce code style を選択 ? What type of modules does your project use? ❯ JavaScript modules (import/export) を選択 CommonJS (require/exports) None of these ? Which framework does your project use? ❯ React を選択 Vue.js None of these ? Does your project use TypeScript? ❯ Yes を選択 ? Where does your code run? ❯ Browser を選択 Node ? How would you like to define a style for your project? ❯ Use a popular style guide を選択 Answer questions about your style ? Which style guide do you want to follow? ❯ Airbnb: https://github.com/airbnb/javascript を選択 Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo ? What format do you want your config file to be in? ❯ JavaScript を選択 YAML JSON ...中略... ? Would you like to install them now with npm? ❯ Yes を選択 作成完了したら下記コマンドでESLintをインストール。 ターミナル $ yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser 末尾につけている@typescript-eslint/parserは ESLintがTypeScriptを理解する為に必要なプラグインなので一緒にインストールしてます。 今回はAirbnbのスタイルガイドを導入するので以下のコマンドを入力し、質問には「y」を選択します。 ターミナル $ npx install-peerdeps --dev eslint-config-airbnb It seems as if you are using Yarn. Would you like to use Yarn for the installation? (y/n) ❯ y を選択 続いて.eslintrc.jsを以下のように編集します。 .eslintrc.js module.exports = { env: { browser: true, es2021: true, }, extends: [ 'plugin:react/recommended', 'airbnb', // ----↓追記↓---- 'airbnb/hooks', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'prettier', // ----↑追記↑---- ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 12, sourceType: 'module', // ----↓追記↓---- tsconfigRootDir: __dirname, project: ['./tsconfig.json'], // ----↑追記↑---- }, plugins: [ 'react' '@typescript-eslint', ], // ----↓追記↓---- ignorePatterns: [ '.eslintrc.js' ], // ----↑追記↑---- rules: { // ----↓追記↓---- 'no-use-before-define': "off", "@typescript-eslint/no-use-before-define": "off", 'import/prefer-default-export': "off", 'import/extensions': [ 'error', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never', }, ], 'react/function-component-definition': [ 2, { namedComponents: 'arrow-function', }, ], 'react/jsx-filename-extension': [ 'error', { extensions: ['.jsx', '.tsx'], }, ], 'react/react-in-jsx-scope': 'off', 'no-void': [ 'error', { allowAsStatement: true, }, ], // ----↑追記↑---- }, // ----↓追記↓---- settings: { 'import/resolver': { node: { paths: ['src'], extensions: ['.js', '.jsx', '.ts', '.tsx'] }, }, }, // ----↑追記↑---- }, ディレクトリルート(私の場合はreact-ts-project配下)に.eslintignore を作成し以下のように記述します。 .eslintignore build/ public/ **/node_modules/ *.config.js .*lintrc.js /*.* prettier prettierをインストールします。 ターミナル $ yarn add -D prettier eslint-config-prettier ディレクトリルートに.prettierrc.js を作成してフォーマットのルールを決めます。 .prettierrc.js module.exports = { printWidth: 120, singleQuote: true, semi: false, } 今回は上記のように設定しますが、お好みでカスタマイズ可能です。 Prettier公式:Options - Prettier hot-loader hot-loderをインストール。 ターミナル $ yarn add react-dom@npm:@hot-loader/react-dom type typeをインストール。 ターミナル $ yarn add -D @types/react ソースを編集 最後に、エラーを解消するためにソースコードを編集します。 src/App.test.tsx // 以下を削除 import React from 'react'; src/App.tsx import React from 'react'; // ↓に変更 import { VFC } from 'react'; // 中略 function App() { // ↓に変更 const App: VFC = () => ( // ↓削除 return ( // 中略 // ↓削除 } src/reportWebVitals.ts const reportWebVitals = (onPerfEntry?: ReportHandler) => { // ↓に変更 const reportWebVitals = (onPerfEntry?: ReportHandler): void => { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { // ↓に変更 void import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 下記コマンドで再度サーバーを立ち上げます。 ターミナル $ yarn start これで最初と同じReactの画面が表示されれば環境構築完了です! お疲れさまでした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む