使用 useState
是管理状态的主要方法,如果使用 useState
解决问题变得复杂麻烦易出错,繁琐,或者要管理对象,此时,优先考虑使用 useReducer
:
- 一些 state 相关联
- 一些 state 的更新依赖于其他 state 的更新,因为此时没办法使用箭头函数安全更新,箭头函数获得的 state 快照是自己的,不是自己依赖的 state 的快照。
可以获得与 Redux 类似功能的两个钩子函数: useReducer
和 useContext
.
如果组件的状态简单,用 useState
, 反之,可以转而用 useReducer
,从而将逻辑从组件中移到一个单独的函数中去,使我们的组件更易于管理。
useReducer
直白地说,现在有一组数据,要对这组数据进行各种操作,例如读写删除等等,就可以将这些操作的逻辑统一集中写到一个 reducer 函数中去,对应各种操作, 返回对应的数据,而不是将这些读写删除等等操作的逻辑分散到组件的各处。组件只需要简单地调用一下dispatch
。useReducer
与 Redux 极其相似。
下面的例子,对一个由多则 note
组成的 notes
数组进行各种操作。例如,可以读取浏览器存储的notes
,可以添加一则 note
,也可以删除一则 note
,于是就写一个可以操作 notes
数组的 notesReducer
函数,收集对这个notes
的各种操作:
import React, { useState, useEffect, useReducer } from "react";
import ReactDOM from "react-dom";
import reportWebVitals from "./reportWebVitals";
// 自己写一个 reducer 函数
const notesReducer = (state, action) => {
switch (action.type) {
case "POPULATE_NOTES":
return action.notes;
case "ADD_NOTE":
return [...state, action.note];
case "REMOVE_NOTE":
return state.filter((note) => note.title !== action.title);
default:
return state;
}
};
const NoteApp = () => {
// 使用上面定义的 reducer 函数 notesReducer !
const [notes, dispatch] = useReducer(notesReducer, []);
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
// 使用 useEffect,实现componentDidMount 效果
useEffect(() => {
const notes = JSON.parse(localStorage.getItem("notes"));
if (notes) {
// 读取全部 note
dispatch({ type: "POPULATE_NOTES", notes });
}
}, []);
// 使用 useEffect,实现 componentDidUpdate 效果
useEffect(() => {
localStorage.setItem("notes", JSON.stringify(notes));
}, [notes]);
const addNote = (e) => {
e.preventDefault();
// 添加一则 note
dispatch({ type: "ADD_NOTE", note: { title, body } });
setTitle("");
setBody("");
};
const removeNote = (title) => {
// 删除一则 note
dispatch({ type: "REMOVE_NOTE", title });
};
return (
<div>
<h1>Notes</h1>
{notes.map((note) => (
<Note key={note.title} note={note} removeNote={removeNote} />
))}
<p>================ Add note =============</p>
<form onSubmit={addNote}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<textarea value={body} onChange={(e) => setBody(e.target.value)} />
<button>add note</button>
</form>
</div>
);
};
const Note = ({ note, removeNote }) => {
useEffect(() => {
console.log("Setting up effect!");
return () => {
console.log("A note is unmounted!");
};
}, []);
return (
<div>
<h3>{note.title}</h3>
<p>{note.body} </p>
<button onClick={() => removeNote(note.title)}>X</button>
</div>
);
};
ReactDOM.render(
<React.StrictMode>
<NoteApp />
</React.StrictMode>,
document.getElementById("root")
);
// 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();
另一个例子,用户登录:
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
const emailReducer = (state, action) => {
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.includes("@") };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.includes("@") };
}
return { value: "", isValid: false };
};
const passwordReducer = (state, action) => {
switch (action.type) {
case "USER_INPUT":
return { value: action.value, isValid: action.value.trim().length > 6 };
case "INPUT_BLRU":
return { value: state.value, isValid: state.value.trim().length > 6 };
default:
return state;
}
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
// const [enteredPassword, setEnteredPassword] = useState("");
// const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isValid: undefined,
});
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
value: "",
isValid: undefined,
});
// 对象解构,并重新将属性命名成别名,不用对象解构语法
// 也是完全可以的
// 目的在于优化 useEffect,减少依赖,从而减少执行次数
const {isValid: emailIsValid} = emailState;
const {isValid: passwordIsValid} = passwordState;
useEffect(() => {
const identifier = setTimeout(() => {
console.log("Checking form validity!------------");
setFormIsValid(emailIsValid && passwordIsValid);
}, 1000);
return () => {
clearTimeout(identifier);
};
}, [emailIsValid, passwordIsValid]);
const emailChangeHandler = (event) => {
// setEnteredEmail(event.target.value);
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
// setFormIsValid(event.target.value.includes("@") && passwordState.isValid);
};
const passwordChangeHandler = (event) => {
// setEnteredPassword(event.target.value);
dispatchPassword({ type: "USER_INPUT", value: event.target.value });
// setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
};
const validateEmailHandler = () => {
// setEmailIsValid(emailState.isValid);
dispatchEmail({ type: "INPUT_BLUR" });
};
const validatePasswordHandler = () => {
// setPasswordIsValid(enteredPassword.trim().length > 6);
dispatchPassword({ type: "INPUT_BLUR" });
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, passwordState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;