十八、useFetch自定义钩子

郁子大约 2 分钟约 728 字笔记React18李立超

(一) src/App.jsx

import React, { useEffect } from "react";
import StudentList from "./components/StudentList";
import StudentContext from "./store/StudentContext";
import "./App.css";
import useFetch from "./hooks/useFetch";

const App = () => {
  const {
    data: stuData,
    loading,
    error,
    fetchData,
  } = useFetch({
    url: "students",
  });

  // useEffect()参数不能是异步函数
  useEffect(() => {
    fetchData();
  }, [fetchData]);

  const loadData = () => {
    fetchData();
  };

  return (
    <StudentContext.Provider value={{ fetchData }}>
      <div className="app">
        <button onClick={loadData}>加载数据</button>
        {!loading && !error && <StudentList students={stuData} />}
        {loading && <p>数据正在加载中...</p>}
        {error && <p>数据加载异常!</p>}
      </div>
    </StudentContext.Provider>
  );
};

export default App;

(二) src/components/Student/index.jsx

import React, { useContext, useState } from "react";
import useFetch from "../../hooks/useFetch";
import StudentContext from "../../store/StudentContext";
import StudentForm from "../StudentForm";

const Student = ({
  stu: {
    id,
    attributes: { name, age, gender, address },
  },
  stu,
}) => {
  /*
    props = {
      stu: {
        id: xxx,
        attributes: {
          name,age,gender,address
        }
      }
    }
  */
  const [editing, setEditing] = useState(false);
  const ctx = useContext(StudentContext);

  const {
    loading,
    error,
    fetchData: delStu,
  } = useFetch(
    {
      url: `students/${id}`,
      method: "delete",
    },
    ctx.fetchData,
  );

  const deleteStudent = () => {
    delStu();
  };

  const cancelEdit = () => {
    setEditing(false);
  };

  return (
    <>
      {!editing && (
        <tr>
          <td>{name}</td>
          <td>{gender}</td>
          <td>{age}</td>
          <td>{address}</td>
          <td>
            <button onClick={deleteStudent}>删除</button>
            <button onClick={() => setEditing(true)}>修改</button>
          </td>
        </tr>
      )}

      {editing && <StudentForm stu={stu} onCancelEdit={cancelEdit} />}

      {loading && (
        <tr>
          <td colSpan={5}>正在删除数据...</td>
        </tr>
      )}
      {error && (
        <tr>
          <td colSpan={5}>删除失败!</td>
        </tr>
      )}
    </>
  );
};

export default Student;

(三) src/components/StudentForm/index.jsx

import React, { useCallback, useContext, useState } from "react";
import useFetch from "../../hooks/useFetch";
import StudentContext from "../../store/StudentContext";
import "./index.css";

const StudentForm = (props) => {
  const ctx = useContext(StudentContext);
  const {
    loading,
    error,
    fetchData: updateStudent,
  } = useFetch(
    {
      url: props.stu ? `students/${props.stu.id}` : "students",
      method: props.stu ? "put" : "post",
    },
    ctx.fetchData,
  );

  const [inputData, setInputData] = useState({
    name: props.stu ? props.stu.attributes.name : "",
    gender: props.stu ? props.stu.attributes.gender : "男",
    age: props.stu ? props.stu.attributes.age : 0,
    address: props.stu ? props.stu.attributes.address : "",
  });
  const { name, gender, age, address } = inputData;

  const nameChange = (e) => {
    setInputData((pre) => ({ ...pre, name: e.target.value }));
  };
  const genderChange = (e) => {
    setInputData((pre) => ({ ...pre, gender: e.target.value }));
  };
  const ageChange = (e) => {
    setInputData((pre) => ({ ...pre, age: +e.target.value }));
  };
  const addressChange = (e) => {
    setInputData((pre) => ({ ...pre, address: e.target.value }));
  };

  const onSubmitAdd = () => {
    updateStudent(inputData);
  };
  const onSubmitEdit = () => {
    updateStudent(inputData);
  };

  return (
    <>
      <tr className="student-form">
        <td>
          <input type="text" value={name} onChange={nameChange} />
        </td>
        <td>
          <select value={gender} onChange={genderChange}>
            <option value="男"></option>
            <option value="女"></option>
          </select>
        </td>
        <td>
          <input type="text" value={age} onChange={ageChange} />
        </td>
        <td>
          <input type="text" value={address} onChange={addressChange} />
        </td>
        <td>
          {props.stu && (
            <>
              <button onClick={() => props.onCancelEdit()}>取消</button>
              <button onClick={onSubmitEdit}>确认</button>
            </>
          )}
          {!props.stu && <button onClick={onSubmitAdd}>添加</button>}
        </td>
      </tr>
      {loading && (
        <tr>
          <td colSpan={5}>添加中...</td>
        </tr>
      )}
      {error && (
        <tr>
          <td colSpan={5}>添加失败!</td>
        </tr>
      )}
    </>
  );
};

export default StudentForm;

(四) src/hooks/useFetch.js

  • React 中的钩子函数只能在 函数组件自定义钩子 中调用
  • 当需要将 React 中的钩子函数提取到一个公共区域时,可以使用自定义钩子
  • 自定义钩子其实就是一个普通函数,只是名字需要使用 use 开头
import { useCallback, useState } from "react";

/*
  reqObj:用来存储请求的参数
    url:请求地址
    method:请求方法
    body:请求体【body在请求时传,获取不到最新的表单数据,应该作为fetchData参数传递】
  cb:回调函数,请求发送成功时执行
*/
export default function useFetch(reqObj, cb) {
  // 数据
  const [data, setData] = useState([]);
  // 记录数据是否正在加载
  const [loading, setLoading] = useState(false);
  // 记录错误信息
  const [error, setError] = useState(null);

  const fetchData = useCallback(async (data) => {
    try {
      setLoading(true);
      setError(null);
      const res = await fetch(`http://localhost:1337/api/${reqObj.url}`, {
        method: reqObj.method || "get",
        headers: {
          "Content-type": "application/json",
        },
        body: data
          ? JSON.stringify({
              data,
            })
          : null,
      });
      if (res.ok) {
        const data = await res.json();
        setData(data.data);
        cb && cb();
      } else {
        throw new Error("数据加载失败!");
      }
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  }, []);

  // 设置返回值
  return {
    data,
    loading,
    error,
    fetchData,
  };
}
上次编辑于: