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

Trello っぽいのを作りたい

最近 MERN Stack と呼ばれる技術スタックをを学んだ。 ( MERN: MongoDB, Express, React, Node ) せっかく学んだので MERN な構成で Trello っぽいのをつくってみる。 段階的に作っていく。 ① ToDoItem の CRUD ② ユーザの認証 ③ Category の CRUD ④ ToDoItem を Draggable にする ⑤ Board の CRUD ⑥ ユーザの Board への招待 開発ログ ① ToDoItem の CRUD 2021/06/15 MERN のそれぞれはある程度わかったけど Express と React をどうつなげるのかよくわかってない。 とりあえずそれぞれで ToDoItem の操作をできるようにして、つなげ方ググりながら進めていこうと思う。 ちなみに ToDoItem は ToDo の一個一個を想定してる。 Express 側の Item の GET と POST はできるようになった。 https://github.com/RyuGotoo/trello-clone/commit/0083aac0f508d438e188ce354af5f5865f9eb91f ちょっとルーティングの書き方で詰まったけど、いい感じ! 次は React 側の Get と Post を書いていく。 2021/06/16 React 側の GET と POST できた! client と server をつなぐのに苦戦した。 勉強した教材に繋ぎ方までは入ってなかったんだよね。 MERN それぞれを勉強するって感じの教材だった。 でも、なんとかできた! axios いいね。 フロントエンドは最近勉強し始めたばかりだから新しいことたくさんあって楽しい。 次は Item の UPDATE と DELETE を実装していく。 DELETE はできたけど UPDATE に苦戦中。 別ページに遷移してそこで編集するんじゃなくて、Trello みたいにテキストをクリックしたら編集が開始されるようにしたい。 contenteditable 属性をつければいけると思ってたけど、なんか動かなかった。 なんかそれようのライブラリが必要なのかな。 今回使う予定の技術スタック MERN Stack (MongoDB, Express, React, Node) mongoose MongoDB Atlas Material UI やりたいけど今はやらない技術スタックもある。 Redux, Testing, CI/CD とか。 これらはまだ良くわかってなくて別途勉強が必要で、今後勉強する予定ではあるけど、今はやらないでおこうと思ってる。 とりあえず学んだことだけでなんか作ってみるのが必要な時期だと個人的に思ってる。 勉強は楽しいけどずっとやってても自分ではなにも生み出せないから、下手でも作ってみるのがいい気がしてる。 伏黒も下手な領域展開してたけど、あんな感じ。 ああいうのが結局成長にはいいんじゃないかなって思ってる。 だから、今回は Redux とか使ってないけど、勉強した知識を総動員してひとつアプリを作ってみる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Trelloっぽいのを作りたい

最近 MERN Stack と呼ばれる技術スタックをを学んだ。 ( MERN: MongoDB, Express, React, Node ) せっかく学んだので MERN な構成で Trello っぽいのをつくってみる。 段階的に作っていく。 ① ToDoItem の CRUD ② ユーザの認証 ③ Category の CRUD ④ ToDoItem を Draggable にする ⑤ Board の CRUD ⑥ ユーザの Board への招待 開発ログ ① ToDoItem の CRUD 2021/06/15 ~ MERN のそれぞれはある程度わかったけど Express と React をどうつなげるのかよくわかってない。 とりあえずそれぞれで ToDoItem の操作をできるようにして、つなげ方ググりながら進めていこうと思う。 ちなみに ToDoItem は ToDo の一個一個を想定してる。 Express 側の Item の GET と POST はできるようになった。 https://github.com/RyuGotoo/trello-clone/commit/0083aac0f508d438e188ce354af5f5865f9eb91f ちょっとルーティングの書き方で詰まったけど、いい感じ! 次は React 側の Get と Post を書いていく。 今回使う予定の技術スタック MERN Stack (MongoDB, Express, React, Node) mongoose MongoDB Atlas Material UI やりたいけど今はやらない技術スタックもある。 Redux, Testing, CI/CD とか。 これらはまだ良くわかってなくて別途勉強が必要で、今後勉強する予定ではあるけど、今はやらないでおこうと思ってる。 とりあえず学んだことだけでなんか作ってみるのが必要な時期だと個人的に思ってる。 勉強は楽しいけどずっとやってても自分ではなにも生み出せないから、下手でも作ってみるのがいい気がしてる。 伏黒も下手な領域展開してたけど、あんな感じ。 ああいうのが結局成長にはいいんじゃないかなって思ってる。 だから、今回は Redux とか使ってないけど、勉強した知識を総動員してひとつアプリを作ってみる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react開発中にParsing error: Unexpected token, expected ";"

