React:一个例子讲清楚 useEffect 和 useReducer

0 阅读2分钟

资料参考来源:小满zs

前言

这个例子,我将使用一个例子,讲解useEffectuseReducer。内容包含useEffect实现防抖,通过useReducer管理复杂的数据。

基础知识

useEffect

语法

useEffect(setup, dependencies?)
// dependencies 
  • dependencies:依赖项数组,控制副作用函数的执行时机。
dependencies依赖项副作用功函数的执行时机
初始渲染一次 + 组件更新时执行
[]初始渲染一次
指定依赖项初始渲染一次 + 依赖项变化时执行

案例

useEffect是副作用函数,在组件渲染完之后执行。

  • 获取DOM
const abc = document.querySelector('abc');
console.log(abc) // null
useEffect(()=>{
  console.log(abc) // 可以获取
})

消除副作用

return一个函数。在副作用函数运行之前,会清除上一次的副作用函数。

据此,我们可以依照他写防抖(防抖相当于在王者荣耀里回城,只会执行最后一次)函数和节流(节流相当于在王者荣耀使用技能,使用完需要等待技能cd结束才能再次使用)函数。

  • 防抖
// 适合输入框下方的推荐
useEffect(() => {
  if(!props.id)  return;  // 这样只有在有输入的情况下进行fetch
  let timer = setTimeout(() => {
    fetch(`http://localhost:5174/?id=${props.id}`)
  }, 500)
  return () => {
	clearTimeout(timer)
  }
},[props.id])
  • 节流
// 适合搜索
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

语法

const [state, dispatch] = useReducer(reducer, initialArg, init?)
参数作用
state当前状态
dispatch修改状态的方法,唯一方式,需要传入action(即进行那一项操作)
reducer定义dispatch如何修改状态,传入stateaction
initialArg初始状态
init?可选初始化函数

购物车案例

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的数据。
  • 案例内容为使用useReduceruseEffect,通过输入框输入userId,调用api获得UserData的数据。
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;