2021年8月25日水曜日

React:デザインパターン

1.デザインパターンの重要性

デザインパターンとは、ソフトウェア開発において一般的に発生する問題に対する反復可能な解決策のことです。

デザインパターンは基本的なテンプレートとして機能し、与えられた要求に応じてプログラムの機能を構築することができます。


デザインパターンは、開発プロセスを高速化するだけでなく、コードの読みやすさやメンテナンスのしやすさを向上させます。

デザインパターンの一般的な例としては、SingletonパターンやGang-of-Fourパターンなどがあります。


ソフトウェア開発において、デザインパターンには2つの共通した役割があります。

  • 開発者に共通のプラットフォームを提供する

デザインパターンは、標準的な用語や既知の問題に対する解決策を提供する。

  • ベストプラクティスの確保

デザインパターンは、広範な研究とテストの結果として作成されています。デザインパターンは、開発者が開発環境に容易に慣れることができるだけでなく、ベストプラクティスが守られていることを保証します。

その結果、エラーが少なくなり、適切なデザインパターンが実装されていれば簡単に回避できたであろう問題のデバッグや解明にかかる時間を節約することができます。


2.Reactjsの機能

reactは、Githubで公開されているcreate-react-appを使ってインストールできます。npmを使用すると、他のすべての依存関係を追加できます。

React.jsは、JSXを利用しています。これは、JavaScriptの構文拡張です。JavaScriptのフルパワーを備えており、Reactの「要素」と呼ばれるものを提供してくれます。


JSXの使用は必須ではありませんが、JSXが提供する便利な視覚支援とスタイリングオプションのため、好ましい方法です。また、有用なエラーメッセージや警告も表示されます。

React.jsの基本理念は、再利用可能なリアクトコンポーネントです。このコンポーネントベースのアプローチは、Webアプリケーションのリッチなユーザーインターフェースを構築するために活用できることがわかります。

これらのReactコンポーネントは、それ自体が小さなシステムであると考えることができます。各コンポーネントは、独自の状態、入力、出力を持ちます。

コンポーネントの入力は、プロップの形で取得されます。コンポーネントは、ブラックボックスと考えることができます。それぞれが独自の状態とライフサイクルを持っています。コンポーネントは簡単に構成することができます。

最終的なReactアプリは、保守性の高いコードで構成されています。

3.Reactのデザインパターン

3.1.ステートレスコンポーネント

ステートとは、コンポーネントにインポートされるデータのことです。通常、データはデータベースから取得されます。

コンポーネントには、ステートフルコンポーネントとステートレスコンポーネントという2つのタイプがあります。両者の違いは、単にステートがあるかないかだけです。

ステートレスコンポーネントでは、その内部でthis.stateに到達することはできません。

ステートレスコンポーネントは、機能的コンポーネントや提示的コンポーネントとも呼ばれます。Reactでは、このようなコンポーネントは常に同じものをレンダリングするか、propsで渡されたものだけをレンダリングします。

開発者としての目的は、たとえその特定のコンポーネントを再利用しなければならないシナリオがすぐになくても、ステートレスコンポーネントを作成することです。

多くの場合、開発者は、あるコンポーネントが状態を持つ必要があるかどうかを、コードを書き始めてから理解しますが、それは必ずしも事前に明確ではないからです。

階層構造のコンポーネントでは、親コンポーネントにできるだけ多くの状態を保持させ、ステートレスな子コンポーネントを作るのがベストな方法です。データの受け渡しはプロップで行います。


3.2.条件付きレンダリング

条件はソフトウェア開発者の武器の中で最も重要なツールです。

Reactコンポーネントを作成する過程で、状態に応じて特定のJSXコードをレンダリングする必要が生じることがよくあります。これを実現するのが、条件付きレンダリングです。

条件付きレンダリングは、ニーズに応じて個別のコンポーネントを作成し、アプリケーションに必要なものだけをレンダリングすることができるので、非常に便利です。

例えば、条件付きレンダリングを使って、ユーザーのログイン状態に応じて異なるメッセージをユーザーに表示することができます。メッセージは、 isLoggedIn  というプロップの値に従います。


3.3.レンダリングプロップ

デザインパターンが共通の問題を解決するために存在することを説明しました。Reactでは、ロジックの繰り返しの問題を解決するためにRenderプロップが用意されています。

Reactの公式ドキュメントによると、レンダープロップは「値が関数であるプロップを使ってReactコンポーネント間でコードを共有するためのテクニック」と定義されています。

レンダープロップは、異なるコンポーネント間で同じ状態を共有することができるため、非常に便利なものです。各コンポーネント内のロジックをハードコーディングする代わりに、何をレンダリングするかを決定するために関数プロップを使用することができます。

Formik、React Router、Downshiftなど、レンダープロップを利用した人気のあるライブラリがあります。


3.4.制御されたコンポーネント

Webフォームは多くのアプリケーションで共通の要件であり、制御されたコンポーネントはフォームの状態を処理するためのReactの答えです。

コントロールされたコンポーネントは、プロップを通して状態を受け取ります。それは  onChange のようなコールバックによって変更を通知することができます。 親コンポーネントは、コールバックを処理し、自身の状態を管理することで、コントロールされたコンポーネントを制御することができ、その間、新しい値はコントロールされたコンポーネントにpropsとして渡されます。デフォルトでは、Reactフォームは制御されたコンポーネントと制御されていないコンポーネントの両方をサポートしています。制御されたコンポーネントを使用することを強くお勧めします。

次のコードスニペットは、制御されたコンポーネントを示しています。

<input type = "text" value = {value} onChange = {handleChange} />    


3.5.Reactのフック

フックはReactの比較的新しい追加機能で、React 16.8で導入されました。

これらの機能により、開発者はクラスなしでReactを使用することができます。Effect Hook ( useEffect ) や State Hook など、さまざまなフックがあらかじめ用意されています。

利用可能なフックの完全なリストについては、 Hooks API Reference  を参照してください。 Reactにあらかじめ用意されているフックとは別に、独自のフックを作成することもできます。これにより、コンポーネントのロジックを抽出し、再利用可能な関数を作成することができます。


フックはReactの歓迎すべき機能であり、開発者コミュニティはこの新しい機能を非常に熱心に評価しています。

しかし、フックの引数がオブジェクト、配列、関数である場合には、フックの扱いが少し難しくなることがあることを覚えておく必要があります。これでは少々混乱してしまいます。

一方で、カスタムフックは簡単で使いやすく、開発者にとっても大きなメリットがあります。


React:React

1.Reactとは 

JavaScript フロントエンドフレームワーク

アプリケーションで各状態に対するシンプルな設計をすることで、React はデータが変更された時に、適切なコンポーネントだけを効率的に更新してレンダリングすることができる

自身の状態を管理するカプセル化されたコンポーネントを構築することで、それらのコンポーネントを呼び出し複雑なUI もコンポーネント呼び出しで作成できる


2.Reduxとは

Fluxアーキテクチャの一種

2.1.構成用要素

2.1.1.Reduce

状態を変化させる関数

引数

store        今の状態を示すオブジェクト

action        どんな動作を行ったかを示すオブジェクト


ES2015仕様:Spread Operator

const hoge = [2, 3];
console.log(1, ...hoge, 4.5);

// 実行結果 [1,2,3,4,5]


const fuga = { name: 'Taro', age: 25 };
const piyo = { name: 'Jiro', location: 'Tokyo' };
console.log({ ...fuga, ...piyo });

// プロパティがかぶる場合、後のものが優先する
// 実行結果 {name: 'Jiro',age: 25 ,location: 'Tokyo' }


const myFunc = (x, y, z) => [x, y, z];
const age = [0, 1, 2];
mfFunc(...age);

// 実行結果 [0,1,2]


注意点

値渡が基本 オブジェクトや配列は参照渡しとなるため、Object.assignを用いる

onst target = { a: 1, b: 2}
const source = { b: 3, d: 4}

const returnedTarget = Object.assign(target, source);

console.log(target);
console.log(source);
console.log(returnedTarget);

// $ node test.js
// { a: 1, b: 3, d: 4 }
// { b: 3, d: 4 }
// { a: 1, b: 3, d: 4 }


let user = {
    username: "John",
};

let user_id = {
    id: 1,
};

let email = {
    email: "john@example.com",
};

user = Object.assign(user, user_id, email);

console.log(user);

  //結果
  //{ username: 'John', id: 1, email: 'john@example.com' }


Object.assignを使った例(form)

const form = {
    firstName: null,
    lastName: null,
    Email: null,
    zipCode: null,
    Address: null,
    Phone: null
}

const input = {
    firstName: 'John',
    lastName: 'Doe',
    Email: 'john@example.com'
}

Object.assign(form, input)

console.log(form)

// $ node test.js
//  {
//  firstName: 'John',
//  lastName: 'Doe',
//  Email: 'john@example.com',
//  zipCode: null,
//  Address: null,
//  Phone: null
// }


2021年8月24日火曜日

JS:Atomic Design

1.Atomic Designのコンポーネント

Atomic Designで有名なのは、UIの構造を次の5段階で考える

  • Atoms(原子)
  • Molecules(分子)
  • Organisms(有機体)
  • Templates(テンプレート)
  • Pages(ページ)


あらゆるものが原子の集まりでできているように、ページもAtoms(原子)の集まりであると考え、Atomsが組み合わさってMolecules(分子)になり、Molecules(分子)が組み合わさってOrganisms(有機体)となる。

Organisms(有機体)の集まりでページのフレームであるTemplates(テンプレート)を作るが、このTemplatesは具体的なコンテンツの入っていない空っぽのページの枠組み。そこに具体的なテキストや画像を入れ込んでPages(ページ)を作る……というふうに、UIを階層構造で考えます。


1.1.Atoms(原子)の例

Atomsの例は、検索フォームのパーツです。ここでは「ラベル」「テキスト入力欄」「ボタン」というように、これ以上分けられないような単位のUIをAtomとしています。


1.2.Molecules(分子)の例

「ラベル」「テキスト入力欄」「ボタン」といったAtomを組み合わせて作った「検索フォーム」がMoleculeです。


1.3.Organisms(有機体)の例

そして、この「検索フォーム」やその他「メニュー」や「サイトのロゴ」など、他のMoleculeらが集まってできている「ヘッダ」がOrganismです。


1.4.Templates(テンプレート)の例

Organismを組み合わせ、ページの枠組みを構成します。ただ中身はすべてダミーです。これがTemplate。


1.5.Pages(ページ)の例

Templateに具体的なコンテンツを入れ込んだのが、最終的なアウトプットとなるPageです。


このように、UIを分解して考え、他の画面でも共通で使えるAtom、Moleculeをどう設計するか、細部を作りながら全体を俯瞰し、また細部が適切かどうか、具体的にコンテンツを入れながら考えるというのが、Atomic DesignのUIデザイン方法論です。

このようにしてUIを考え、サイト全体として共通のUIライブラリを作り上げます。



React:React + Material-UI

ReactとMaterial-UIを利用した管理画面などを作成することが多くなりました

また、前提としてJavaScriptやTypeScriptの知識があるとより分かりやすいです。


1.アプリの作成

Create React Appで新しいReactアプリを作成します。

npx create-react-app react-material-ui-sample --template typescript        

プロジェクトのディレクトリへ移動して実行します。

cd react-material-ui-sample    

npm start    


2.ディレクトリ構成

ディレクトリはあまりネストさせすぎずシンプルな構造にしました。
コンポーネントの分け方はAtomic Designを参考にしています。

3.ルーティング

ルーティングを実装するためのライブラリをインストールします。

npm install --save react-router-dom    

npm install --save-dev @types/react-router-dom    


4.Material-UIを導入

Reactアプリの準備が出来たので、Material-UIを導入していきます。


4.1.Material-UIをインストール

npm install --save @material-ui/core @material-ui/icons    


4.2.フォントを導入

Material-UIと相性の良いGoogle日本語フォントとフォントアイコンを導入しておきます。public/index.htmlのヘッダーにCDNのURLを追加します。

index.html

<head>

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+JP&subset=japanese" />

    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

</head>

4.3.テンプレートを作成

src/components/templates/にページのテンプレートを作成します。Material-UIが公開しているテンプレートを参考に実装します。

import React from "react";
import clsx from "clsx";
import { createTheme } from "@material-ui/core/styles";
import * as colors from "@material-ui/core/colors";
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
import { ThemeProvider } from "@material-ui/styles";
import CssBaseline from "@material-ui/core/CssBaseline";
import Drawer from "@material-ui/core/Drawer";
import Box from "@material-ui/core/Box";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import List from "@material-ui/core/List";
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import Container from "@material-ui/core/Container";
import { Link } from "react-router-dom";
import MenuIcon from "@material-ui/icons/Menu";
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import IconButton from "@material-ui/core/IconButton";
import HomeIcon from "@material-ui/icons/Home";
import ShoppingCartIcon from "@material-ui/icons/ShoppingCart";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";

const drawerWidth = 240;

const theme = createTheme({
  typography: {
    fontFamily: [
      "Noto Sans JP",
      "Lato",
      "游ゴシック Medium",
      "游ゴシック体",
      "Yu Gothic Medium",
      "YuGothic",
      "ヒラギノ角ゴ ProN",
      "Hiragino Kaku Gothic ProN",
      "メイリオ",
      "Meiryo",
      "MS Pゴシック",
      "MS PGothic",
      "sans-serif",
    ].join(","),
  },
  palette: {
    primary: { main: colors.blue[800] }, // テーマの色
  },
});

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      display: "flex",
    },
    toolbar: {
      paddingRight: 24,
    },
    toolbarIcon: {
      display: "flex",
      alignItems: "center",
      justifyContent: "flex-end",
      padding: "0 8px",
      ...theme.mixins.toolbar,
    },
    appBar: {
      zIndex: theme.zIndex.drawer + 1,
      transition: theme.transitions.create(["width", "margin"], {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
      }),
    },
    appBarShift: {
      marginLeft: drawerWidth,
      width: `calc(100% - ${drawerWidth}px)`,
      transition: theme.transitions.create(["width", "margin"], {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.enteringScreen,
      }),
    },
    menuButton: {
      marginRight: 36,
    },
    menuButtonHidden: {
      display: "none",
    },
    title: {
      flexGrow: 1,
    },
    pageTitle: {
      marginBottom: theme.spacing(1),
    },
    drawerPaper: {
      position: "relative",
      whiteSpace: "nowrap",
      width: drawerWidth,
      transition: theme.transitions.create("width", {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.enteringScreen,
      }),
    },
    drawerPaperClose: {
      overflowX: "hidden",
      transition: theme.transitions.create("width", {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
      }),
      width: theme.spacing(7),
      [theme.breakpoints.up("sm")]: {
        width: theme.spacing(9),
      },
    },
    appBarSpacer: theme.mixins.toolbar,
    content: {
      flexGrow: 1,
      height: "100vh",
      overflow: "auto",
    },
    container: {
      paddingTop: theme.spacing(4),
      paddingBottom: theme.spacing(4),
    },
    paper: {
      padding: theme.spacing(2),
      display: "flex",
      overflow: "auto",
      flexDirection: "column",
    },
    link: {
      textDecoration: "none",
      color: theme.palette.text.secondary,
    },
  })
);

const Copyright = () => {
  return (
    <Typography variant="body2" color="textSecondary" align="center">
      {"Copyright © "}
      <Link color="inherit" to="/">
        管理画面
      </Link>{" "}
      {new Date().getFullYear()}
      {"."}
    </Typography>
  );
};

export interface GenericTemplateProps {
  children: React.ReactNode;
  title: string;
}

