自定义 Hooks
18.1 创建可复用逻辑
javascript
// useForm - 表单状态管理
function useForm(initialValues, onSubmit) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(values);
};
const reset = () => setValues(initialValues);
return { values, errors, handleChange, handleSubmit, reset };
}
// 使用
const LoginForm = () => {
const { values, handleChange, handleSubmit } = useForm(
{ email: "", password: "" },
(data) => console.log(data),
);
return (
<form onSubmit={handleSubmit}>
<input name="email" value={values.email} onChange={handleChange} />
<input name="password" value={values.password} onChange={handleChange} />
<button type="submit">Login</button>
</form>
);
};18.2 useFetch 数据获取钩子
javascript
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Fetch failed");
const json = await response.json();
if (isMounted) setData(json);
} catch (err) {
if (isMounted) setError(err);
} finally {
if (isMounted) setLoading(false);
}
};
fetchData();
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
// 使用
function UserList() {
const { data: users, loading } = useFetch("/api/users");
if (loading) return <div>Loading...</div>;
return (
<div>
{users?.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</div>
);
}18.3 useLocalStorage 本地存储钩子
javascript
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用
const App = () => {
const [theme, setTheme] = useLocalStorage("theme", "light");
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme}
</button>
);
};状态管理(useReducer)
19.1 基础使用
javascript
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return initialState;
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}19.2 复杂状态管理
javascript
const initialState = {
todos: [],
filter: "ALL",
loading: false,
error: null,
};
function todoReducer(state, action) {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [
...state.todos,
{ id: Date.now(), text: action.payload, completed: false },
],
};
case "TOGGLE_TODO":
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo,
),
};
case "DELETE_TODO":
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload),
};
case "SET_FILTER":
return { ...state, filter: action.payload };
case "SET_LOADING":
return { ...state, loading: action.payload };
case "SET_ERROR":
return { ...state, error: action.payload };
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const handleAddTodo = (text) => {
dispatch({ type: "ADD_TODO", payload: text });
};
const filteredTodos = state.todos.filter((todo) => {
if (state.filter === "ACTIVE") return !todo.completed;
if (state.filter === "COMPLETED") return todo.completed;
return true;
});
return (
<div>
<input
onKeyPress={(e) => {
if (e.key === "Enter") {
handleAddTodo(e.target.value);
e.target.value = "";
}
}}
placeholder="Add a todo..."
/>
<ul>
{filteredTodos.map((todo) => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
>
{todo.text}
<button
onClick={() =>
dispatch({ type: "TOGGLE_TODO", payload: todo.id })
}
>
Toggle
</button>
<button
onClick={() =>
dispatch({ type: "DELETE_TODO", payload: todo.id })
}
>
Delete
</button>
</li>
))}
</ul>
</div>
);
}