2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【備忘録】日本一わかりやすいReact-Redux講座 実践編 #8 「Swiperで画像スライダーを作ろう」

Posted at

はじめに

この記事は、トラハック氏(@torahack_)が運営するYoutubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux講座 実践編』の学習備忘録です。

前回まで講座で、Products の CRUD 機能を一通り実装しました。

今回の講座では、

  • React-swiperを用いた商品情報詳細ページ(CRUD の R)
  • MuiThemeProviderによる Material-UI のテーマカラー設定

について実装します。

※ 前回記事: 【備忘録】日本一わかりやすいReact-Redux講座 実践編 #7 「商品を一覧表示しよう」

要点

  • <MuiThemeProvider>で、Material-UI のテーマカラーを設定できる。
  • react-swiperで画像スワイプ機能を実装できる。
  • Material-UIの<Table>コンポーネントでテーブルを作成できる。

完成系イメージ

http://localhost:3000/product/jJV3RsxCgFllZeo771wR

image.png

商品情報の詳細ページを作成します。

左側が画像スライダー、右側がサイズ表示用のテーブルになっています。

サイズ表示用テーブルは、Material-UIの<Table>コンポーネントを使うことで比較的容易に作成できます。

画像スライダーは、react-id-swiperというnpmパッケージをインストールすることで、非常に簡単に実装することができます。

メイン

MuiThemeProvider によるテーマカラー設定

Material-UI において、アプリ内で使用するテーマカラーを設定する機能としてMuiThemeProviderというものがあります。

これを用いることで、例えばコンポーネント内のフォント一つ一つにカラーコードを定義するような必要がなくなります。

また、「primaryが赤、secondaryが青」のように限られた数の色をテーマカラーを設定して運用するため、無駄な色の使いすぎを自然と防ぐことにも繋がります。

今回は既存のthemeファイルを読み込むだけですが、一連の流れが理解できれば、テーマカラーを簡単に変更することができます。

実装ファイル(theme関連)
1.src/assets/theme.js  ## themeカラーが定義されたファイル
2.src/index.js  ## theme.jsを読み込み、アプリ全体に適用
src/assets/theme.js
import { createMuiTheme } from '@material-ui/core/styles';

// Pick colors on https://material.io/resources/color/#!/

export const theme = createMuiTheme({
  palette: {
    primary: {
      light: '#88ffff',
      main: '#4dd0e1',
      dark: '#009faf',
      contrastText: '#000',
    },
    secondary: {
      light: '#ffff81',
      main: '#ffd54f',
      dark: '#c8a415',
      contrastText: '#000',
    },
    grey: {
      50: "#fafafa",
      100: "#f5f5f5",
      200: "#eeeeee",
      300: "#e0e0e0",
      400: "#bdbdbd",
      500: "#9e9e9e",
      600: "#757575",
      700: "#616161",
      800: "#424242",
      900: "#212121",
      A100: "#d5d5d5",
      A200: "#aaaaaa",
      A400: "#303030",
      A700: "#616161"
    }
  },
});

themeカラーをカラーコードとして定義します。これを、アプリ全体に適用させます。

src/index.js
import React from 'react';
.
.
.
import {MuiThemeProvider} from "@material-ui/core";
import {theme} from "./assets/theme"
.
.
.
ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <MuiThemeProvider theme={theme}>
        <App />
      </MuiThemeProvider>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);
.
.
.

<MuiThemeProvider><App />を丸ごとラッピングして、設定完了です。

これにより、各コンポーネントのmakeStyle()内において、

任意のコンポーネントファイル
const useStyles = makeStyles((theme) => ({
  root: {
    color: theme.palette.primary.main
  }
  .
  .
  .
}));

theme.jsで定義した色を適用することができます。

今回のtheme.jsでは、primaryが水色系、secondaryが黄色系で配色されています。

詳細ページの大枠作成