const GenericTemplate: React.FC<GenericTemplateProps> = ({
  children,
  title,
}) => {
  const classes = useStyles();
  const [open, setOpen] = React.useState(true);
  const handleDrawerOpen = () => {
    setOpen(true);
  };
  const handleDrawerClose = () => {
    setOpen(false);
  };

  return (
    <ThemeProvider theme={theme}>
      <div className={classes.root}>
        <CssBaseline />
        <AppBar
          position="absolute"
          className={clsx(classes.appBar, open && classes.appBarShift)}
        >
          <Toolbar className={classes.toolbar}>
            <IconButton
              edge="start"
              color="inherit"
              aria-label="open drawer"
              onClick={handleDrawerOpen}
              className={clsx(
                classes.menuButton,
                open && classes.menuButtonHidden
              )}
            >
              <MenuIcon />
            </IconButton>
            <Typography
              component="h1"
              variant="h6"
              color="inherit"
              noWrap
              className={classes.title}
            >
              管理画面
            </Typography>
          </Toolbar>
        </AppBar>
        <Drawer
          variant="permanent"
          classes={{
            paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose),
          }}
          open={open}
        >
          <div className={classes.toolbarIcon}>
            <IconButton onClick={handleDrawerClose}>
              <ChevronLeftIcon />
            </IconButton>
          </div>
          <Divider />
          <List>
            <Link to="/" className={classes.link}>
              <ListItem button>
                <ListItemIcon>
                  <HomeIcon />
                </ListItemIcon>
                <ListItemText primary="トップページ" />
              </ListItem>
            </Link>
            <Link to="/products" className={classes.link}>
              <ListItem button>
                <ListItemIcon>
                  <ShoppingCartIcon />
                </ListItemIcon>
                <ListItemText primary="商品ページ" />
              </ListItem>
            </Link>
          </List>
        </Drawer>
        <main className={classes.content}>
          <div className={classes.appBarSpacer} />
          <Container maxWidth="lg" className={classes.container}>
            <Typography
              component="h2"
              variant="h5"
              color="inherit"
              noWrap
              className={classes.pageTitle}
            >
              {title}
            </Typography>
            {children}
            <Box pt={4}>
              <Copyright />
            </Box>
          </Container>
        </main>
      </div>
    </ThemeProvider>
  );
};

export default GenericTemplate;

簡単に記載している



2021年8月22日日曜日

JS:Babel

1.Babelとは

Babel(読み方:「バベル」)は、次の世代のJavaScriptの標準機能をブラウザのサポートを待たずに使えるようにするNode.js製のツールである。次の世代の標準機能を使って書かれたコードを、それらの機能をサポートしないブラウザでも動くコードに変換する。

JavaScriptのコードを新しい書き方から古い書き方に変換するツールである。
具体的には、JavaScriptの言語仕様であるES2015以上の仕様のJavaScriptで記述すると、古いブラウザでは動作しない。そこで、Babelを使ってES2015・ES2016といった仕様で記述したJavaScriptファイルを互換性のあるEXMAScript5に変換する。

主に、es2015以降の新しい構文をes5の構文に変換するのに利用されています。もちろん、設定によってはes2018→es2015といった記述方法も可能です。その他にも、ReactのJSXやTypeScriptの変換にも使われます。

2.インストール

package.jsonがある場所で以下のコマンドを実行する。babel-cliはbabelをcliで実行するための本体モジュールで、babel-preset-envはES6の内容をES5に変換する情報のようなもの。

npmで三つのパッケージをインストールします。

$ npm install @babel/core @babel/cli @babel/preset-env --save-dev

@babel/core: Babel本体

@babel/cli: コマンドライン操作用

@babel/preset-env: 変換内容設定用


3..babelrcによる設定

Babelはそのままでは何も変換してはくれないので、どのように変換するのかを事前に設定します。作業フォルダのルートに.babelrcを作成し、JSON形式で設定情報を追記していきます。

{

  "presets": ["@babel/preset-env"]

}


4.プリセット(Presets)の基本

