概要
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の組み合わせを学習していくのが
ハードルが低く、おすすめです。
参考
- React Docs BETA( https://beta.reactjs.org/ )
- Thinking in React( https://beta.reactjs.org/learn/thinking-in-react )