商品情報詳細ページの大枠を作成します。

ひとまずは、URLに含まれるid情報を元に正しいproductsをCloud Firstoreから取り出すところまで進めます。テンプレート、及びパスは、

  • テンプレート: ProductDetail.jsx
  • path: /product/:id

として実装します。

実装ファイル(詳細ページ大枠)
1.src/Router.jsx
2.src/templates/ProductDetail.jsx
3.src/templates/index.js
1.src/Router.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {ProductDetail,ProductEdit,ProductList,Reset,SignIn,SignUp} from "./templates";
import Auth from "./Auth"

const Router = () => {
  return (
    <Switch>
      <Route exact path={"/signup"} component={SignUp} />
      <Route exact path={"/signin"} component={SignIn} />
      <Route exact path={"/signin/reset"} component={Reset} />

      <Auth>
        <Route exact path={"(/)?"} component={ProductList} />
        <Route exact path={"/product/:id"} component={ProductDetail} /> {/*追記*/}
        <Route path={"/product/edit(/:id)?"} component={ProductEdit} />
      </Auth>
    </Switch>
  );
};

export default Router
2.src/templates/ProductDetail.jsx
import React,{ useState,useEffect,useCallback } from "react";
import {useSelector} from "react-redux"
import {db} from "../firebase"
import { makeStyles } from "@material-ui/core";
import HTMLReactParser from "html-react-parser"

const useStyles = makeStyles((theme)=>({
  sliderBox: {
    [theme.breakpoints.down("sm")]: {
      margin: "0 auto 24px auto",
      height: 320,
      width: 320
    },
    [theme.breakpoints.up("sm")]:{
      margin: "0 auto",
      height: 400,
      width: 400
    }
  },
  detail: {
    [theme.breakpoints.down("sm")]: {
      margin: "0 auto 16px auto",
      height: "auto",
      width: 320
    },
    [theme.breakpoints.up("sm")]:{
      margin: "0 auto",
      height: "auth",
      width: 400
    }
  },
  price: {
    fontSize: 36
  }
}));

const returnCodeToBr = (text) => {
  if (text === "") {
      return text
  } else {
      return HTMLReactParser(text.replace(/\r?\n/g, '<br/>'))
  }
};

const ProductDetail = () => {
  const classes = useStyles();
  const selector = useSelector((state)=>state);
  const path = selector.router.location.pathname;
  const id = path.split("/product/")[1];

  const [product,setProduct] = useState();

  useEffect(()=>{
    db.collection("products").doc(id).get()
    .then(doc => {
      const data = doc.data();
      setProduct(data)
    })
  },[]);

  return (
    <section className="c-sention-wrapin">
      {product && (
        <div className="p-grid__row" >
          <div className={classes.sliderBox}>

          </div>
          <div className={classes.detail}>
            <h2 className="u-text__headline">{product.name}</h2>
            <p className={classes.price}>{product.price.toLocaleString()}</p>
            <div className="module-spacer--small"></div>
            <div className="module-spacer--small"></div>
            <p>{returnCodeToBr(product.description)}</p>
          </div>
        </div>
      )}
    </section>
  )
};

export default ProductDetail
  • [theme.breakpoints.down("sm")]: {...}とすることで、smサイズの幅をブレイクポイントとして、適用するスタイルを変更しています。
  • useEffect()を用いて、初期レンダー時に、DBから該当するidの情報を取得します。
  • returnCodeToBr()で、改行コード\nを、改行のHTMLタグ<br />に変換しています。この関数に用いているhtml-react-parserは、この時点ではインストールしていないはずなので、npm からインストールします。
html-react-parserのインストール
$ npm install --save html-react-parser
3.src/templates/index.js
export {default as Home} from './Home'
export {default as ProductDetail} from './ProductDetail'
export {default as ProductEdit} from './ProductEdit'
export {default as ProductList} from './ProductList'
export {default as Reset} from './Reset'
export {default as SignIn} from './SignIn'
export {default as SignUp} from './SignUp'

