メインコンテンツに移動
React DocsのQuick StartをTypeScriptで書き直してみた

概要

Reactの公式ドキュメントの新しいものがbeta版ですが公開されています。( https://beta.reactjs.org/ )

こちらは関数コンポーネントを学習するのにおすすめとなっております。(2023/02/01現在 英語版のみ。)

 

さて、
ReactとTypeScriptの学習も兼ねて、
Quick StartのThinking in React( https://beta.reactjs.org/learn/thinking-in-react )をTypeScriptに書き直してみました。

 

環境

WSL2(Ubuntu 20.04.5)

Node.js 16.14.0

React 18.2.0

Create React App 5.0.1

TypeScript 4.8.4

 

環境構築

$ npx create-react-app sample_app --template typescript

 

プロジェクトを実行

$ npm start

 

ソースコード

  • 各コンポーネントは「components」ディレクトリにファイルを作成した
  • propsの型定義は各コンポーネントファイルに記述した
  • 複数箇所で共通で使用している型定義は「types」ディレクトリにファイルを作成した

 

src/components/FilterableProductTable.tsx

import { useState } from 'react';

 

import { SearchBar } from './SearchBar';

import { ProductTable } from './ProductTable';

 

import type { Product } from '../types/ProductType';

 

type Props = {

  products: Product[];

};

 

export const FilterableProductTable: React.FC<Props> = ({ products }) => {

  const [filterText, setFilterText] = useState('');

  const [inStockOnly, setInStockOnly] = useState(false);

 

  return (

    <>

      <SearchBar

        filterText={filterText}

        inStockOnly={inStockOnly}

        onFilterTextChange={setFilterText}

        onInStockOnlyChange={setInStockOnly}

      />

      <ProductTable products={products} filterText={filterText} inStockOnly={inStockOnly} />

    </>

  );

};

 

 

src/components/ProductCategoryRow.tsx

type Props = {

  category: string;

};

 

export const ProductCategoryRow: React.FC<Props> = ({ category }) => {

  return (

    <tr>

      <th colSpan={2}>{category}</th>

    </tr>

  );

};

 

 

src/components/ProductRow.tsx

import type { Product } from '../types/ProductType';

 

type Props = {

  product: Product;

};

 

export const ProductRow: React.FC<Props> = ({ product }) => {

  const name = product.stocked ? (

    product.name

  ) : (

    <span style={{ color: 'red' }}>{product.name}</span>

  );

 

  return (

    <tr>

      <td>{name}</td>

      <td>{product.price}</td>

    </tr>

  );

};

 

 

src/components/ProductTable.tsx

import { ProductCategoryRow } from './ProductCategoryRow';

import { ProductRow } from './ProductRow';

 

import type { Product } from '../types/ProductType';

 

type Props = {

  products: Product[];

  filterText: string;

  inStockOnly: boolean;

};

 

export const ProductTable: React.FC<Props> = ({ products, filterText, inStockOnly }) => {

  const rows: JSX.Element[] = [];

  let lastCategory: string | null = null;

 

  products.forEach((product) => {

    if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {

      return;

    }

    if (inStockOnly && !product.stocked) {

      return;

    }

    if (product.category !== lastCategory) {

      rows.push(<ProductCategoryRow category={product.category} key={product.category} />);

    }

    rows.push(<ProductRow product={product} key={product.name} />);

    lastCategory = product.category;

  });

 

  return (

    <table>

      <thead>

        <tr>

          <th>Name</th>

          <th>Price</th>

        </tr>

      </thead>

      <tbody>{rows}</tbody>

    </table>

  );

};

 

 

src/components/SearchBar.tsx

import React from 'react';

 

type Props = {

  filterText: string;

  inStockOnly: boolean;

  onFilterTextChange: React.Dispatch<React.SetStateAction<string>>;

  onInStockOnlyChange: React.Dispatch<React.SetStateAction<boolean>>;

};

 

export const SearchBar: React.FC<Props> = ({

  filterText,

  inStockOnly,

  onFilterTextChange,

  onInStockOnlyChange,

}) => {

  return (

    <form>

      <input

        type="text"

        value={filterText}

        placeholder="Search..."

        onChange={(e) => onFilterTextChange(e.target.value)}

      />

      <label>

        <input

          type="checkbox"

          checked={inStockOnly}

          onChange={(e) => onInStockOnlyChange(e.target.checked)}

        />

        Only show products in stock

      </label>

    </form>

  );

};

 

 

src/types/ProductType.ts

export type Product = {

  category: string;

  price: string;

  stocked: boolean;

  name: string;

};

 

 

src/App.tsx

import { FilterableProductTable } from './components/FilterableProductTable';

 

const PRODUCTS = [

  { category: 'Fruits', price: '$1', stocked: true, name: 'Apple' },

  { category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit' },

  { category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit' },

  { category: 'Vegetables', price: '$2', stocked: true, name: 'Spinach' },

  { category: 'Vegetables', price: '$4', stocked: false, name: 'Pumpkin' },

  { category: 'Vegetables', price: '$1', stocked: true, name: 'Peas' },

];

 

export const App = () => {

  return (

    <>

      <FilterableProductTable products={PRODUCTS} />

    </>

  );

};

 

 

src/index.tsx

import React from 'react';

import ReactDOM from 'react-dom/client';

import './index.css';

import { App } from './App';

import reportWebVitals from './reportWebVitals';

 

const root = ReactDOM.createRoot(

  document.getElementById('root') as HTMLElement

);

root.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>

);

 

// If you want to start measuring performance in your app, pass a function

// to log results (for example: reportWebVitals(console.log))

// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

reportWebVitals();

 

 

最後に

こういう簡単なものからReactとTypeScriptの組み合わせを学習していくのが
ハードルが低く、おすすめです。

 

参考

 

記事一覧へ戻る