プリセットは、Babelが変換処理を行う際に利用するプラグインのコレクションです。設定情報を基に、コンパイルに必要なプラグインのリストをBabel本体に渡す役割をしているそうです。公式では下記4つのプリセットが用意されています。その他にもいろいろ開発されています。

  • @babel/preset-env: ECMAScript用
  • @babel/preset-flow: Flow用
  • @babel/preset-react: React用
  • @babel/preset-typescript: Typescript用


利用したいプリセットをインストールした後、配列で名前を指定します。後ろから順に適用されます。下記のケースでは、typescript → react → env の順で実行されます。

{

  "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]

}

各プリセットにて詳細な設定をしたい場合は、配列で囲みます。0番目に名前、1番目に設定情報です。

{

  "presets": [

    ["@babel/preset-env", {<@babel/preset-envの設定値>}],

    "@babel/preset-react",

    "@babel/preset-typescript"

  ]

}


4.1.@babel/preset-env

@babel/preset-envは、出力したいECMAScriptのバージョンを指定するためのプリセットです。基本的には、サポートしたい範囲(ブラウザバージョンやブラウザシェア、サポートの可否など)を指定します。指定内容に応じて適切なバージョンのJSに変換されるようにしてくれます。特に何も指定しない場合は、一律es5の構文に変換されます。

下記の設定は、Chrome40以上・iOS10以上をサポート対象とした場合です。es5で出力されました。


{

  "presets": [

    ["@babel/preset-env", {

        "targets": [

          "chrome 40",

          "iOS 10"

        ]

      }

    ]

  ]

}

cover 80% in JPは、ブラウザシェア率を基に日本で80%のユーザをカバーするという指定です。es5で出力されました。モダンブラウザの新しいバージョンを指定すると、es2015以降のバージョンで出力されます。


4.2.PolyfillとuseBuiltInsオプション

初期設定では、@babel/preset-envが行うのは構文の変換のみです。Promiseやgeneratorなど、新たに追加された機能の変換には対応していません。オプションで、useBuiltInsにusageを指定すると、必要に応じてPolyfill(代替コード)に変換してくれます。(初期値はfalse)

{

  "presets": [

    ["@babel/preset-env", {

        "targets": [...],

        "useBuiltIns": "usage"

      }

    ]

  ]

}


@babel/polyfill

2019年2月現在、「useBuiltIns:'usage'」の項目には「experimental(実験的)」とあります。お使いの環境でうまく動かなかった場合は、@babel/polyfillと「useBuiltIns:'entry'」の合わせ業もあります。


@babel/polyfillをインストール

$ npm install @babel/polyfill --save

アプリ(サイト)内の一箇所にだけ、下記インポート文を記述します。複数個所に記述するとエラーとなるそうです。この記述は、必要に応じて個別のpolyfillのインポート文に書き換えられます。


// 記述する内容

import "@babel/polyfill";


// 最終的に個別のimport文に変換される

import "core-js/modules/es7.string.pad-start";

import "core-js/modules/es7.string.pad-end";


4.3.設定ファイルをpackage.jsonにまとめる

設定ファイルを増やしたくない場合は、package.jsonにまとめることも可能です。babelという項目を追加します。記述方法は.babelrcの場合と同様です。またwebpack同様、コマンドをscriptsに登録して利用することも可能です。


{

  "name": "sample",

  "version": "1.0.0",

  "main": "index.js",

  "scripts": {

    "build": "babel ./src/index.js --out-file ./dist/index.js"

  },

  "devDependencies": {

    "@babel/cli": "^7.2.3",

    "@babel/core": "^7.2.4",

    "@babel/preset-env": "^7.3.4",

    "webpack": "^4.29.6",

    "webpack-cli": "^3.2.3"

  },

  "babel": {

    "presets": ["@babel/preset-env", {

        "targets": [...],

        "useBuiltIns": "usage"

      }

    ]

  }

}

以上がBabelの基本(の一部)となります。設定方法一つとっても手段が多種多様で迷います。。。