いったん動作確認をします。すでにProductCard.jsxにおいて、リストアイテムをクリックすることで/product/:idへ遷移されるよう定義されているため、そこからアクセスします。

http://localhost:3000

image.png

スカートをクリックすると、

http://localhost:3000/product/ewzuLXPK2Dr0owCG3dt7

image.png

スカートの商品情報が取得できていることが分かります!

画像スライダーの設置

このProductDetailテンプレートに対して、画像スライダーを設置します。

画像スライダーの実装には、react-id-swiperというnpmパッケージを利用します。依存関係にあるswiperと合わせて、インストールしておきます。

swiperとreact-id-swiperをインストール
$  npm install -S swiper@5.4.2 react-id-swiper@3.0.0  

画像スライダー用のコンポーネントしてImageSwiper.jsxを定義し、それをProductDetail.jsxで読み込みます。

実装ファイル(画像スライダー)
1.src/components/Products/ImageSwiper.jsx
2.src/components/Products/index.js
3.src/templates/ProductDetail.jsx
1.src/components/Products/ImageSwiper.jsx
import React, { useState } from "react";
import Swiper from "react-id-swiper";
import NoImage from "../../assets/img/src/no_image.png"
import 'swiper/css/swiper.css'

const ImageSwiper = (props) => {
  const [params] = useState({
    pagination: {
      el: ".swiper-pagination",
      type: "bullets",
      clickable: true,
      dynamicBullets: true
    },
    navigation: {
      nextEl: ".swiper-button-next",
      prevEl: ".swiper-button-prev"
    },
    loop: true
  })

  const images = props.images

  return (
    <Swiper {...params}>
      {images.length === 0 ? (
        <div className="p-media__thumb">
          <img src={NoImage} alt="no images"/>
        </div>
      ) : (
        images.map(image => (
          <div className="p-media__thumb" key={image.id}>
            <img src={image.path} alt="商品情報"/>
          </div>
        ))
      )}
    </Swiper>
  );
};

export default ImageSwiper;

react-id-swiperは、

1.import 'swiper/css/swiper.css'でスライダー用のcssを読み込む。

  1. スライダーの性質をparamsとして定義
  2. <Swiper {...params}>としてJSXに展開

という流れで実装をします。

'swiper/css/swiper.css'は、npmインストールすることによって、node_modules下に保存されるcssファイルです。この中で、スライダーとしての動的な挙動が実装されているイメージです。

また、paramsの中で定義するパラメータを変えることで、様々な形式のスライダーを実装できるようです(公式リファレンス参照)

:2.src/components/Products/index.js
export {default as ImageArea} from "./ImageArea"
export {default as ImagePreview} from "./ImagePreview"
export {default as ImageSwiper} from "./ImageSwiper" //追記
export {default as ProductCard} from "./ProductCard"
export {default as SetSizeArea} from "./SetSizeArea"
3.src/templates/ProductDetail.jsx
.
.
.
import {ImageSwiper} from "../components/Products" {*追記*}
.
.
.
const ProductDetail = () => {
  .
  .
  .
  return (
    <section className="c-sention-wrapin">
      {product && (
        <div className="p-grid__row" >
          <div className={classes.sliderBox}>
            <ImageSwiper images={product.images} /> {*追記*}
          </div>
          <div className={classes.detail}>
            <h2 className="u-text__headline">{product.name}</h2>
            <p className={classes.price}>{product.price.toLocaleString()}</p>
            <div className="module-spacer--small"></div>
            <div className="module-spacer--small"></div>
            <p>{returnCodeToBr(product.description)}</p>
          </div>
        </div>
      )}
    </section>
  )
};

export default ProductDetail

画像スライダー動作確認

http://localhost:3000/product/jJV3RsxCgFllZeo771wR

8-1.gif

