SolidJS で API 通信を極める:fetch/Axios 実践例

SolidJS は、React ライクな構文を持ちながら、より軽量で高速なフレームワークとして注目を集めています。しかし、API 通信の実装となると、多くの開発者が「どの方法が最適なのか」「エラーハンドリングはどうすればいいのか」といった疑問を抱えるのではないでしょうか。
この記事では、SolidJS での API 通信を徹底的に解説します。fetch API から Axios まで、実際のプロジェクトで使える実践的なコード例とともに、よくあるエラーとその解決策も紹介していきます。
SolidJS の特徴である細粒度のリアクティビティを活かした API 通信の実装方法を学び、あなたのアプリケーションを次のレベルに引き上げましょう。
基本的な API 通信の仕組み
HTTP 通信の基礎知識
API 通信の根幹となる HTTP 通信について理解を深めましょう。SolidJS で API 通信を実装する前に、基本的な仕組みを押さえておくことが重要です。
HTTP リクエストには主に以下の種類があります:
- GET: データの取得
- POST: データの作成
- PUT: データの更新(完全置換)
- PATCH: データの部分更新
- DELETE: データの削除
javascript// HTTPステータスコードの例
const statusCodes = {
200: 'OK - リクエスト成功',
201: 'Created - リソース作成成功',
400: 'Bad Request - リクエストが不正',
401: 'Unauthorized - 認証が必要',
403: 'Forbidden - アクセス拒否',
404: 'Not Found - リソースが見つからない',
500: 'Internal Server Error - サーバーエラー',
};
RESTful API の理解
RESTful API は、Web サービスで標準的に使用される API 設計の原則です。SolidJS で API 通信を実装する際も、この原則に従うことで保守性の高いコードを書くことができます。
RESTful API の特徴:
- リソース指向: URL でリソースを表現
- HTTP メソッドの適切な使用: GET、POST、PUT、DELETE を用途に応じて使用
- ステートレス: 各リクエストは独立している
- 統一されたインターフェース: 一貫した API 設計
javascript// RESTful APIのエンドポイント例
const apiEndpoints = {
users: '/api/users',
userById: (id) => `/api/users/${id}`,
posts: '/api/posts',
postById: (id) => `/api/posts/${id}`,
comments: '/api/comments',
};
非同期処理と Promise
SolidJS での API 通信は非同期処理が基本となります。Promise と async/await を理解することで、より読みやすく保守しやすいコードを書くことができます。
javascript// Promiseの基本構造
const fetchData = () => {
return new Promise((resolve, reject) => {
// 非同期処理
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve({ data: '成功データ' });
} else {
reject(new Error('エラーが発生しました'));
}
}, 1000);
});
};
// async/awaitを使った実装
const handleData = async () => {
try {
const result = await fetchData();
console.log(result.data);
} catch (error) {
console.error('エラー:', error.message);
}
};
SolidJS での API 通信の基本
createSignal と createEffect の活用
SolidJS の核となるリアクティビティシステムを活用して、API 通信の状態を管理しましょう。createSignal で状態を定義し、createEffect で副作用を処理します。
javascriptimport { createSignal, createEffect } from 'solid-js';
// データとローディング状態を管理
const [data, setData] = createSignal(null);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
// API通信の基本パターン
const fetchUserData = async (userId) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status}`
);
}
const userData = await response.json();
setData(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
状態管理と API 通信の連携
SolidJS のリアクティビティを活用して、API 通信の結果を自動的に UI に反映させることができます。
javascript// ユーザーIDが変更されたときに自動的にデータを取得
const [userId, setUserId] = createSignal(1);
createEffect(() => {
const currentUserId = userId();
if (currentUserId) {
fetchUserData(currentUserId);
}
});
// コンポーネントでの使用例
const UserProfile = () => {
return (
<div>
{loading() && <p>読み込み中...</p>}
{error() && <p class='error'>エラー: {error()}</p>}
{data() && (
<div>
<h2>{data().name}</h2>
<p>{data().email}</p>
</div>
)}
</div>
);
};
エラーハンドリングの基本
SolidJS でのエラーハンドリングは、ユーザーエクスペリエンスを向上させる重要な要素です。適切なエラーハンドリングを実装しましょう。
javascript// エラーハンドリングの基本パターン
const handleApiError = (error) => {
console.error('API Error:', error);
// エラーの種類に応じた処理
if (
error.name === 'TypeError' &&
error.message.includes('fetch')
) {
return 'ネットワークエラーが発生しました。インターネット接続を確認してください。';
}
if (error.status === 401) {
return '認証が必要です。ログインしてください。';
}
if (error.status === 404) {
return 'リソースが見つかりません。';
}
return '予期しないエラーが発生しました。しばらく時間をおいて再度お試しください。';
};
// エラーハンドリングを含むAPI通信
const safeApiCall = async (apiFunction) => {
try {
return await apiFunction();
} catch (error) {
const userMessage = handleApiError(error);
setError(userMessage);
throw error;
}
};
fetch API を使った実践例
基本的な GET リクエスト
fetch API は、ブラウザ標準の API 通信ライブラリです。追加のライブラリをインストールする必要がなく、軽量で使いやすいのが特徴です。
javascript// 基本的なGETリクエスト
const fetchUsers = async () => {
const [users, setUsers] = createSignal([]);
const [loading, setLoading] = createSignal(false);
const getUsers = async () => {
setLoading(true);
try {
const response = await fetch('/api/users');
// レスポンスのステータスチェック
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status}`
);
}
const data = await response.json();
setUsers(data);
} catch (error) {
console.error('ユーザー取得エラー:', error);
throw error;
} finally {
setLoading(false);
}
};
return { users, loading, getUsers };
};
POST/PUT/DELETE リクエスト
データの作成、更新、削除を行うリクエストの実装例です。適切な HTTP メソッドとヘッダーを設定することが重要です。
javascript// POSTリクエスト - データ作成
const createUser = async (userData) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`作成エラー: ${response.status}`);
}
return await response.json();
};
// PUTリクエスト - データ更新
const updateUser = async (userId, userData) => {
const response = await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`更新エラー: ${response.status}`);
}
return await response.json();
};
// DELETEリクエスト - データ削除
const deleteUser = async (userId) => {
const response = await fetch(`/api/users/${userId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`削除エラー: ${response.status}`);
}
return true;
};
ファイルアップロード
ファイルアップロードの実装例です。FormData を使用してファイルとその他のデータを送信します。
javascript// ファイルアップロードの実装
const uploadFile = async (file, additionalData = {}) => {
const formData = new FormData();
formData.append('file', file);
// 追加データがあれば追加
Object.keys(additionalData).forEach((key) => {
formData.append(key, additionalData[key]);
});
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
// Content-Typeは自動設定されるため指定不要
});
if (!response.ok) {
throw new Error(
`アップロードエラー: ${response.status}`
);
}
return await response.json();
};
// ファイルアップロードコンポーネント
const FileUpload = () => {
const [uploading, setUploading] = createSignal(false);
const [uploadProgress, setUploadProgress] =
createSignal(0);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
setUploading(true);
setUploadProgress(0);
try {
const result = await uploadFile(file, {
description: 'アップロードされたファイル',
});
console.log('アップロード成功:', result);
} catch (error) {
console.error('アップロードエラー:', error);
} finally {
setUploading(false);
setUploadProgress(0);
}
};
return (
<div>
<input
type='file'
onChange={handleFileUpload}
disabled={uploading()}
/>
{uploading() && (
<p>アップロード中... {uploadProgress()}%</p>
)}
</div>
);
};
認証付き API 通信
認証が必要な API 通信の実装例です。JWT トークンやセッションを使用した認証を実装します。
javascript// 認証ヘッダーを付けたAPI通信
const authenticatedFetch = async (url, options = {}) => {
const token = localStorage.getItem('authToken');
const defaultHeaders = {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
};
const config = {
...options,
headers: {
...defaultHeaders,
...options.headers,
},
};
const response = await fetch(url, config);
// 認証エラーの場合
if (response.status === 401) {
localStorage.removeItem('authToken');
// ログインページにリダイレクト
window.location.href = '/login';
throw new Error('認証が必要です');
}
return response;
};
// 認証付きAPI通信の使用例
const fetchUserProfile = async () => {
const response = await authenticatedFetch('/api/profile');
if (!response.ok) {
throw new Error(
`プロフィール取得エラー: ${response.status}`
);
}
return await response.json();
};
Axios を使った実践例
Axios の導入と設定
Axios は、より豊富な機能と使いやすさを提供する HTTP クライアントライブラリです。まずはプロジェクトに導入しましょう。
bash# Axiosのインストール
yarn add axios
javascript// Axiosの基本設定
import axios from 'axios';
// ベースURLの設定
axios.defaults.baseURL = 'https://api.example.com';
// デフォルトヘッダーの設定
axios.defaults.headers.common['Content-Type'] =
'application/json';
// タイムアウトの設定
axios.defaults.timeout = 10000;
// インスタンスの作成(推奨)
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
インターセプターの活用
Axios のインターセプターを使用することで、リクエストとレスポンスを自動的に処理できます。認証トークンの自動付与やエラーハンドリングを一元化できます。
javascript// リクエストインターセプター
apiClient.interceptors.request.use(
(config) => {
// 認証トークンの自動付与
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// リクエストログ
console.log(
'リクエスト:',
config.method?.toUpperCase(),
config.url
);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// レスポンスインターセプター
apiClient.interceptors.response.use(
(response) => {
// レスポンスログ
console.log(
'レスポンス:',
response.status,
response.config.url
);
return response;
},
(error) => {
// エラーハンドリング
if (error.response?.status === 401) {
localStorage.removeItem('authToken');
window.location.href = '/login';
}
console.error(
'API エラー:',
error.response?.data || error.message
);
return Promise.reject(error);
}
);
リクエスト/レスポンスの変換
Axios の transformRequest と transformResponse を使用して、データの自動変換を実装できます。
javascript// データ変換の設定
const apiClientWithTransform = axios.create({
baseURL: 'https://api.example.com',
transformRequest: [
(data, headers) => {
// リクエストデータの変換
if (data && typeof data === 'object') {
// 日付をISO文字列に変換
Object.keys(data).forEach((key) => {
if (data[key] instanceof Date) {
data[key] = data[key].toISOString();
}
});
}
return JSON.stringify(data);
},
],
transformResponse: [
(data) => {
// レスポンスデータの変換
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
return data;
}
}
// 日付文字列をDateオブジェクトに変換
if (data && typeof data === 'object') {
const convertDates = (obj) => {
Object.keys(obj).forEach((key) => {
if (
typeof obj[key] === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(
obj[key]
)
) {
obj[key] = new Date(obj[key]);
} else if (
typeof obj[key] === 'object' &&
obj[key] !== null
) {
convertDates(obj[key]);
}
});
};
convertDates(data);
}
return data;
},
],
});
エラーハンドリングの高度な実装
Axios を使用した高度なエラーハンドリングの実装例です。エラーの種類に応じた適切な処理を実装します。
javascript// カスタムエラークラス
class ApiError extends Error {
constructor(message, status, data) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
// エラーハンドリング関数
const handleApiError = (error) => {
if (error.response) {
// サーバーからのレスポンスがある場合
const { status, data } = error.response;
switch (status) {
case 400:
return new ApiError(
'リクエストが不正です',
status,
data
);
case 401:
return new ApiError('認証が必要です', status, data);
case 403:
return new ApiError(
'アクセスが拒否されました',
status,
data
);
case 404:
return new ApiError(
'リソースが見つかりません',
status,
data
);
case 422:
return new ApiError(
'バリデーションエラー',
status,
data
);
case 500:
return new ApiError(
'サーバーエラーが発生しました',
status,
data
);
default:
return new ApiError(
`エラーが発生しました (${status})`,
status,
data
);
}
} else if (error.request) {
// リクエストは送信されたがレスポンスがない場合
return new ApiError(
'ネットワークエラーが発生しました',
null,
null
);
} else {
// リクエストの設定でエラーが発生した場合
return new ApiError(
'リクエストの設定エラー',
null,
null
);
}
};
// エラーハンドリング付きAPI関数
const safeApiCall = async (apiFunction) => {
try {
const response = await apiFunction();
return response.data;
} catch (error) {
const apiError = handleApiError(error);
throw apiError;
}
};
// 使用例
const fetchUsers = () => {
return safeApiCall(() => apiClient.get('/users'));
};
実践的なアプリケーション例
TODO アプリの API 連携
実際の TODO アプリケーションで API 通信を実装する例です。CRUD 操作をすべて含む実践的な例となっています。
javascript// TODOアプリのAPI通信
import { createSignal, createEffect } from 'solid-js';
import axios from 'axios';
const todoApi = axios.create({
baseURL: '/api/todos',
});
// TODOの型定義
const createTodo = (title, description = '') => ({
id: null,
title,
description,
completed: false,
createdAt: new Date(),
});
// TODO管理のカスタムフック
const useTodos = () => {
const [todos, setTodos] = createSignal([]);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
// TODO一覧の取得
const fetchTodos = async () => {
setLoading(true);
setError(null);
try {
const response = await todoApi.get('/');
setTodos(response.data);
} catch (err) {
setError('TODOの取得に失敗しました');
console.error(err);
} finally {
setLoading(false);
}
};
// TODOの作成
const addTodo = async (title, description) => {
try {
const newTodo = createTodo(title, description);
const response = await todoApi.post('/', newTodo);
setTodos((prev) => [...prev, response.data]);
return response.data;
} catch (err) {
setError('TODOの作成に失敗しました');
throw err;
}
};
// TODOの更新
const updateTodo = async (id, updates) => {
try {
const response = await todoApi.put(`/${id}`, updates);
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? response.data : todo
)
);
return response.data;
} catch (err) {
setError('TODOの更新に失敗しました');
throw err;
}
};
// TODOの削除
const deleteTodo = async (id) => {
try {
await todoApi.delete(`/${id}`);
setTodos((prev) =>
prev.filter((todo) => todo.id !== id)
);
} catch (err) {
setError('TODOの削除に失敗しました');
throw err;
}
};
return {
todos,
loading,
error,
fetchTodos,
addTodo,
updateTodo,
deleteTodo,
};
};
ユーザー管理システム
ユーザー管理システムでの API 通信実装例です。認証、プロフィール管理、権限管理を含む包括的な例です。
javascript// ユーザー管理システムのAPI通信
const userApi = axios.create({
baseURL: '/api/users',
});
// 認証状態の管理
const useAuth = () => {
const [user, setUser] = createSignal(null);
const [isAuthenticated, setIsAuthenticated] =
createSignal(false);
const [loading, setLoading] = createSignal(true);
// ログイン
const login = async (email, password) => {
try {
const response = await axios.post('/api/auth/login', {
email,
password,
});
const { token, user: userData } = response.data;
localStorage.setItem('authToken', token);
setUser(userData);
setIsAuthenticated(true);
return userData;
} catch (err) {
throw new Error('ログインに失敗しました');
}
};
// ログアウト
const logout = () => {
localStorage.removeItem('authToken');
setUser(null);
setIsAuthenticated(false);
};
// ユーザー情報の取得
const fetchUser = async () => {
try {
const response = await userApi.get('/profile');
setUser(response.data);
setIsAuthenticated(true);
} catch (err) {
logout();
} finally {
setLoading(false);
}
};
// 初期化時にユーザー情報を取得
createEffect(() => {
if (localStorage.getItem('authToken')) {
fetchUser();
} else {
setLoading(false);
}
});
return {
user,
isAuthenticated,
loading,
login,
logout,
fetchUser,
};
};
リアルタイムデータ更新
WebSocket や Server-Sent Events を使用したリアルタイムデータ更新の実装例です。
javascript// リアルタイムデータ更新の実装
const useRealtimeData = (endpoint) => {
const [data, setData] = createSignal([]);
const [connected, setConnected] = createSignal(false);
const [error, setError] = createSignal(null);
let ws = null;
// WebSocket接続の確立
const connect = () => {
try {
ws = new WebSocket(`ws://localhost:3000${endpoint}`);
ws.onopen = () => {
setConnected(true);
setError(null);
console.log('WebSocket接続確立');
};
ws.onmessage = (event) => {
const newData = JSON.parse(event.data);
setData((prev) => [...prev, newData]);
};
ws.onclose = () => {
setConnected(false);
console.log('WebSocket接続終了');
};
ws.onerror = (error) => {
setError('WebSocketエラーが発生しました');
console.error('WebSocket error:', error);
};
} catch (err) {
setError('WebSocket接続に失敗しました');
}
};
// データの送信
const sendData = (message) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
};
// 接続の切断
const disconnect = () => {
if (ws) {
ws.close();
ws = null;
}
};
// コンポーネントのクリーンアップ
onCleanup(() => {
disconnect();
});
return {
data,
connected,
error,
connect,
sendData,
disconnect,
};
};
パフォーマンス最適化
キャッシュ戦略
API 通信のパフォーマンスを向上させるためのキャッシュ戦略を実装しましょう。
javascript// シンプルなキャッシュシステム
const createCache = () => {
const cache = new Map();
const get = (key) => {
const item = cache.get(key);
if (!item) return null;
// キャッシュの有効期限チェック
if (Date.now() > item.expiresAt) {
cache.delete(key);
return null;
}
return item.data;
};
const set = (key, data, ttl = 5 * 60 * 1000) => {
// デフォルト5分
cache.set(key, {
data,
expiresAt: Date.now() + ttl,
});
};
const clear = () => cache.clear();
return { get, set, clear };
};
// キャッシュ付きAPI関数
const cachedApiCall = (apiFunction, cacheKey, ttl) => {
const cache = createCache();
return async (...args) => {
const key =
typeof cacheKey === 'function'
? cacheKey(...args)
: cacheKey;
// キャッシュから取得を試行
const cached = cache.get(key);
if (cached) {
return cached;
}
// API呼び出し
const data = await apiFunction(...args);
// キャッシュに保存
cache.set(key, data, ttl);
return data;
};
};
// 使用例
const fetchUserWithCache = cachedApiCall(
(id) =>
apiClient.get(`/users/${id}`).then((res) => res.data),
(id) => `user-${id}`,
10 * 60 * 1000 // 10分
);
リクエストの最適化
不要なリクエストを防ぎ、効率的な API 通信を実現するための最適化手法です。
javascript// デバウンス機能付きAPI呼び出し
const createDebouncedApiCall = (
apiFunction,
delay = 300
) => {
let timeoutId = null;
return (...args) => {
return new Promise((resolve, reject) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(async () => {
try {
const result = await apiFunction(...args);
resolve(result);
} catch (error) {
reject(error);
}
}, delay);
});
};
};
// 検索機能での使用例
const debouncedSearch = createDebouncedApiCall(
(query) =>
apiClient
.get(`/search?q=${query}`)
.then((res) => res.data),
500
);
// 重複リクエストの防止
const createRequestManager = () => {
const pendingRequests = new Map();
const execute = async (key, apiFunction) => {
// 既に同じリクエストが進行中の場合はそれを返す
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}
// 新しいリクエストを作成
const promise = apiFunction().finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
};
return { execute };
};
// 使用例
const requestManager = createRequestManager();
const fetchUserData = (userId) => {
return requestManager.execute(`user-${userId}`, () =>
apiClient
.get(`/users/${userId}`)
.then((res) => res.data)
);
};
メモリリークの防止
SolidJS でのメモリリークを防ぐためのベストプラクティスを実装します。
javascript// メモリリークを防ぐためのカスタムフック
const useApiWithCleanup = (apiFunction) => {
const [data, setData] = createSignal(null);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
let abortController = null;
let isMounted = true;
const execute = async (...args) => {
// 前のリクエストをキャンセル
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
setLoading(true);
setError(null);
try {
const result = await apiFunction(
...args,
abortController.signal
);
// コンポーネントがまだマウントされている場合のみ状態を更新
if (isMounted) {
setData(result);
}
} catch (err) {
if (err.name !== 'AbortError' && isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
// クリーンアップ関数
const cleanup = () => {
isMounted = false;
if (abortController) {
abortController.abort();
}
};
// コンポーネントのアンマウント時にクリーンアップ
onCleanup(cleanup);
return {
data,
loading,
error,
execute,
cleanup,
};
};
// 使用例
const UserComponent = () => {
const { data, loading, error, execute } =
useApiWithCleanup((userId, signal) =>
apiClient
.get(`/users/${userId}`, { signal })
.then((res) => res.data)
);
createEffect(() => {
execute(1);
});
return (
<div>
{loading() && <p>読み込み中...</p>}
{error() && <p>エラー: {error()}</p>}
{data() && <p>ユーザー: {data().name}</p>}
</div>
);
};
まとめ
SolidJS での API 通信について、fetch API から Axios まで、実践的な実装方法を詳しく解説してきました。
この記事で学んだ重要なポイントを振り返ってみましょう:
基本の理解
- HTTP 通信の仕組みと RESTful API の原則
- 非同期処理と Promise の活用
- SolidJS のリアクティビティシステムとの連携
実装技術
- fetch API を使った基本的な API 通信
- Axios を使った高度な機能の活用
- 適切なエラーハンドリングの実装
実践的な応用
- 実際のアプリケーションでの API 通信
- パフォーマンス最適化とキャッシュ戦略
- メモリリークの防止
SolidJS の特徴である細粒度のリアクティビティを活かすことで、効率的で保守性の高い API 通信を実装できます。fetch API と Axios の使い分けを理解し、プロジェクトの要件に応じて最適な方法を選択することが重要です。
エラーハンドリングやパフォーマンス最適化を適切に実装することで、ユーザーエクスペリエンスを大幅に向上させることができます。この記事で学んだ知識を活用して、あなたの SolidJS アプリケーションを次のレベルに引き上げてください。
実装する際は、必ずエラーハンドリングを忘れずに、ユーザーにとって分かりやすいエラーメッセージを提供することを心がけてください。また、パフォーマンスを意識したキャッシュ戦略やリクエスト最適化も、大規模なアプリケーションでは不可欠な要素となります。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来