20201227のReactに関する記事は7件です。

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 && yarn 

tyconfig.json

tscのinitオプションで作成

$ npx tsc --init
tsconfig.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.js
const 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.tsx
import 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.tsx
import React from 'react'

const Hoge = () => {
    return <div>Hoge</div>
}

export default Hoge

実行

$ yarn start

結果

スクリーンショット 2020-12-27 23.42.26.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gatsbyとtailwindcssでclassNameが再レンダリングされない

techブログ

状況

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が適応されることはなくなりました。

経緯とか

https://kajirikajiri.netlify.app/20201227222747/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

prettierでgo fmt的なことをする

インストール

npm install -D prettier

整形

prettier --write src/Orders.js

あ、

  • package.jsonなどのコマンドを定義すれば再帰的に一気に整形とかもできるっぽいのだけどよくわからない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

useCallback 学習メモ

useCallback()を利用するメリット

通常、Reactコンポーネント内で宣言したコールバック関数はrender毎に生成される

useCallback()を使うと、、、
コールバック関数の再生成を抑止(不変値化)
クラスコンポーネントのbind()と似た役割

useCallback()の使い方

文法

useCallback(() => {}, [hoge]) // (コールバック関数, deps(再描画の条件))

子コンポーネントにpropsで関数を渡す場合に使用する

const handleClose = useCallback(() => {
  setOpen(false)
}, [])
// 中略
<FormDialog
  open={open}
  handleClose={handleClose}
/>

setOpenuseState由来の関数の場合は、Reactで同一性が保証されているためdepsに含めなくても問題ない
https://ja.reactjs.org/docs/hooks-reference.html#usestate

クラスコンポーネントとの比較

this.handleClose = this.handleClose.bind(this)
// 中略
handleClose = () => {
  this.setState({open: false})
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] JavaScriptのクラスとReact

JavaScriptのクラスとReact

※ 個人用の覚え書きでございます。

JavaScriptのクラス(継承)

script.js
class 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.js
import 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;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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は、同じくプロパティのwithFootertrueなら必須、falseなら不要なのに、両方を満たすために任意とされている
  • 上記から、型としてはtrue+countなしまたはfalse+countありが許されるが、前者は動かず、後者は無駄
  • Footerコンポーネントを分離すれば良いという考えもあるが、書ききれない背景を考慮するとコストが見合わなかった

Conditional Types

そこで使いたいのが Conditional Typesです。
これは簡単に言うと、条件によって分岐する型です。(そのまま)
三項演算子に似た形で書き、extendsの左が右に置き換えられるならtrue相当の動きをします。
下記の場合はDogAnimalに置き換え可能で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の分岐を作ることができました。
もちろんコンポーネント自体をリファクタリングした方が良いというのもありますが、
選択肢の一つとして持っておけると、いざという時に役に立つかと思います。

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【技術書まとめ】『りあクト! 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
      • コンポーネントを引数にとりコンポーネントを返す
      • 外から状態やロジックを注入する関数と分ける
        • ReduxRecomposeなどで採用された
    • `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) = 2f(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をつけて切り出す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む