
変数とデータ型(let, const, プリミティブ型)
はじめに JavaScriptを学ぶ上で、変数とデータ型の理解は基礎中の基礎です。この知識なしでは効果的なプログラミングはできません。本記事では、現代のJava […]
現代のフロントエンド開発において、状態管理は最も重要な概念の1つです。この章では、JavaScriptアプリケーションにおける状態管理の基本概念から、具体的な実装方法、主要なライブラリまで、初心者の方にもわかりやすく詳細に解説します。状態管理を適切に行うことで、アプリケーションの保守性や拡張性が大幅に向上します。
状態管理(State Management)とは、アプリケーションが持つデータ(状態)をどのように保持し、更新し、コンポーネント間で共有するかを体系化する方法論です。
小さなアプリケーションでは、コンポーネントのローカル状態(useStateなど)で十分かもしれません。しかし、アプリケーションが成長するにつれて以下の問題が発生します:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // ローカルステート
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function ParentComponent() {
const [sharedState, setSharedState] = useState('');
return (
<>
<ChildA state={sharedState} setState={setSharedState} />
<ChildB state={sharedState} setState={setSharedState} />
</>
);
}
import { createContext, useContext, useState } from 'react';
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</AppContext.Provider>
);
}
function useAppContext() {
return useContext(AppContext);
}
// 使用例
function UserProfile() {
const { user } = useAppContext();
// ...
}
import { createStore } from 'redux';
// アクションタイプ
const ADD_TODO = 'ADD_TODO';
// アクションクリエーター
function addTodo(text) {
return { type: ADD_TODO, text };
}
// 初期状態
const initialState = {
todos: []
};
// リデューサー
function todoApp(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, { text: action.text, completed: false }]
};
default:
return state;
}
}
// ストア作成
const store = createStore(todoApp);
// 使用例
store.dispatch(addTodo('Reduxを学ぶ'));
console.log(store.getState());
import create from 'zustand';
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
// 使用例
function BearCounter() {
const bears = useStore((state) => state.bears);
const increase = useStore((state) => state.increasePopulation);
return ;
}
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const textState = atom({
key: 'textState', // 一意のID
default: '', // 初期値
});
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
function TextInput() {
const [text, setText] = useRecoilState(textState);
const count = useRecoilValue(charCountState);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>文字数: {count}</p>
</div>
);
}
プロジェクトに適した状態管理ソリューションを選ぶ際の考慮点:
状態管理の重要なポイント:
状態管理をマスターすることで、複雑なアプリケーションも自信を持って構築できるようになります。
以下のコードには状態管理上の問題点があります。それを指摘し、改善案を提案してください。
function UserProfile() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
fetchUser().then(data => {
setUser(data);
setIsLoading(false);
});
}, []);
return (
<div>
{isLoading ? (
<p>Loading...</p>
) : (
<div>
<h1>{user.name}</h1>
<UserDetails user={user} />
<UserPreferences user={user} />
</div>
)}
</div>
);
}
function UserDetails({ user }) {
const [details, setDetails] = useState(null);
useEffect(() => {
fetchUserDetails(user.id).then(setDetails);
}, [user.id]);
return <div>{/* ユーザー詳細表示 */}</div>;
}
次の状態管理に関する記述について、正しいものには○、間違っているものには×をつけてください。
以下の要件を満たすシンプルなカウンターアプリを、Zustandを使用して実装してください。
問題点:
UserProfile
とUserDetails
)改善案:
// 改善例(Context API使用)
const UserContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
Promise.all([
fetchUser(),
fetchUserDetails()
]).then(([userData, details]) => {
setUser({ ...userData, details });
setIsLoading(false);
});
}, []);
return (
<UserContext.Provider value={{ user, isLoading }}>
{children}
</UserContext.Provider>
);
}
function useUser() {
return useContext(UserContext);
}
function UserProfile() {
const { user, isLoading } = useUser();
if (isLoading) return <p>Loading...</p>;
return (
<div>
<h1>{user.name}</h1>
<UserDetails details={user.details} />
<UserPreferences user={user} />
</div>
);
}
import create from 'zustand';
const useCounterStore = create((set, get) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
getDoubleCount: () => get().count * 2
}));
function Counter() {
const { count, increment, decrement, reset, getDoubleCount } = useCounterStore();
return (
<div>
<h1>Count: {count}</h1>
<p>Double: {getDoubleCount()}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}