-
Notifications
You must be signed in to change notification settings - Fork 0
Basic Syntax
Makoto Horikawa edited this page Jul 9, 2025
·
1 revision
GitHub Power Scouterで使用されているReact + TypeScriptの基本文法を、初心者にも分かりやすく実際のコードサンプルと共に解説します。
// 関数コンポーネント(推奨)
const UserInput: React.FC = () => {
return (
<div>
<input type="text" placeholder="GitHub Username" />
<button>SCAN</button>
</div>
);
};
// TypeScript型定義付きコンポーネント
interface UserInputProps {
onScan: (username: string) => void;
isScanning: boolean;
}
const UserInput: React.FC<UserInputProps> = ({ onScan, isScanning }) => {
return (
<div>
<input type="text" placeholder="GitHub Username" />
<button disabled={isScanning} onClick={() => onScan('username')}>
{isScanning ? 'SCANNING...' : 'SCAN'}
</button>
</div>
);
};// JSX基本構文
const ScouterDisplay = () => {
const powerLevel = 9001;
const isScanning = true;
return (
<div className="scouter-display">
{/* コメント記法 */}
<h1>Power Level: {powerLevel}</h1>
{/* 条件分岐 */}
{isScanning ? (
<p>Scanning in progress...</p>
) : (
<p>Scan complete!</p>
)}
{/* 配列のレンダリング */}
{['JavaScript', 'TypeScript', 'React'].map(skill => (
<span key={skill} className="skill-badge">
{skill}
</span>
))}
{/* イベントハンドラー */}
<button onClick={() => console.log('Clicked!')}>
Click me
</button>
</div>
);
};import { useState } from 'react';
const UserInput = () => {
// 基本的な状態
const [username, setUsername] = useState('');
const [isScanning, setIsScanning] = useState(false);
// 複合的な状態
const [user, setUser] = useState<{
name: string;
followers: number;
} | null>(null);
// 配列の状態
const [languages, setLanguages] = useState<string[]>([]);
const handleScan = () => {
setIsScanning(true);
// 非同期処理の例
setTimeout(() => {
setUser({
name: 'Test User',
followers: 1000,
});
setLanguages(['JavaScript', 'TypeScript']);
setIsScanning(false);
}, 2000);
};
return (
<div>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter username"
/>
<button onClick={handleScan} disabled={isScanning}>
{isScanning ? 'Scanning...' : 'Scan'}
</button>
{user && (
<div>
<h3>{user.name}</h3>
<p>Followers: {user.followers}</p>
<ul>
{languages.map(lang => (
<li key={lang}>{lang}</li>
))}
</ul>
</div>
)}
</div>
);
};import { useState, useEffect } from 'react';
const ScouterDisplay = ({ username }: { username: string }) => {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(false);
// コンポーネントマウント時の処理
useEffect(() => {
console.log('Component mounted');
// クリーンアップ関数
return () => {
console.log('Component unmounted');
};
}, []); // 空の依存配列 = マウント時のみ実行
// 特定の値が変更された時の処理
useEffect(() => {
if (!username) return;
setLoading(true);
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${username}`);
const data = await response.json();
setUserData(data);
} catch (error) {
console.error('Error fetching user:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [username]); // username が変更される度に実行
// タイマーの例
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => clearInterval(timer); // クリーンアップ
}, []);
return (
<div>
{loading ? (
<p>Loading...</p>
) : (
<div>{userData && <p>User: {userData.name}</p>}</div>
)}
</div>
);
};import { useState, useCallback, useMemo } from 'react';
const PowerLevelCalculator = ({ userData, repoData }) => {
const [multiplier, setMultiplier] = useState(1);
// 重い計算をメモ化
const powerLevel = useMemo(() => {
console.log('Calculating power level...');
const basePower = (
userData.public_repos * 100 +
userData.followers * 50 +
userData.following * 10
);
return basePower * multiplier;
}, [userData, multiplier]); // 依存配列が変更時のみ再計算
// 関数をメモ化
const handleScan = useCallback(() => {
console.log('Scanning user...');
// スキャン処理
}, [userData]); // userData が変更時のみ新しい関数を生成
// 配列の処理をメモ化
const sortedLanguages = useMemo(() => {
return repoData
.flatMap(repo => repo.languages)
.sort((a, b) => b.bytes - a.bytes);
}, [repoData]);
return (
<div>
<h2>Power Level: {powerLevel.toLocaleString()}</h2>
<button onClick={handleScan}>Rescan</button>
<div>
<label>
Multiplier:
<input
type="range"
min="1"
max="10"
value={multiplier}
onChange={(e) => setMultiplier(Number(e.target.value))}
/>
</label>
</div>
<ul>
{sortedLanguages.map(lang => (
<li key={lang.name}>{lang.name}</li>
))}
</ul>
</div>
);
};// 基本的な型
let username: string = 'torvalds';
let powerLevel: number = 9001;
let isScanning: boolean = true;
// 配列の型
let languages: string[] = ['JavaScript', 'TypeScript'];
let numbers: Array<number> = [1, 2, 3];
// オブジェクトの型
interface GitHubUser {
login: string;
id: number;
name: string | null; // null許容型
followers: number;
following: number;
public_repos: number;
created_at: string;
bio?: string; // オプショナル(undefined許容)
}
// 型エイリアス
type PowerLevel = number;
type UserData = GitHubUser;
// Union型
type Theme = 'light' | 'dark' | 'auto';
type Status = 'loading' | 'success' | 'error';
// 関数の型
type ScanFunction = (username: string) => Promise<GitHubUser>;
type EventHandler = (event: React.MouseEvent<HTMLButtonElement>) => void;// インターフェース(推奨:オブジェクト定義)
interface GitHubRepo {
id: number;
name: string;
full_name: string;
stargazers_count: number;
language: string | null;
}
// インターフェースの拡張
interface DetailedRepo extends GitHubRepo {
description: string;
topics: string[];
contributors: GitHubUser[];
}
// 型エイリアス(推奨:Union型、計算型)
type ApiResponse<T> = {
data: T;
status: 'success' | 'error';
message?: string;
};
type UserApiResponse = ApiResponse<GitHubUser>;
type RepoApiResponse = ApiResponse<GitHubRepo[]>;
// 条件付き型
type NonNullable<T> = T extends null | undefined ? never : T;
type UserName = NonNullable<GitHubUser['name']>;// ユーザー定義型ガード
function isGitHubUser(data: any): data is GitHubUser {
return (
typeof data === 'object' &&
data !== null &&
typeof data.login === 'string' &&
typeof data.id === 'number'
);
}
// 使用例
const fetchUser = async (username: string): Promise<GitHubUser> => {
const response = await fetch(`/api/users/${username}`);
const data = await response.json();
if (isGitHubUser(data)) {
return data; // 型が確定
}
throw new Error('Invalid user data');
};
// in演算子を使った型ガード
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: GitHubUser };
type ErrorState = { status: 'error'; message: string };
type AppState = LoadingState | SuccessState | ErrorState;
const handleState = (state: AppState) => {
if ('data' in state) {
// SuccessState として扱われる
console.log(state.data.login);
} else if ('message' in state) {
// ErrorState として扱われる
console.error(state.message);
} else {
// LoadingState として扱われる
console.log('Loading...');
}
};// GitHub API用カスタムフック
const useGitHubApi = () => {
const [token, setToken] = useState<string | null>(
localStorage.getItem('github_token')
);
const getHeaders = useCallback((): HeadersInit => {
return token ? { Authorization: `token ${token}` } : {};
}, [token]);
const fetchUser = useCallback(async (username: string): Promise<GitHubUser> => {
const response = await fetch(`https://api.github.com/users/${username}`, {
headers: getHeaders(),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}, [getHeaders]);
const fetchRepos = useCallback(async (username: string): Promise<GitHubRepo[]> => {
const response = await fetch(`https://api.github.com/users/${username}/repos`, {
headers: getHeaders(),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}, [getHeaders]);
return {
token,
setToken,
fetchUser,
fetchRepos,
};
};
// 使用例
const UserProfile = ({ username }: { username: string }) => {
const { fetchUser } = useGitHubApi();
const [user, setUser] = useState<GitHubUser | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const loadUser = async () => {
setLoading(true);
setError(null);
try {
const userData = await fetchUser(username);
if (!cancelled) {
setUser(userData);
}
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
loadUser();
return () => {
cancelled = true;
};
}, [username, fetchUser]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user data</div>;
return (
<div>
<h2>{user.name || user.login}</h2>
<p>Followers: {user.followers}</p>
<p>Following: {user.following}</p>
</div>
);
};// 基本コンポーネント
const Button: React.FC<{
variant?: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}> = ({
variant = 'primary',
size = 'medium',
disabled = false,
onClick,
children
}) => {
const className = `button button--${variant} button--${size}`;
return (
<button
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
// 合成コンポーネント
const ScanButton: React.FC<{
isScanning: boolean;
onScan: () => void;
}> = ({ isScanning, onScan }) => {
return (
<Button
variant="primary"
size="large"
disabled={isScanning}
onClick={onScan}
>
{isScanning ? 'SCANNING...' : 'SCAN'}
</Button>
);
};
// 高階コンポーネント(HOC)
const withLoading = <P extends object>(
Component: React.ComponentType<P>
) => {
return (props: P & { isLoading: boolean }) => {
const { isLoading, ...rest } = props;
if (isLoading) {
return <div>Loading...</div>;
}
return <Component {...(rest as P)} />;
};
};
// 使用例
const UserProfileWithLoading = withLoading(UserProfile);// Reducer パターン
type State = {
user: GitHubUser | null;
repos: GitHubRepo[];
loading: boolean;
error: string | null;
};
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_USER_SUCCESS'; payload: GitHubUser }
| { type: 'FETCH_REPOS_SUCCESS'; payload: GitHubRepo[] }
| { type: 'FETCH_ERROR'; payload: string }
| { type: 'RESET' };
const initialState: State = {
user: null,
repos: [],
loading: false,
error: null,
};
const userReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_USER_SUCCESS':
return { ...state, user: action.payload, loading: false };
case 'FETCH_REPOS_SUCCESS':
return { ...state, repos: action.payload, loading: false };
case 'FETCH_ERROR':
return { ...state, error: action.payload, loading: false };
case 'RESET':
return initialState;
default:
return state;
}
};
// 使用例
const UserDashboard = () => {
const [state, dispatch] = useReducer(userReducer, initialState);
const { fetchUser, fetchRepos } = useGitHubApi();
const handleFetchUser = useCallback(async (username: string) => {
dispatch({ type: 'FETCH_START' });
try {
const user = await fetchUser(username);
dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
const repos = await fetchRepos(username);
dispatch({ type: 'FETCH_REPOS_SUCCESS', payload: repos });
} catch (error) {
dispatch({
type: 'FETCH_ERROR',
payload: error instanceof Error ? error.message : 'Unknown error'
});
}
}, [fetchUser, fetchRepos]);
return (
<div>
<button onClick={() => handleFetchUser('torvalds')}>
Fetch User
</button>
{state.loading && <div>Loading...</div>}
{state.error && <div>Error: {state.error}</div>}
{state.user && (
<div>
<h2>{state.user.name}</h2>
<p>Repos: {state.repos.length}</p>
</div>
)}
</div>
);
};-
基本コンポーネント作成
- ユーザー情報を表示するコンポーネント
- Props でデータを受け取る
- 条件分岐でローディング状態を表示
-
状態管理
- useState でフォーム入力を管理
- useEffect で API 呼び出し
- エラーハンドリング
-
カスタムフック作成
- GitHub API 呼び出し用フック
- ローディング、エラー状態の管理
- TypeScript 型定義
-
パフォーマンス最適化
- useMemo で重い計算をメモ化
- useCallback で関数をメモ化
- React.memo でコンポーネントをメモ化
-
高階コンポーネント
- エラーバウンダリー実装
- ローディング状態のラッパー
- 認証チェック HOC
-
複雑な状態管理
- useReducer で複雑な状態管理
- Context API でグローバル状態
- 型安全なアクション定義
次のステップ: Tips & Best Practicesで実践的な開発テクニックを学びましょう。