资料参考来源:小满zs
前言
这个例子,我将使用一个例子,讲解useEffect和useReducer。内容包含useEffect实现防抖,通过useReducer管理复杂的数据。
基础知识
useEffect
语法
tsx
useEffect(setup, dependencies?)
// dependencies
dependencies:依赖项数组,控制副作用函数的执行时机。
| dependencies依赖项 | 副作用功函数的执行时机 |
|---|---|
| 无 | 初始渲染一次 + 组件更新时执行 |
[] |
初始渲染一次 |
| 指定依赖项 | 初始渲染一次 + 依赖项变化时执行 |
案例
useEffect是副作用函数,在组件渲染完之后执行。
- 获取DOM
tsx
const abc = document.querySelector('abc');
console.log(abc) // null
useEffect(()=>{
console.log(abc) // 可以获取
})
消除副作用
return一个函数。在副作用函数运行之前,会清除上一次的副作用函数。
据此,我们可以依照他写防抖(防抖相当于在王者荣耀里回城,只会执行最后一次)函数和节流(节流相当于在王者荣耀使用技能,使用完需要等待技能cd结束才能再次使用)函数。
- 防抖
tsx
// 适合输入框下方的推荐
useEffect(() => {
if(!props.id) return; // 这样只有在有输入的情况下进行fetch
let timer = setTimeout(() => {
fetch(`http://localhost:5174/?id=${props.id}`)
}, 500)
return () => {
clearTimeout(timer)
}
},[props.id])
- 节流
tsx
// 适合搜索
const timer = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (!props.id) return;
if (!timer.current) {
timer.current = setTimeout(() => {
fetch(`http://localhost:5174/?id=${props.id}`);
timer.current = null; // 允许下一次触发
}, 500);
}
return () => {
if (timer.current) clearTimeout(timer.current);
}
}, [props.id]);
useReducer
语法
tsx
const [state, dispatch] = useReducer(reducer, initialArg, init?)
| 参数 | 作用 |
|---|---|
state |
当前状态 |
dispatch |
修改状态的方法,唯一方式,需要传入action(即进行那一项操作) |
reducer |
定义dispatch如何修改状态,传入state和action |
initialArg |
初始状态 |
init? |
可选初始化函数 |
购物车案例
tsx
import { useReducer } from "react";
// 初始值
const initData = [
{ name: "苹果", price: 100, count: 1, id: 1, isEdit: false },
{ name: "香蕉", price: 200, count: 1, id: 2, isEdit: false },
{ name: "梨", price: 300, count: 1, id: 3, isEdit: false },
];
// 定义 state 的数据类型
type State = typeof initData;
// reducer 定义 dispatch 如何修改状态
// state 当前状态
// action
// - type 通过dispatch获得具体指令,定位具体进行什么操作
// - 其他 从外界获得的动态的参数
const reducer = (
state: State,
action: {
type: "ADD" | "SUB" | "DELETE" | "EDIT" | "UPDATE_NAME";
id: number;
newName?: string;
},
) => {
// 通过传入 id 找到具体修改的一项
const item = state.find((i) => i.id === action.id);
if (!item) return state;
switch (action.type) {
case "ADD":
return state.map((i) =>
i.id === action.id ? { ...i, count: item.count + 1 } : i,
);
// 不太规范的写法如下:
// item.count++;
// return [...state];
case "SUB":
item.count--;
if (item.count === 0) {
return [...state.filter((i) => i.id !== action.id)];
}
return [...state];
case "DELETE":
return [...state.filter((i) => i.id !== action.id)];
case "EDIT":
item.isEdit = !item.isEdit;
return [...state];
case "UPDATE_NAME":
if (action.newName !== undefined) item.name = action.newName.trim();
return [...state];
default:
return [...state];
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initData);
return (
<div className="abc">
<div className="">
<table>
<thead>
<tr>
<th>名称</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{state.map((i) => (
<tr key={i.id}>
<td>
{i.isEdit ? (
<input
type="text"
value={i.name}
onChange={(e) => {
dispatch({
type: "UPDATE_NAME",
id: i.id,
newName: e.target.value,
});
}}
/>
) : (
<p>{i.name}</p>
)}
</td>
<td>{i.price}</td>
<td className="flex">
<button
className=""
onClick={() => dispatch({ type: "SUB", id: i.id })}
>
-
</button>
<div className="">{i.count}</div>
<button
className=""
onClick={() => dispatch({ type: "ADD", id: i.id })}
>
+
</button>
</td>
<td>{i.price * i.count}</td>
<td>
<button onClick={() => dispatch({ type: "EDIT", id: i.id })}>
修改
</button>
<button
onClick={() => dispatch({ type: "DELETE", id: i.id })}
>
删除
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default App;
案例
- 通过
fetch(https://jsonplaceholder.typicode.com/users/${state.userId}),获得一份userData的数据。 - 案例内容为使用
useReducer和useEffect,通过输入框输入userId,调用api获得UserData的数据。
tsx
import { useEffect, useReducer } from "react";
interface UserData {
name: string;
email: string;
username: string;
phone: string;
website: string;
}
type State = UserData & {
userId?: string;
};
type Action =
| { type: "INPUT"; userId: string }
| { type: "FETCH_DATA"; payload: UserData };
const initState: State = {
userId: "",
name: "",
email: "",
username: "",
phone: "",
website: "",
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "INPUT":
return {
...state,
userId: action.userId,
};
case "FETCH_DATA":
return {
...state,
...action.payload,
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initState);
useEffect(() => {
if (!state.userId) return;
let timer = setTimeout(() => {
const fetchData = async () => {
try {
const resp = await fetch(
`https://jsonplaceholder.typicode.com/users/${state.userId}`,
);
if (!resp.ok) {
throw new Error("网络响应不正常");
}
const data = await resp.json();
dispatch({
type: "FETCH_DATA",
payload: {
name: data.name,
email: data.email,
username: data.username,
phone: data.phone,
website: data.website,
},
});
} catch (error) {
console.log(error);
}
};
fetchData();
}, 500);
return () => clearTimeout(timer);
}, [state.userId]);
return (
<div style={{ padding: 40 }}>
<h2>输入 User ID</h2>
<input
type="text"
value={state.userId}
onChange={(e) => dispatch({ type: "INPUT", userId: e.target.value })}
/>
<h3>User Info</h3>
<div>name: {state.name}</div>
<div>username: {state.username}</div>
<div>email: {state.email}</div>
<div>phone: {state.phone}</div>
<div>website: {state.website}</div>
</div>
);
};
export default App;