コード量も少なく簡単に画像スライダーが実装できるのはすばらしいですね!

サイズ表示テーブル

最後に、商品情報のサイズを表示するテーブルを作成します。

<SizeTable>コンポーネントを作成し、<ProductDetail>で読み込みます。

実装ファイル(サイズ表示テーブル)
1.src/components/Products/SizeTable.jsx
2.src/components/Products/index.js
3.src/templates/ProductDetail.jsx
1.src/components/Products/SizeTable.jsx
import React from 'react';
import Table from "@material-ui/core/Table";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import TableBody from "@material-ui/core/TableBody";
import IconButton from "@material-ui/core/IconButton";
import TableContainer from "@material-ui/core/TableContainer";
import ShoppingCartIcon from '@material-ui/icons/ShoppingCart';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import {makeStyles} from "@material-ui/styles";

const useStyles = makeStyles({
  iconCell: {
    padding: 0,
    height: 48,
    width: 48
  }
})

const SizeTable = (props) => {
  const classes = useStyles();

  const sizes = props.sizes;

  return (
    <TableContainer>
      <Table>
        <TableBody>
          {sizes.length > 0 && (
            sizes.map(size => (
              <TableRow key={size.size}>
                <TableCell component="th" scope="row">{size.size}</TableCell>
                <TableCell>残り{size.quantity}</TableCell>
                <TableCell className={classes.iconCell}>
                  {size.quantity > 0 ? (
                    <IconButton>
                      <ShoppingCartIcon />
                    </IconButton>
                  ) : (
                    <div>売切</div>
                  )}
                </TableCell>
                <TableCell className={classes.iconCell}>
                  <IconButton>
                    <FavoriteBorderIcon />
                  </IconButton>
                </TableCell>
            </TableRow>
            ))
          )}
        </TableBody>
      </Table>
    </TableContainer>
  );
};

export default SizeTable;

Material-UIの

  • <TableContainer>
  • <Table>
  • <TableBody>
  • <TableRow>
  • <TableCell>

で、テーブルを実装しています。

2.src/components/Products/index.js
export {default as ImageArea} from "./ImageArea"
export {default as ImagePreview} from "./ImagePreview"
export {default as ImageSwiper} from "./ImageSwiper"
export {default as ProductCard} from "./ProductCard"
export {default as SetSizeArea} from "./SetSizeArea"
export {default as SizeTable} from "./SizeTable" //追記
3.src/templates/ProductDetail.jsx
.
.
.
import {SizeTable} from "../components/Products" {*追記*}
.
.
.
const ProductDetail = () => {
  .
  .
  .
  return (
    <section className="c-sention-wrapin">
      {product && (
        <div className="p-grid__row" >
          <div className={classes.sliderBox}>
            <ImageSwiper images={product.images} />
          </div>
          <div className={classes.detail}>
            <h2 className="u-text__headline">{product.name}</h2>
            <p className={classes.price}>{product.price.toLocaleString()}</p>
            <div className="module-spacer--small"></div>
            <SizeTable sizes={product.sizes}/>  {*追記*}
            <div className="module-spacer--small"></div>
            <p>{returnCodeToBr(product.description)}</p>
          </div>
        </div>
      )}
    </section>
  )
};

export default ProductDetail

<SizeTable>コンポーネントをインポートします。

### 動作確認

http://localhost:3000/product/jJV3RsxCgFllZeo771wR

image.png

左に画像スライダー、右にサイズテーブルが設置されています!

さいごに

今回の要点をおさらいすると、

  • <MuiThemeProvider>で、Material-UI のテーマカラーを設定できる。
  • react-swiperで画像スワイプ機能を実装できる。
  • Material-UIの<Table>コンポーネントでテーブルを作成できる。

以上です!

このような学習内容を日々呟いていますので、よろしければTwitter(@ddpmntcpbr)のフォローもよろしくお願いします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?