何をしたかったか react typescriptで開発中 ログインユーザーの情報をcontext内に保持する処理を書いてた createContextの引数{}にtypeをアサーションしたかった コード export type LoginUserContextType = { loginUser: User | null; setLoginUser: Dispatch<SetStateAction<User | null>>; }; export const LoginUserContext = createContext<LoginUserContextType>({} as LoginUserContextType); エラー文原文 Failed to compile src/providers/LoginUserProvider.tsx Line 10:72: Parsing error: Unexpected token, expected "," 8 | }; 9 | > 10 | export const LoginUserContext = createContext<LoginUserContextType>({} as LoginUserContextType); | ^ 11 | エラー文訳(Deep L翻訳) 構文解析エラーです。予期しないトークン、期待される "," 原因 何かしらのルールで許可されていない 改善案 .eslintrc.ymlに下記を追加 extends: [ 'react-app', 'react-app/jest' ] 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】React クイズ!!

はじめに いきなりですがクイズです!! React.js初心者向けのクイズを3問考えました。 クイズは、Reactの関数コンポーネントを用いたstateとライフサイクルに関する問題です。 クイズの答えは、記事の最後に記載しています。 第1問: 以下の様なコードを実行した場合、どのような順番に実行されるでしょうか? A〜Cの回答で答えよ。 import React, {useState, useEffect} from 'react' const Button = () => { const [count, setCount] = useState(0) useEffect(()=> { // ① console.log("①") return ()=> { // ② console.log("②") } }, []) return ( // ③ <div> {count} </div> ) } export default Button 回答: A. ① → ② → ③ B. ③ → ① → ② C. ② → ③ → ① 第2問: 以下の様なコードがあった場合、countの値はいくつになるでしょうか? import React, {useState, useEffect} from 'react' const Button = () => { const [count, setCount] = useState(0) useEffect(()=> { setCount(count + 100) setCount(count + 1000) setCount(count + 10000) setCount(count + 10000) setCount(count + 100000) setCount(count + 1000000) return ()=> { console.log("clean up") } }, []) return( <></> ) } export default Button 回答: A. 1111100 B. 100 C. 1000000 D. 1121100 第3問: 以下の様なコードがあった場合、初回マウント時の正しい実行順序を答えなさい。 import React, {useState, useEffect} from 'react' const Button = () => { const [count1, setCount1] = useState(0) const [count2, setCount2] = useState(0) const [count3, setCount3] = useState(0) useEffect(()=> { // ① console.log("update count1") }, [count1]) useEffect(()=> { // ② console.log("update count3") }, [count3]) useEffect(()=> { // ③ console.log("update") return ()=> { console.log("clear") } }, []) useEffect(()=> { // ④ console.log("update count2") }, [count2]) return( <></> ) } export default Button 回答: A. ③ → ① → ④ → ② B. ① → ② → ③ → ④ C. ③ → ① → ② → ④ D. ④ → ③ → ② → ① - - - - - - - - - - - 問題の答え 第1問の答え: B. ( ③がclassにおけるrender()、①がcomponent Did Mount()、②がcomponent will unmount()に該当する。) 第2問の答え: C. 第3問の答え: B. コードは上から順番に実行されます。component Did Mount()後のアップデートでは、①→②→④の順に実行されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jestで同じファイル内でfunctionがfunctionを呼ぶ時にどうやってstubするか?

問題 utils.ts function a() { return "a" } function b() { return a() } export { a, b } 例えばこういう風にb()がa()を呼ぶような場合、 utils.test.ts import {a, b} from "./utils" jest.mock("./utils", () => { const original = jest.requireActual("./utils"); return { ...original, a: jest.fn().mockReturnValue("modified a") }; }); describe("utils", () => { it("sholuld use mocked a()", () => { expect(b()).toEqual("modified a") expect(a).toHaveBeenCalledTimes(1) }); }); こうやってもexpect(b()).toEqual("modified a")でpassしない。 import * as utils from "./utils" jest.spyOn(utils, 'a').mockImplementation(jest.fn().mockReturnValue("modified a")); ... こうしても駄目。 どちらもb()はmockされていないutils.ts内のa()を呼ぶことになる。 解決 utils.ts import * as utils from "./utils" // 自分自身をimport // import名は.test側と一致する必要はない function a() { return "a" } function b() { return utils.a() // importしたものから呼び出す } export { a, b } utils.test.ts import * as utils from "./utils" jest .spyOn(utils, "a") .mockImplementation(jest.fn().mockReturnValue("modified a")) describe("utils", () => { it("sholuld use mocked a()", () => { expect(b()).toEqual("modified a") expect(a).toHaveBeenCalledTimes(1) }); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでi18nのjson書き出しサボり設定

使いどころ create-react-appを使って作成したプロジェクトを、 i18n-nextで多言語化したとき、翻訳データの*.jsonを手書きするのが面倒。 *.tsxや*.jsで翻訳する部分を自動で見つけて*.jsonに追記してほしい! 今回の例として紹介するのは、フロントエンドだけで完結する、非常に簡単なSPAです。 i18n-nextは、locizeを導入できたり、バックエンド開発にも使えるので、知っておくとお得です。 ハマりポイント 先にハマりポイント解説します。 少し前はbabel-plugin-i18next-extractが流行ったようですが、create-react-appを使って制作しているアプリケーションの場合、webpackの設定を変更することは、npm ejectというコマンドを使うと実現できますが、不可逆であり得策ではありません。そのため、babel-plugin-i18next-extractを使うのはやめたほうが良さそうです。そこで、利用するのは、公式ドキュメントにも記載のある、i18next-parserです。 まずは、公式ドキュメントにも記載のある、i18next-parserをインストールします。 terminal npm install -g i18next-parser 設定 公式ドキュメントを参考に、i18next-parserの設定を書きます。 ./i18next-parser.config.js module.exports = { locales: ['en', 'ja', 'cn'], output: 'src/locales/$LOCALE/$NAMESPACE.json', } locale: 欲しい分だけ言語を追記 output: 位置がsrcの中でないと、tsxからimport出来ないので、デフォルト設定を上書き npm の scripts を設定します。 ./package.json "scripts": { "i18next-extract": "i18next 'src/**/*.{tsx,ts}'" } ひとまず設定が終わったので、翻訳ファイルのjsonを書き出します。 terminal npm run i18next-extract すると、下記のような構成で出力されるはずです。 tree ./ ┃ ┣━ src ┃ ┣━ locales ┃ ┃ ┣━ cn ┃ ┃ ┃ ┗━ translation.json ┃ ┃ ┣━ en ┃ ┃ ┃ ┗━ translation.json ┃ ┃ ┗━ jp ┃ ┃ ┗━ translation.json コード変更 ここから、ソースコードに翻訳用の設定を足していきます。 まずは、トップレベルのReactファイルにi18nの初期化設定を書き込みます。 この時、先ほど出力されたtranslation.jsonファイルを設定します。 ./src/index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.sass'; import reportWebVitals from './reportWebVitals'; import App from "./components/App"; import { createBrowserHistory } from "history" import { Router } from 'react-router'; import HeaderApp from './components/header'; // i18n import i18next from 'i18next'; import { initReactI18next } from 'react-i18next'; import jaCommon from './locales/ja/translation.json' import enCommon from './locales/en/translation.json' import cnCommon from './locales/cn/translation.json' i18next.use(initReactI18next).init({ debug: true, resources : { ja:{common:jaCommon}, en:{common:enCommon}, cn:{common:cnCommon}, }, lng: 'ja', fallbackLng: 'ja', keySeparator: false, interpolation: { escapeValue: false }, }); const history = createBrowserHistory({ basename: '/' }); ReactDOM.render( <React.StrictMode> <Router history={history}> <HeaderApp/> <App></App> </Router> </React.StrictMode>, document.getElementById('root') ); reportWebVitals(console.log); <App />の定義は下記です。 ./src/components/App.tsx import React from 'react'; import i18next from 'i18next'; import { useTranslation } from 'react-i18next'; import { useEffect } from 'react' import ja_Trans from '../locales/ja/maincontent.json' import en_Trans from '../locales/en/maincontent.json' import cn_Trans from '../locales/cn/maincontent.json' export function Translate() { const { t } = useTranslation('maincontent'); useEffect(() => { i18next.addResources('ja', 'maincontent', ja_Trans); i18next.addResources('en', 'maincontent', en_Trans); i18next.addResources('cn', 'maincontent', cn_Trans); }, []) return ( <React.StrictMode> <p>{t('message')}</p> </React.StrictMode> ); } class App extends React.Component { render() { return <Translate/>; } } export default App; これだけだと、まだ言語切り替えが出来ません。先ほどのindex.tsxに書かれている<HeaderApp />の詳細を書いていきます。 ./src/components/header.tsx import React from 'react'; import { FunctionComponent } from "react"; import { LanugageSelector } from "./LanguageSelector"; import { useTranslation } from 'react-i18next' import { useState } from 'react'; import { useEffect } from 'react'; const HeaderApp: FunctionComponent = () => { const [t, i18n] = useTranslation(); const languages = [{ code: "ja", name: "JAPANESE" }, { code: "en", name: "ENGLISH" }, { code: "cn", name: "CHINESE" }]; const [currentLang, setCurrentLang] = useState("ja"); // Change language of i18n useEffect(() => { i18n.changeLanguage(currentLang); }, [currentLang, i18n]) // Handler const handleLanguageSelectionChange = (lang: string) => { setCurrentLang(lang); } return ( <React.StrictMode> <header> <div> <ul> <LanugageSelector languages={languages} selectedLanguage={currentLang} onLanguageSelectionChange={handleLanguageSelectionChange} /> </ul> </div> </header> </React.StrictMode> ); } export default HeaderApp; <LanugageSelector />の定義は下記です。 ./src/components/LanguageSelector.tsx import React, { FunctionComponent } from "react"; export type LanguageSelectorProps = { languages: { code: string, name: string }[], selectedLanguage: string, onLanguageSelectionChange: (lang: string) => void, } export const LanugageSelector: FunctionComponent<LanguageSelectorProps> = (props) => { const langs = props.languages.map((lang) => { return (lang.code === props.selectedLanguage) ? '' : <li><p onClick={() => props.onLanguageSelectionChange(lang.code)}>{lang.name}</p></li> }); return ( <div className="i18n"> {langs} </div> ); } この<LanguageSelector/>により、<p>ENGLISH</p>、<p>JAPANESE</p>、<p>CHINESE</p>が表示されます。これをクリックすると表示が切り替わる仕組みです。3項演算子で、「現在表示中の言語」は消しています。 再びのextract ここまで書けたら、もう一度、先ほどのコマンドを実行してみます。 terminal npm run i18next-extract tree ./ ┃ ┣━ src ┃ ┣━ locales ┃ ┃ ┣━ cn ┃ ┃ ┃ ┣━ maincontent.json ┃ ┃ ┃ ┗━ translation.json ┃ ┃ ┣━ en ┃ ┃ ┃ ┣━ maincontent.json ┃ ┃ ┃ ┗━ translation.json ┃ ┃ ┗━ jp ┃ ┃ ┃ ┣━ maincontent.json ┃ ┃ ┗━ translation.json ここまで来たら、おめでとうございます! あとは、ひたすら翻訳を書いていくだけです。 まとめ npm startの中でnpm run i18next-extractを動かすにはどうするのでしょう? Excelsior!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでモーダルダイアログ Promise方式

概要 良くあるOK/Cancel確認ダイアログをReactで実装する場合、 ダイアログを表示している/していない、OK押された時のハンドラをStateで管理するのがReact的。 ただ、こういう場合はwindow.confirmのように手続き的なコーディングの方が理解しやすかったりするのでそれをPromiseで表現する。 汎用化してモーダルダイアログ全般に対応。 方針 ダイアログはルート的な要素で管理してコンテキスト経由でダイアログ呼び出しの非同期関数をダイアログを利用するコンポーネントに渡したい。 呼び出し側はawaitすることで手続き的に記述可能。 ダイアログコンポーネントは自由に指定可能。UIには関知しない。モーダルの管理機構のみ提供。 どうせなら汎用的にしてみる。コンテキスト使わずrender propsで関数を渡す方式も対応。 ダイアログのインプット、アウトプットの型を指定可能。TypeScriptでタイプセーフ。 モーダルはtransitionが入って描画コストが高かったりするので、モーダルの状態変更時に子コンポーネントのrenderが走らないようにチューニング。 実装 親コンポーネントでConfirmダイアログを子コンポートに提供 App.tsx import React from 'react'; import { WithPromiseModal } from 'src/WithPromiseModal'; import MyComponent from 'src/MyComponent'; import ConfirmModal from 'src/ConfirmModal'; import { ConfirmParam, ConfirmContext } from 'src/contexts'; function App() { return ( <WithPromiseModal initialParam={{ title: '', message: '' }} // モーダルオープンしていない場合のshowModalParam // モーダルの状態が変わる度に呼ばれる。モーダルをrenderする。 renderModal={({ modalState: { show, handleOk, handleCancel }, // モーダルの状態 // モーダルオープン関数のパラメータ(Contextからの型推論が効く) showModalParam: { title, message }, }) => { return ( <ConfirmModal show={show} handleOk={handleOk} handleCancel={handleCancel} title={title} message={message} /> ); }} context={ConfirmContext} // モーダルオープン関数をこのコンテキストでプロバイドする > <MyComponent /> </WithPromiseModal> ); } export default App; コンテキストの生成 contexts.tsx import React, { useContext } from 'react'; // コンテキストで渡すモーダルオープン関数の引数の型 export type ConfirmParam = { title: string; message: string; }; export const ConfirmContext = React.createContext( async (param: ConfirmParam) => {} ); export function useConfirm() { return useContext(ConfirmContext); } 子コンポーネントで利用 MyComponent.tsx import React, { useState, useEffect } from 'react'; import Button from 'react-bootstrap/Button'; import Modal from 'react-bootstrap/Modal'; import { useConfirm } from 'src/contexts'; import { WithPromiseModal, ModalState } from 'src/WithPromiseModal'; type ModalInput = { initialSelectedValue: string; }; type ModalOutput = { selectedValue: string; }; const MyComponent = () => { // コンテキストからconfirm関数を取得するHook const confirm = useConfirm(); const handleClick = () => { confirm({ title: '確認', message: '本気ですか?' }) .then(() => { // はいが押された console.log('はい'); }) .catch(() => { // いいえが押された console.log('いいえ'); }); }; return ( <> <Button onClick={() => handleClick()}>とうろく</Button> <Content /> </> ); }; // Contextを使わないパターンのテスト const Content = () => { return ( <WithPromiseModal<ModalInput, ModalOutput> initialParam={{ initialSelectedValue: '' }} renderModal={({ modalState: { show, handleOk, handleCancel }, showModalParam, // 型はModalInput }) => { return ( <MyModal show={show} handleOk={handleOk} handleCancel={handleCancel} initialSelectedValue={showModalParam.initialSelectedValue} /> ); }} // Contextを使わずrender propsでモーダルオープン関数を渡すパターン render={({ showModal }) => { // showModal関数のパラメータの型はModalOutput return <MyContent showModal={showModal} />; }} /> ); }; type MyContentProps = { showModal: (param: ModalInput) => Promise<ModalOutput>; }; const MyContent: React.FC<MyContentProps> = ({ showModal }) => { console.log('MyContent render'); const handleClick = () => { showModal({ initialSelectedValue: 'B' }) .then(({ selectedValue }) => { console.log('selected', selectedValue); }) .catch(() => { console.log('not selected'); }); }; return ( <div> <Button onClick={() => handleClick()}>おーぷん</Button> </div> ); }; type MyModalProps<T = ModalOutput> = ModalState<T> & { initialSelectedValue: string; }; const MyModal: React.FC<MyModalProps> = ({ show, handleOk, handleCancel, initialSelectedValue: param, }) => { const [selectedValue, setSelectedValue] = useState(''); useEffect(() => { setSelectedValue(param); }, [param]); return ( <> <Modal show={show} onHide={handleCancel}> <Modal.Header closeButton> <Modal.Title>もーだる</Modal.Title> </Modal.Header> <Modal.Body> <select value={selectedValue} onChange={(e) => setSelectedValue(e.target.value)} > <option value="A">A</option> <option value="B">B</option> <option value="C">C</option> </select> </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={handleCancel}> きゃんせる </Button> <Button variant="primary" onClick={() => handleOk({ selectedValue: selectedValue })} > せんたく </Button> </Modal.Footer> </Modal> </> ); }; export default MyComponent; モーダル管理コンポーネント import React, { useState, useCallback, useMemo } from 'react'; export type ModalState<T> = { show: boolean; handleOk: (param: T) => void; handleCancel: () => void; }; type RenderModalParam<I, O> = { modalState: ModalState<O>; showModalParam: I; // 関数呼び出し時のパラメータ }; type RenderModal<I, O> = ( param: RenderModalParam<I, O> ) => React.ReactElement<any, any>; type RenderParam<I, O> = { showModal: (param: I) => Promise<O>; }; type Render<I, O> = ( param: RenderParam<I, O> ) => React.ReactElement<any, any> | null; type Props<I, O> = { initialParam: I; renderModal: RenderModal<I, O>; context?: React.Context<(params: I) => Promise<O>>; children?: React.ReactNode; render?: Render<I, O>; }; export function WithPromiseModal<I, O = void>({ initialParam, renderModal, children, context, render, }: Props<I, O>): React.ReactElement { const [show, setShow] = useState(false); const [param, setParam] = useState<I>(initialParam); // 関数をStateに設定する時は () => 設定したい関数 const [handleOk, setHandleOk] = useState(() => () => {}); const [handleCancel, setHandleCancel] = useState(() => () => {}); // ダイアログで良くあるtransitionは結構重いから // useCallbackを使用してダイアログのState変更時に利用する側のrenderが走らないようにしておく const showModal = useCallback((param: I) => { return new Promise<O>((resolve, reject) => { console.log('confirm called'); setShow(true); setParam(param); setHandleOk(() => (param: O) => { setShow(false); resolve(param); }); setHandleCancel(() => () => { setShow(false); reject(); }); }); }, []); const memorizedRender = useMemo(() => { return render ? render({ showModal }) : null; }, [render, showModal]); return ( <> {context ? ( <context.Provider value={showModal}>{children}</context.Provider> ) : ( children )} {memorizedRender} {renderModal({ modalState: { show, handleOk, handleCancel }, showModalParam: param, })} </> ); } Confirmダイアログ ConfirmModal.tsx import React from 'react'; import Modal from 'react-bootstrap/Modal'; import Button from 'react-bootstrap/Button'; import { ModalState } from 'src/WithPromiseModal'; import { ConfirmParam } from 'src/contexts'; type Props<T = void> = ModalState<T> & ConfirmParam; const ConfirmModal: React.FC<Props> = ({ show, handleOk, handleCancel, title, message, }) => { return ( <> <Modal show={show} onHide={() => handleCancel()}> <Modal.Header closeButton> <Modal.Title>{title}</Modal.Title> </Modal.Header> <Modal.Body>{message}</Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={() => handleCancel()}> いいえ </Button> <Button variant="primary" onClick={() => handleOk()}> はい </Button> </Modal.Footer> </Modal> </> ); }; export default ConfirmModal; 所感 汎用的な共通機構を作るとTypeScriptが大変だな。 でもリファクタしやすい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Create React AppでReactの開発環境を構築

はじめに 自身のメモとして、本記事では、Create React Appを利用したReact+TypeScriptのビルド環境を構築手順を記載します. Create React Appとは Create React AppはFacebookにより提供されているReactの公式ツールで、コマンド一発でReactの開発環境を構築することが出来るというもので、現在では、Reactアプリ開発におけるデファクトスタンダードとなっています. Create React App 公式サイト Create React App GitHub Create React Appを利用した開発環境構築 事前確認 各種コマンドのバージョンを確認します. nodeのバージョン $ node -v v16.0.0 yarnのバージョン $ yarn -v 1.22.10 npxのバージョン $ npx -v 7.10.0 create-react-appのバージョン確認 $ npx create-react-app --version 4.0.3 新規プロジェクト作成 下記のCreate React Appのコマンドを実行し、TypeScriptを利用したReactのビルド環境を構築します. $ npx create-react-app sample --template typescript Creating a new React app in sample. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template-typescript... yarn add v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.20.0" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". [4/4] ? Building fresh packages... success Saved lockfile. success Saved 7 new dependencies. info Direct dependencies ├─ cra-template-typescript@1.1.2 ├─ react-dom@17.0.2 ├─ react-scripts@4.0.3 └─ react@17.0.2 info All dependencies ├─ cra-template-typescript@1.1.2 ├─ immer@8.0.1 ├─ react-dev-utils@11.0.4 ├─ react-dom@17.0.2 ├─ react-scripts@4.0.3 ├─ react@17.0.2 └─ scheduler@0.20.2 ✨ Done in 21.77s. Initialized a git repository. Installing template dependencies using yarnpkg... yarn add v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning " > @testing-library/user-event@12.8.3" has unmet peer dependency "@testing-library/dom@>=7.21.4". [4/4] ? Building fresh packages... success Saved lockfile. success Saved 23 new dependencies. info Direct dependencies ├─ @testing-library/jest-dom@5.14.1 ├─ @testing-library/react@11.2.7 ├─ @testing-library/user-event@12.8.3 ├─ @types/jest@26.0.23 ├─ @types/node@12.20.15 ├─ @types/react-dom@17.0.7 ├─ @types/react@17.0.11 ├─ react-dom@17.0.2 ├─ react@17.0.2 ├─ typescript@4.3.2 └─ web-vitals@1.1.2 info All dependencies ├─ @testing-library/dom@7.31.2 ├─ @testing-library/jest-dom@5.14.1 ├─ @testing-library/react@11.2.7 ├─ @testing-library/user-event@12.8.3 ├─ @types/aria-query@4.2.1 ├─ @types/jest@26.0.23 ├─ @types/node@12.20.15 ├─ @types/prop-types@15.7.3 ├─ @types/react-dom@17.0.7 ├─ @types/react@17.0.11 ├─ @types/scheduler@0.16.1 ├─ @types/testing-library__jest-dom@5.14.0 ├─ css.escape@1.5.1 ├─ css@3.0.0 ├─ csstype@3.0.8 ├─ lz-string@1.4.4 ├─ min-indent@1.0.1 ├─ react-dom@17.0.2 ├─ react@17.0.2 ├─ redent@3.0.0 ├─ strip-indent@3.0.0 ├─ typescript@4.3.2 └─ web-vitals@1.1.2 ✨ Done in 12.29s. We detected TypeScript in your project (src/App.test.tsx) and created a tsconfig.json file for you. Your tsconfig.json has been populated with default values. Removing template package using yarnpkg... yarn remove v1.22.10 [1/2] ? Removing module cra-template-typescript... [2/2] ? Regenerating lockfile and installing missing dependencies... warning " > @testing-library/user-event@12.8.3" has unmet peer dependency "@testing-library/dom@>=7.21.4". success Uninstalled packages. ✨ Done in 9.58s. Created git commit. Success! Created sample at sample Inside that directory, you can run several commands: yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd sample yarn start Happy hacking! 依存関係の解消 あらためてエラー/警告が出てないか確認します.参考までにyarn install —check-filesは「node_modules に既にインストールされたファイルが削除されていないことを確認」するコマンドです(参考). $ yarn install --check-files yarn install v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning " > @testing-library/user-event@12.8.3" has unmet peer dependency "@testing-library/dom@>=7.21.4". [4/4] ? Building fresh packages... ✨ Done in 8.38s. unmet peer dependencyの警告が出ている.警告の内容としては@testing-library/user-event@12.8.3が正しく動作するのにバージョン7.21.4以上の@testing-library/domが必要という意味になる. warning " > @testing-library/user-event@12.8.3" has unmet peer dependency "@testing-library/dom@>=7.21.4". install可能なバージョンを確認 $ yarn info @testing-library/dom versions | tail -n 10 '7.31.1', '7.31.2', '8.0.0-alpha.1', '8.0.0-alpha.2', '8.0.0-alpha.3', '8.0.0-alpha.4', '8.0.0-alpha.5', '8.0.0-alpha.6' ] Done in 0.24s. 依存関係を解消するため、パッケージを追加.latestを指定して最新のstable版をインストール. $ yarn add -D @testing-library/dom@latest package.jsonに下記が追加された "devDependencies": { "@testing-library/dom": "^7.31.2" } エラー/警告が出てないか再度確認し、エラー/警告が出なくなっていることを確認 $ yarn install --check-files yarn install v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... ✨ Done in 8.90s. プロジェクトの起動 プロジェクトを実行 $ cd sample $ yarn start 正しく動作していることをブラウザで確認 参考 https://create-react-app.dev/docs/adding-typescript/ https://ja.reactjs.org/docs/create-a-new-react-app.html https://github.com/facebook/create-react-app https://create-react-app.dev/ https://chore-update--yarnpkg.netlify.app/ja/docs/cli/install https://classic.yarnpkg.com/en/docs/cli/install/#toc-yarn-install-check-files
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React - FunctionalComponent vs ClassComponent

Componentとは Componentはユーザーインターフェイスの一部を表します。 Componentは再利用可能で、ユーザーインターフェイスのどこでも使用できます。 Reactには主に2つのコンポーネントタイプがあります。 Class Component Functional Component Functional ComponentとClass Componentの比較 Syntax Class Component React.Componentというクラスから継続するクラスです。 React要素を返却するrenderというメソッドでhtmlUIを定義します。 class Index extend React.Component { render() { return( <div>FunctionComponent</div> ) } } Functional Component html UIを返却する単純なJavaScript関数です。 arrow functionを使用して定義できますが、通常のfunctionキーワードも良いです。 propsを引数として受け入れてReact要素を返します。 renderメソッドがないです。 const Index = () => { return( <div>FunctionComponent</div> ) } Lifecycle Class Component 3つのフェーズがあります。 Mounting Mountingとは、要素をDOMに配置することです。 こちらのフェーズで、下記の順序で呼び出される4つの組み込みメソッドがあります。 constructor() getDerivedStateFromProps() render() componentDidMount() Updating ComponentのStateまたはPropsに変更するたびに、Componentが更新されることです。 こちらのフェーズで、下記の順序で呼び出される5つの組み込みメソッドがあります。 getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate() Unmounting Unmountingとは、要素をDOMから削除されることです。 こちらのフェーズで一つの組み込みメソッドだけがあります。 componentWillUnmount() 参考:https://www.w3schools.com/react/react_lifecycle.asp Function Component 単純なJavaScript関数なので、Component lifecycleメソッドが存在しなくてsetStateも使えないので、Function ComponentはStateLessを呼ばれます。 Lifecycleを管理するため、ReactHooksを使用できます。 useEffectを使用してLifecycleの動作を複製でき、useStateを使用してStateを管理できます。 ClassComponentの三つのフェーズでは必ず必要なメソッドは、 componentDidMount() => 要素をDOMに配置した時の処理 componentDidUpdate() => Componentを更新された時の処理 componentWillUnmount() => 要素をDOMから削除された時の処理 上記の三つのメソッドは、useEffectだけを使用して対応できます。 useEffect(() => { effect return () => { cleanup }; }, [input]); componentDidMountの対応 useEffect import React, { useEffect } from "react"; const Component = () => { useEffect(() => { console.log("Behavior before the component is added to the DOM"); }, []); return <h1>Hello World</h1>; }; => 上記の例では、ComponentがマウントされるときにuseEffectが呼び出されます。 2番目の引数として空の配列([])の場合、 Componentのマウント時に1回だけ実行する必要があります。 componentDidUpdate()の対応 2番目の引数を渡せない場合、Componentをレンダーするたびに、いつもuseEffectをトリガーされます。 useEffect import React, { useEffect } from "react"; const Component = () => { useEffect(() => { console.log("Behavior when the component receives new state or props."); }); return <h1>Hello World</h1>; }; 2番目の引数を渡す時、引数の値を変わる時だけ、useEffectをトリガーされます。 useEffect import React, { useEffect } from "react"; const Component = ({ hoge }) => { useEffect(() => { console.log("Behavior when the value of 'hoge' changes."); }, [hoge]); return <h1>Hello World</h1>; }; componentWillUnmount()の対応 useEffectの中にreturnを定義すると、要素をDOMから削除された時、return内の処理を実行されます。 useEffect import React, { useEffect } from "react"; const Component = () => { useEffect(() => { return () => { console.log("Behavior right before the component is removed from the DOM."); } }, []); return <h1>Hello World</h1>; }; 同時に対応する時 useEffect import React, { useEffect, useRef } from "react"; //// Mountしたかどうか確認 export const useDidMount = () => { const ref = useRef<boolean>(); useEffect(() => { ref.current = true; }, []); return ref.current; }; const Component = () => { const isDidMount = useDidMount(); useEffect(({hoge}) => { if(isDidMount) { // <componentDidMount> } else { // <componentDidUpdate/> } // <componentWillUnmount> // returnがあったら、要素をDOMから削除された時、return内の処理を実行されます。 return () => { console.log('componentWillUnmount'); } // <componentWillUnmount/> }, [hoge]); return <h1>Hello World</h1>; }; useStateで、Stateを管理する FunctionComponent.jsx import React, { useState } from "react"; const FunctionComponent = () => { const [value, setValueMethod] = useState("default_value"); return ( <input value={value} onChange={(event) => setValueMethod(event.target.value)} /> ); }; export default FunctionComponent; ClassComponentで同じ意味のコードです。 ClassComponent.jsx import React from "react"; class ClassComponent extends React.Component { constructor(props) { super(props); this.state = { value: "default_value" }; } render() { return ( <input value={this.state.value} onChange={(event) => this.setState({ value: event.target.value })} /> ); } } export default ClassComponent; なぜFunctionalComponentを使った方が良い? Javascriptメソッドなので、クラスより、コードを読みやすくて、テストしやすいです。 クラスよりコードが少ないです。 メソッドなのでScopeを管理しやすいです。クラスだと、いつもFunctionを呼ぶ時、いつもthis(componentのScopeを確定する必要です。(例:this.handle.bind(this)) Lifecycleをもっと管理しやすいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む