- 投稿日:2020-12-27T23:44:31+09:00
react x typescript x webpackで環境構築
create-react-appを使わずに環境構築する
インストール
## package.json作る $ yarn init -y ## webpack $ yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin ## babel $ yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-react ## react $ yarn add -D react react-dom ## typescript $ yarn add -D typescript ts-loader $ typesync && yarntyconfig.json
tscのinitオプションで作成
$ npx tsc --inittsconfig.json{ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } }webpack.config.js 作成
webpack.config.jsconst path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', entry: path.resolve(__dirname, 'src', 'index.tsx'), output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js' }, module: { rules: [ { test: /\.(js|jsx)$/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', '@babel/react', ] } } }, { test: /\.(ts|tsx)$/, use: { loader: 'ts-loader', } } ] }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src', 'index.html') }) ], devServer: { contentBase: 'dist', open: true }, resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] } }package.json 追記
package.json{ "name": "test5", "version": "1.0.0", "main": "index.js", "license": "MIT", "devDependencies": { "@babel/core": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "babel-loader": "^8.2.2", "html-webpack-plugin": "^4.5.0", "react": "^17.0.1", "react-dom": "^17.0.1", "ts-loader": "^8.0.12", "typescript": "^4.1.3", "webpack": "^5.11.0", "webpack-cli": "^4.3.0", "webpack-dev-server": "^3.11.0" }, "scripts": { "start": "npx webpack serve" } }ソース作成
src/index.html<!DOCTYPE html> <body> <div id="app" /> </body>src/index.tsximport React from 'react' import ReactDOM from 'react-dom' import Hoge from './components/Hoge' const App = () => { return <div> react x typescript <Hoge /> </div> } ReactDOM.render(<App />, document.getElementById('app'))src/components/Hoge.tsximport React from 'react' const Hoge = () => { return <div>Hoge</div> } export default Hoge実行
$ yarn start結果
- 投稿日:2020-12-27T23:27:48+09:00
gatsbyとtailwindcssでclassNameが再レンダリングされない
状況
loadingが終了した後に表示されるdivタグにloading時のdivタグのclassNameが残っていていて同じスタイルが適用される
試してほしいこと
import React from 'react'; ... const Index = () => { ... if (loading) { return ( <div className="mb-20 mt-10"> // <- divタグからmainタグに書き換えてほしい ...loading </div> ) } return ( <div> ... </div> ) } export default Indexおまけ
自分の場合は上記のように、divタグをmainタグに書き換え、タグの重複を避けた場合に同じclassNameが適応されることはなくなりました。
経緯とか
- 投稿日:2020-12-27T21:17:45+09:00
prettierでgo fmt的なことをする
- 投稿日:2020-12-27T20:37:51+09:00
useCallback 学習メモ
useCallback()
を利用するメリット通常、Reactコンポーネント内で宣言したコールバック関数はrender毎に生成される
useCallback()
を使うと、、、
コールバック関数の再生成を抑止(不変値化)
クラスコンポーネントのbind()
と似た役割
useCallback()
の使い方文法
useCallback(() => {}, [hoge]) // (コールバック関数, deps(再描画の条件))子コンポーネントにpropsで関数を渡す場合に使用する
const handleClose = useCallback(() => { setOpen(false) }, []) // 中略 <FormDialog open={open} handleClose={handleClose} />
setOpen
がuseState
由来の関数の場合は、Reactで同一性が保証されているためdeps
に含めなくても問題ない
https://ja.reactjs.org/docs/hooks-reference.html#usestateクラスコンポーネントとの比較
this.handleClose = this.handleClose.bind(this) // 中略 handleClose = () => { this.setState({open: false}) }
- 投稿日:2020-12-27T18:33:19+09:00
[React] JavaScriptのクラスとReact
JavaScriptのクラスとReact
※ 個人用の覚え書きでございます。
JavaScriptのクラス(継承)
script.jsclass Class_1{ constructor(name, age) { this.name = name; this.age = age; } info() { console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Class_2 extends Class_1 { } const class_2 = new Class_2("山田",20); class_2.info();React(ボタンをクリックすると表示が切り替わる)
app.jsimport React from 'react'; class App extends React.Component { constructor(props) { super(props); this.state = {name: '山田'}; } handleClick(f_name){ this.setState({name: f_name}); } render() { return ( <div> <h1>こんにちは、{this.state.name}さん!</h1> <button onClick={ () => {this.handleClick('山田')}}> 山田 </button> <button onClick={ () => {this.handleClick('田中')}}> 田中 </button> </div> ); } } export default App;
- 投稿日:2020-12-27T15:39:25+09:00
React + TypeScript でフラグ分岐するpropsを型安全に作る
前書き
ちょっとしたストーリーです。
不要な方は Conditional Typesを適用してみよう まで飛んでね。ある日のでき事です。
「このコンポーネント、フッターなしで使いたいんですが、フラグで分岐していいですか?」
そんなissueが飛んできました。
私もちょうど同じ様な使い方がしたかったので、「是非お願いしますー」と返事をしました。before/after
before
const Table: React.FC<{ count: number; }> = (props) => ( <div> {props.children} <Footer count={props.count} /> </div> );after
const Table: React.FC<{ count?: number; withFooter?: boolean; }> = (props) => ( <div> {props.children} {props.withFooter !== false ? <Footer count={props.count} /> : null } </div> );既存の利用箇所を考慮して、「
withFooter===false
とするとなしにできる」と拡張されました。ちょっとした問題
やってもらえてラッキー、だったんですがちょっとした問題ができました。
withFooter=true
の時はcount
は必須で欲しいのですが、
withFooter=false
の時は不要なので、両方を満たすためにwithFooter?:
と任意項目になっています。
これではwithFooter=true
の時にcount
を渡し忘れてもrenderしてみるまで気がつけないのです。型安全にしてみよう
問題点の整理
- プロパティの
count
は、同じくプロパティのwithFooter
がtrue
なら必須、false
なら不要なのに、両方を満たすために任意
とされている- 上記から、型としては
true+countなし
またはfalse+countあり
が許されるが、前者は動かず、後者は無駄Footer
コンポーネントを分離すれば良いという考えもあるが、書ききれない背景を考慮するとコストが見合わなかったConditional Types
そこで使いたいのが Conditional Typesです。
これは簡単に言うと、条件によって分岐する型です。(そのまま)
三項演算子に似た形で書き、extendsの左が右に置き換えられるならtrue相当の動きをします。
下記の場合はDog
はAnimal
に置き換え可能でFoo: number
となります。interface Dog extends Animal type Foo = Dog extends Animal ? number : string;Conditional Typesを適用してみよう
1.props型を作る
事前準備で、分岐用専門の型として
WithFooter
,WithoutFooter
を作りました。type WithFooter = 'withFooter'; type WithoutFooter = 'withoutFooter';そしてこちらがメインです。
T
型にWithoutFooter
を指定するとWithoutFooterProps
になり、それ以外(指定なしを含む)ではWithFooterProps
になります。type WithFooterProps = React.PropsWithChildren<{ withFooter?: true, count: number; }>; type WithoutFooterProps = React.PropsWithChildren<{ withFooter: false, }>; type TableProps<T = WithFooter> = T extends WithoutFooter ? WithoutFooterProps : WithFooterProps;2.コンポーネントに適用
genericsが必要になりますが、
React.FC
では上手く書けなかったので分解しています。
また、<T, >
とは、これはjsxではなくてgenericsだよ、とパーサーにわかってもらうための記述です。const Table = <T, >(props: TableProps<T>): React.ReactNode => (Type Guardも作って適用しましょう。
const isWithFooter = (props: WithFooterProps|WithoutFooterProps): props is TableProps<WithFooter> => props.withFooter !== false;{isWithFooter(props) ? <Footer count={props.count} /> : null }3.利用箇所に適用
// フッターありの場合 <Table count={count} />// フッターなしの場合 <Table<WithoutFooter> withFooter={false} />まとめ
無事に型安全にpropsの分岐を作ることができました。
もちろんコンポーネント自体をリファクタリングした方が良いというのもありますが、
選択肢の一つとして持っておけると、いざという時に役に立つかと思います。参考リンク
- 投稿日:2020-12-27T12:46:05+09:00
【技術書まとめ】『りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅱ. React基礎編】』を読んだまとめ
- JSXに埋め込めるのは式だけ
- falsyな値だけでなくtrueも表示されない
React ではカプセル化されたコンポーネントをベースとしてそれに必要なデータが宣言的に取得されるようにしておけば、model というデータの抽象化が必要かどうかというのは本質的な議論 にならない
- 変化の早い世界ではワンストップであることは技術の進歩の妨げになる
- フルスタックではないことが逆に強みになる
- React Element はコンポーネントを任意の props で呼ぶための実行リンクのようなもの
- 展開されていく
- リアルDOM変換前の最終的な要素ツリーができる
- メモリにキャッシュされる
- これが仮想DOM
- 差分のみリアルDOMに反映する
- React の単方向データフロー
- props が滝のように流れ落ちる
- 関数型プログラミングと相性がいい
- 数学的な純粋性
- フォーム
- 「state のリフトアップ」
- 親コンポーネントが state を持つ
- state 変更関数を作る
- 子コンポーネントに props として渡す
- コンポーネントのメンタルモデル
- ReactElement で仮想DOMをつくる
- パラメータ設定できるロジックブロックの化身
- コンポーネント
- JSの関数
- props が引数、戻り値は ReactElements
- state を持てる点が関数と違う
- props と state が同じなら変わらない
- どちらかが変わると戻り値も変わる
- レンダリングが再実行される
- props と state だけを見ていればいい
- props が大事
- state は副作用がある
- 純粋関数にしたい
- 引数が同じなら同じ戻り値を返す関数
nullish coalescing
- なかったらこれを表示する
{character.height ?? '???'}
- ライフサイクル
componentDidMount
shouldComponentUpdate
componentDidUpdate
componentWillUnmount
- Hooksができた背景
mixin
- コンポーネントとの依存関係を持ってしまう
- 変数やメソッド名の名前の重複が起きやすい
- 他の
mixin
同士が混ざって依存し合う
- 削除の影響範囲がわからない
- CSSみたい?
HOC(Higher Order Component
- コンポーネントを引数にとりコンポーネントを返す
- 外から状態やロジックを注入する関数と分ける
Redux
Recompose
などで採用された- `Render Propsパターン
- ReactElement を返す関数を props として受け取って自身のレンダリングに利用するコンポーネント
- HOCへの批判
- 名前衝突の危険性がある
- props の型合成が面倒
- ラッパー地獄
Hooks
- HOCやrender props は設計パターンだった
- Hooksは機能
- 解決したこと
- ロジック追加ごとにコンポーネントの階層が深くなる問題
- 外に状態やロジックを持つ
- 状態を伴った再利用可能なロジックを追加できる
- ほぼ関数コンポーネントだけで作れるようになった
- クラスコンポーネントの必要がほぼなくなる
- 機能ごとに副作用処理をまとめられる
- ストラングラーパターン
- 書き換える必要はない
useState
- 初期値をnullにするには?
useState<User | null>(null)
- 前の値を使うときはアローで書く
setCount(c => c + 1)
- ❌
setCount(count + 1)
- if の中に書いてはいけない
- 副作用とは
- side-effect
- コンポーネントの状態を変化させ、それ以降の出力を変えてしまう処理
y = f(x)
でf(1) = 2
がf(1) = 5
になる
- propsが同じでも出力内容が変わる
- レンダリングに同期させて実行するための
Hooks API
- Effect Hook
const SampleComponent: FC = () => { const [data, setData] = useState(null); ... useEffect(() => { doSomething(); return () => clearSomething(); }, [someDeps]); ... };
useEffect
clearSomething()
で外部の購読処理を解除するsomeDeps
は依存配列
- 省略すると?
- レンダリングごとに第一引数の関数が実行される
- 空配列を渡すと?
- 初回レンダリング時のみ第一引数の関数が実行される
- 初期値レンダリングの後、副作用反映後の内容で再レンダリングされる
- 副作用処理がコンポーネント表示をブロックしない
- とりあえず何か表示されている
- 処理中を伝える
- レイアウト崩れを防ぐ
useLayoutEffect
- 副作用処理が軽いもの
- DOM要素・ブラウザ幅を取得してレンダリングに利用する
- あくまで
useEffect
を使う- クラスコンポーネントとの違い
- クラス
- マウントでインスタンス生成
- アンマウントまで生き続ける
- props や state は常に最新
- メンバー変数
- 関数
- レンダリングのたびに実行されては破棄される
- props や state は外から注入される
- 機能凝集度が高い
- 購読開始・切り替え・解除が
useEffect
内にまとめられる
- 可読性が高い
- 再利用しやすい
- 「この処理はこの条件の時に実行されるべき」
- メモ化
- 必要なときだけ計算して、結果をメモしておく
- パフォーマンス最適化
- 再レンダリングされるたびに実行されるのを防ぐ
useCallback
- 関数定義そのものをメモ化する
useRef
- 本来はリアルDOMと繋げるもの
- 再レンダリングを伴わず何らかのデータを関数コンポーネントに保存したいとき使う
const timerId = useRef<NodeJS.Timeout>(); ... if (timerId.current) clearIntervall(timerID.current);
Custom Hook
use
をつけて切り出す