1. React Hooks #

React Hooks 是从 React 16.8 版本开始引入的新特性。Hooks 允许你在函数组件中使用 state 和其他 React 特性。这提供了一个更好的函数式编程的途径,让我们能够在不编写 class 组件的情况下使用 state 和生命周期等特性。

2. useState #

在 React 中,useState 是一种让你在函数组件中添加内部状态的钩子(Hook)。在以前,只有类组件能够拥有内部状态,但现在,useState 使得函数组件也可以拥有内部状态。

useState 的基本使用方法如下:

import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
const root = createRoot(document.getElementById('root'));
root.render(<Counter/>);

在这个例子中,我们通过 useState(0) 来创建一个新的状态变量 count,并且将它的初始值设置为 0。useState 返回了一个数组,第一个元素是当前的状态值(在这里是 count),第二个元素是一个函数,可以用来更新这个状态值(在这里是 setCount)。

每次你调用 setCount,React 都会重新渲染你的组件,并使用你传给 setCount 的最新值更新 count

需要注意的是,setCount 不像 this.setState 那样合并状态对象,而是替换它。例如,如果你的状态是一个对象,你需要手动合并旧状态:

const [state, setState] = useState({ a: 1, b: 1 });
setState(prevState => ({ ...prevState, a: 2 })); // 更新 a,保留 b

useState 还支持惰性初始化。这意味着你可以将一个函数传给 useState,这个函数会在初次渲染时被调用以生成初始状态:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

在这个例子中,someExpensiveComputation 只会在组件初次渲染时被调用,而不会在后续的渲染中被调用。这对于那些需要大量计算才能得到的初始状态来说是很有用的。

3.useCallback #

在 React 中,useCallback 是一个用于优化性能的 Hook,它可以避免在每次渲染时都创建新的函数,从而避免不必要的重新渲染。

useCallback 的基本使用方法如下:

import React, { useState, useCallback } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
    const [count, setCount] = useState(0);
    const increment = useCallback(() => {
      setCount(count + 1);
    }, [count]);
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={increment}>Click me</button>
      </div>
    );
  }
const root = createRoot(document.getElementById('root'));
root.render(<Counter/>);

在这个例子中,我们使用 useCallback 来包装我们的 increment 函数。useCallback 的第一个参数是我们需要记忆的函数,第二个参数是一个数组,里面包含了当这些值发生变化时我们需要重新创建函数的依赖。

在这个例子中,我们的 increment 函数依赖于 count 的值。每次 count 的值发生改变时,我们都会创建一个新的 increment 函数。如果 count 的值没有改变,我们就会复用上一次渲染时的 increment 函数。

4.useMemo #

在 React 中,useMemo 是一个用于优化性能的 Hook。当你需要计算一些开销大的值,并且你希望在依赖未改变时复用这个值而不是在每次渲染时都重新计算,这时你可以使用 useMemo

useCallback 的主要用途是配合 React.memo 或者 useMemo 一起使用,当你需要传递一个函数给子组件,并且你希望避免因为函数身份改变而导致的不必要渲染时,你可以使用 useCallback。但是需要注意的是,创建和记忆函数也是有开销的,所以你不应该过度使用 useCallback,只有在你能确定它能带来性能提升时才使用它。

// 导入 React 和 ReactDOM
import React from 'react';
import ReactDOM from 'react-dom/client';
// 定义一个子组件,接收 data 和 handleClick 作为 props
let  Child = ({data,handleClick})=>{
  // 在控制台打印出 'Child render'
  console.log('Child render');
  // 渲染一个按钮,点击时触发 handleClick,按钮上的文本由 data.number 决定
  return (
     <button onClick={handleClick}>{data.number}</button>
  )
}
// 使用 React.memo 进行优化,只有当 props 发生变化时,Child 组件才会重新渲染
Child = React.memo(Child);
// 定义一个 App 组件
function App(){
  // 在控制台打印出 'App render'
  console.log('App render');
  // 使用 useState 初始化两个状态变量 name 和 number
  const[name,setName]=React.useState('zhufeng');
  const[number,setNumber]=React.useState(0);
  // 使用 useMemo 记忆 data,只有当 number 发生变化时,data 才会被重新计算
  let data = React.useMemo(()=>({number}),[number]);
  // 使用 useCallback 记忆 handleClick,只有当 number 发生变化时,handleClick 才会被重新创建
  let handleClick = React.useCallback(()=> setNumber(number+1),[number]);
  // 渲染一个输入框和 Child 组件
  // 当输入框的值发生变化时,更新 name 的值
  // 将 data 和 handleClick 传递给 Child 组件
  return (
    <div>
      <input type="text" value={name} onChange={event=>setName(event.target.value)}/>
      <Child data={data} handleClick={handleClick}/>
    </div>
  )
}
// 创建 App 组件的元素
const element = <App/>;
// 获取页面上 id 为 root 的 DOM 元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染 App 组件
root.render(element);

4. useReducer #

useReducer 是 React 中的一个 Hook,提供了一种更加灵活的方式来处理函数组件的状态。这个 Hook 尤其适用于那些状态逻辑较复杂且包含多个子值,或者下一个状态依赖于之前状态的情况。

useReducer 接收两个参数,一个是 reducer 函数,另一个是初始状态,它返回一个包含两个元素的数组,第一个元素是当前的状态值,第二个元素是一个可以接受 action 并触发状态更新的 dispatch 函数。

这是一个基本的 useReducer 的使用示例:

// 导入 React,useReducer Hook 和 ReactDOM
import React, { useReducer } from 'react';
import ReactDOM from 'react-dom/client';
// 初始化 state 对象
const initialState = { count: 0 };
// 定义 reducer 函数,根据当前的 state 和 action 计算出新的 state
function reducer(state, action) {
    switch (action.type) {
        // 如果 action 类型是 'increment',则将 count 值加一
        case 'increment':
            return { count: state.count + 1 };
        // 如果 action 类型是 'decrement',则将 count 值减一
        case 'decrement':
            return { count: state.count - 1 };
        // 如果 action 类型不是 'increment' 或 'decrement',则抛出错误
        default:
            throw new Error();
    }
}
// 定义 Counter 组件
function Counter() {
    // 使用 useReducer Hook 管理 state
    const [state, dispatch] = useReducer(reducer, initialState);
    // 返回 JSX
    return (
        <>
            {/* 渲染当前 count 值 */}
            Count: {state.count}
            {/* 渲染一个按钮,点击时 dispatch 一个 'decrement' action */}
            <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
            {/* 渲染一个按钮,点击时 dispatch 一个 'increment' action */}
            <button onClick={() => dispatch({ type: 'increment' })}>+</button>
        </>
    );
}
// 创建 Counter 组件的元素
const element = <Counter />;
// 获取页面上 id 为 root 的 DOM 元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染 Counter 组件
root.render(element);

在这个例子中,reducer 函数接收当前的 state 和一个 action 对象,然后返回新的 state。在用户点击按钮时,我们调用 dispatch 并传入一个具有 type 属性的 action 对象,这个 type 属性会告诉 reducer 应该进行何种操作。

如果你熟悉 Redux,那么你应该对这个模式感到很熟悉,因为 useReducer 基本上就是在组件级别使用 Redux 的模式。

useReducer 非常适合那些逻辑复杂且包含多个子值的复杂状态逻辑。在某些情况下,使用 useReducer 会比 useState 更便捷,因为 reducer 函数可以根据旧的 state 和 action 来计算出新的 state。

5. useContext #

useContext 是 React 的一个 Hook,用于让函数组件能够访问 React 的 Context API。

在很多情况下,我们需要在组件树的不同层级间共享一些数据,例如主题(theme)或者当前的语言等。这时,我们就可以使用 React 的 Context API,它允许我们在组件树中“跳过”中间的组件,直接向更深层级的组件提供数据。

在 Class 组件中,我们可以使用 contextType 属性或者 Context.Consumer 组件来访问 Context。在函数组件中,我们可以使用 useContext Hook,它会返回当前 Context 的值。

以下是一个 useContext 的使用示例:

// 导入 React,useReducer Hook 和 ReactDOM
import React, { useContext } from 'react';
import ReactDOM from 'react-dom/client';
// 创建一个 Context 对象
const ThemeContext = React.createContext('light');
function App() {
  // 使用 Context.Provider 组件在组件树中提供一个 Context 值
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}
function Toolbar() {
  // Toolbar 组件中不需要知道 ThemeContext 的值,我们可以直接渲染它的子组件
  return <ThemeButton />;
}
function ThemeButton() {
  // 在需要访问 Context 值的组件中,我们可以使用 useContext Hook
  const theme = useContext(ThemeContext);
  return <button theme={theme}>{theme}主题</button>;
}
// 创建 Counter 组件的元素
const element = <App />;
// 获取页面上 id 为 root 的 DOM 元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染 Counter 组件
root.render(element);

在这个例子中,ThemeButton 组件可以直接访问我们通过 ThemeContext.Provider 提供的 Context 值,而不需要通过 props 从父组件中传递。useContext Hook 接收一个 Context 对象(React.createContext 的返回值)并返回该 Context 的当前值。

6. useRef #

useRef 是 React 的一种 Hook,用于在你的组件中保存一个可变的值,这个值在组件的所有生命周期和渲染过程中都保持不变。这是一种让函数式组件能够访问和修改组件外部的变量,但不触发重新渲染的方式。

下面是一个简单的示例:

import React, {useRef  } from 'react';
import ReactDOM from 'react-dom/client';
function TextInputWithFocusButton() {
    // 创建一个名为 inputEl 的 ref
    const inputEl = useRef(null);
    const onButtonClick = () => {
      // 'current' 指向了真实的输入框 DOM 节点,调用该节点的 focus 方法
      inputEl.current.focus();
    };
    return (
      <>
        {/* 将 ref 挂载到 input 元素上 */}
        <input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>Focus the input</button>
      </>
    );
  }
const element = <TextInputWithFocusButton />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

在上面的例子中,useRef 创建了一个可以在整个组件的生命周期中持续存在的对象,这个对象的 .current 属性可以被更改,但并不会触发组件的重新渲染。在这个例子中,我们通过给输入框(input 元素)绑定 ref 来获取它的 DOM 节点,并在按钮点击时,调用输入框的 focus 方法。

总的来说,useRef 是一种让你在函数式组件中保存和修改可以跨越渲染周期的数据的工具,但并不会导致组件的重新渲染。

7. useEffect #

useEffect是React的一个Hooks,它使你能够在函数组件中执行副作用操作。副作用是那些不只是返回值的操作,包括例如数据获取、订阅、手动更改React组件的DOM,或者包含其他命令式的API调用。

useEffect的基本用法如下:

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
function App() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        document.title = `You clicked ${count} times`;
    });
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    );
}
const element = <App />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

在这个例子中,useEffect Hook告诉React组件需要在渲染后做一些额外的操作。React将记住传递给useEffect的函数(我们称之为“副作用函数”),并且将在执行DOM更新之后调用它。副作用函数可能会返回一个清理函数,用于取消副作用的设置,例如取消订阅或清除定时器。

另外,useEffect可以接受第二个参数,这个参数是一个数组。这个数组中可以指定一些变量,只有当这些变量的值发生变化时,才会重新执行副作用函数。例如:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在count变化时更新

在这个例子中,副作用函数只会在count变量发生变化时才会执行。这样就可以避免不必要的副作用函数的执行,提高了程序的性能。

如果你不提供这个参数,那么副作用函数将在每一次渲染后都执行。如果你提供一个空数组([])作为这个参数,那么副作用函数将只在组件首次渲染后执行一次,以及在组件卸载时执行清理函数。

8. useLayoutEffect #

useLayoutEffect是React的一个Hooks,它的作用与useEffect非常相似,都是用于执行副作用操作。但两者在何时执行副作用上有明显的不同。

useEffect是在渲染后,浏览器已经绘制了更新的屏幕后延迟异步执行,而useLayoutEffect则会在浏览器绘制之前同步执行。因此,如果你的副作用操作需要在用户看到新的屏幕绘制之前完成,那么你应该使用useLayoutEffect

以下是一个基本的useLayoutEffect使用例子:

import React, { useEffect,useLayoutEffect } from 'react';
import ReactDOM from 'react-dom/client';
const Animate = () => {
    const ref = React.useRef();
    useLayoutEffect(() => {
        ref.current.style.transform = `translate(500px)`;
        ref.current.style.transition = `all 500ms`;
    });
    let style = {
        width: '100px',
        height: '100px',
        borderRadius: '50%',
        backgroundColor: 'red'
    }
    return (
        <div style={style} ref={ref}></div>
    )
}
const element = <Animate />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

请注意,由于useLayoutEffect是在浏览器绘制之前执行的,因此如果你的副作用函数执行时间过长,那么它可能会阻塞屏幕的更新,导致用户体验下降。因此,在大多数情况下,我们更推荐使用useEffect

在很多情况下,useEffect将是你的首选解决方案。它比useLayoutEffect更安全,因为如果你的副作用有可能被延迟执行,React将在用户看到更新的屏幕后执行它。

但是,如果你的副作用中有一些需要同步执行的计算(例如测量布局),或者你需要在浏览器绘制新的屏幕之前立即同步更新状态,那么你就需要使用useLayoutEffect

9. 如何获取最新的state值 #

如果你希望在某些异步操作(例如:setTimeout、fetch等)中获取到最新的 state 值,你可能会遇到一些问题。因为在异步函数中获取的 state 值,可能是定义这个异步函数时 state 的值,而不是最新的值。

一种解决办法是使用 useRef,而不是直接使用 state。你可以将 state 的值同步到 ref 中,然后在异步函数中使用 ref,这样你就可以获取到最新的 state 值了。

下面是一个例子:

// 引入 React 库,useState,useEffect 和 useRef 钩子
import React, { useState, useEffect, useRef } from 'react';
// 引入 ReactDOM 库的客户端部分
import ReactDOM from 'react-dom/client';
// 定义一个名为 Counter 的函数组件
function Counter() {
    // 使用 useState 钩子创建一个状态变量 count 和一个更新此状态的函数 setCount
    const [count, setCount] = useState(0);
    // 使用 useRef 钩子创建一个 ref 对象 countRef
    const countRef = useRef(count);
    // 使用 useEffect 钩子在 count 状态变更时,将 count 的最新值赋给 countRef.current
    useEffect(() => {
      countRef.current = count;
    }, [count]);
    // 定义一个名为 handleAlertClick 的函数,该函数在3秒后弹出警告框显示点击次数
    const handleAlertClick = () => {
      setTimeout(() => {
        alert('You clicked on: ' + countRef.current);
      }, 3000);
    }
    // 返回 JSX 元素,包含一个显示点击次数的段落,一个增加点击次数的按钮和一个弹出警告的按钮
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
        <button onClick={handleAlertClick}>
          Show alert
        </button>
      </div>
    );
}
// 创建一个名为 element 的 JSX 元素,元素类型为 Counter 组件
const element = <Counter />
// 通过 ReactDOM.createRoot 函数在 id 为 'root' 的 DOM 元素上创建一个 root
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将 element 渲染到 root 中
root.render(element);

在上述代码中,当用户点击 "Click me" 按钮时,state count 会增加,这个更改将触发 useEffect,并更新 countRef.current 的值。然后,当用户点击 "Show alert" 按钮时,会显示一个在 3 秒后的警报,显示的是点击 "Click me" 按钮时的 count 值。即使在此期间 count 的值发生了更改,警报仍然会显示出最新的 count 值,因为它使用的是 countRef.current,而不是 count

10. useImperativeHandle #

useImperativeHandle 是 React 的一个内置 Hook,它可以让你在使用 ref 时自定义暴露给父组件的实例值。通常,父组件通过 ref 可以获取子组件的 DOM 节点,但有时我们希望提供更复杂的操作,或者不暴露某些信息,这时就可以用到 useImperativeHandle

useImperativeHandle 接受两个参数:第一个参数是一个 ref,第二个参数是一个返回对象的函数,这个对象中的值将会附加到第一个参数 ref 上。

下面是一个例子:

// 从 React 中导入 useRef,useImperativeHandle 和 forwardRef 函数
import React, { useRef, useImperativeHandle, forwardRef } from "react";
// 从 'react-dom/client' 中导入 ReactDOM
import ReactDOM from 'react-dom/client';
// 定义一个名为 FancyInput 的组件,接受 props 和 ref 参数
function FancyInput(props, ref) {
    // 创建一个名为 inputRef 的 ref
    const inputRef = useRef();
    // 使用 useImperativeHandle 自定义暴露给父组件的实例值
    useImperativeHandle(ref, () => ({
        // 提供一个名为 focus 的方法,用于使输入框获取焦点
        focus: () => {
            inputRef.current.focus();
        }
    }));
    // 返回一个带有 inputRef 的 input 元素
    return <input ref={inputRef} />;
}
// 使用 forwardRef 将 FancyInput 组件的 ref 属性暴露给父组件
FancyInput = forwardRef(FancyInput);
// 定义一个名为 ParentComponent 的组件
function ParentComponent() {
    // 创建一个名为 inputRef 的 ref
    const inputRef = useRef();
    // 返回包含 FancyInput 组件和一个按钮的元素
    // 点击按钮时,会调用 FancyInput 组件的 focus 方法
    return (
        <div>
            <FancyInput ref={inputRef} />
            <button onClick={() => inputRef.current.focus()}>Focus the input</button>
        </div>
    );
}
// 创建一个名为 element 的 JSX 元素,元素类型为 ParentComponent 组件
const element = <ParentComponent />
// 在 id 为 'root' 的 DOM 元素上创建一个名为 root 的 root
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将 element 渲染到 root 中
root.render(element);

在这个例子中,FancyInput 组件接收到 ref,然后通过 useImperativeHandle 自定义了父组件可以通过 ref 调用的 focus 方法。然后在 ParentComponent 组件中,我们就可以通过 inputRef.current.focus() 来让输入框获得焦点。

请注意,在使用 useImperativeHandle 的同时,也需要配合 React.forwardRef 来实现将 ref 从父组件传递给子组件。

总的来说,useImperativeHandle 是一个比较高级的 Hook,一般情况下我们不会太频繁地使用它,只有在必须对子组件的某些实例方法进行自定义或限制访问时,才会使用到。

11.Todo #

todos

11.1 任务管理API #

11.1.1 POST /tasks #

创建新任务。

请求体:

{
    "description": "string",
    "expected_completion_time": "string"
}

响应:

11.1.2 DELETE /tasks/:id #

通过ID删除任务。

路径参数:

响应:

11.1.3 PUT /tasks/:id #

通过ID更新任务。具体来说,它将任务的状态设置为2,并更新实际完成时间。

路径参数:

响应:

11.1.4 GET /tasks #

检索任务。如果在查询参数中提供了状态,它将返回具有该状态的任务。

查询参数:

响应:

11.2 创建项目 #

npm i create-a-react-app -g
cra todos

11.3 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

11.4 src\App.js #

src\App.js

import React, { useState, useEffect } from 'react';
import { Button, Tag, Table, Modal, Form, Input, DatePicker, message, Popconfirm, ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import moment from 'moment';
import service from './service/task';
import './App.css';
const { Column } = Table;
const App = () => {
    const [tasks, setTasks] = useState([]);
    const [filteredTasks, setFilteredTasks] = useState([]);
    const [loading, setLoading] = useState(false);
    const [filter, setFilter] = useState('all');
    const [visible, setVisible] = useState(false);
    const [form] = Form.useForm();
    const taskStatus = [
        { label: "全部", value: "all" },
        { label: "未完成", value: 1 },
        { label: "已完成", value: 2 },
    ];
    const handleComplete = async (id) => {
        try {
            await service.updateTask(id);
            retrieveTasks();
            message.success('任务完成成功');
        } catch (error) {
            message.error('任务更新失败');
        }
    };
    const handleDelete = async (id) => {
        try {
            await service.deleteTask(id);
            retrieveTasks();
            message.success('任务删除成功');
        } catch (error) {
            message.error('删除任务失败');
        }
    };
    const handleNewTask = async () => {
        try {
            const values = await form.validateFields();
            const formatted_expected_completion_time = moment(values.expected_completion_time).format('YYYY-MM-DD HH:mm:ss');
            await service.createTask(values.description, formatted_expected_completion_time);
            retrieveTasks();
            setVisible(false);
            form.resetFields();
            message.success('任务创建成功');
        } catch (errorInfo) {
            console.log('Failed:', errorInfo);
        }
    };
    const retrieveTasks = async () => {
        try {
            setLoading(true);
            const data = await service.getTasks();
            setTasks(data);
            setLoading(false);
        } catch (error) {
            message.error('获取任务列表失败');
            setLoading(false);
        }
    };
    useEffect(() => {
        retrieveTasks();
    }, []);
    useEffect(() => {
        setFilteredTasks(tasks.filter(task => filter === 'all' || task.status === filter));
    }, [tasks, filter]);
    return (
        <ConfigProvider locale={zhCN}  >
            <div className="home">
                <div className="header">
                    <h3>TASK OA任务管理系统</h3>
                    <Button type="primary" onClick={() => setVisible(true)}>新增任务</Button>
                </div>
                <div className="tags">
                    {taskStatus.map(status =>
                        <Tag color={filter === status.value ? "blue" : "default"} key={status.value} onClick={() => setFilter(status.value)}>
                            {status.label}
                        </Tag>
                    )}
                </div>
                <Table dataSource={filteredTasks} rowKey="id" loading={loading}>
                    <Column title="编号" dataIndex="id" key="id" />
                    <Column title="任务描述" dataIndex="description" key="description" />
                    <Column title="状态" key="status" render={(text, record) => (
                        record.status === 1 ? '未完成' : '已完成'
                    )} />
                    <Column title="预期完成时间" dataIndex="expected_completion_time" key="expected_completion_time" />
                    <Column title="实际完成时间" dataIndex="actual_completion_time" key="actual_completion_time" />
                    <Column title="操作" key="action" render={(text, record) => (
                        <span>
                            <Popconfirm
                                title="确定要删除吗?"
                                onConfirm={() => handleDelete(record.id)}
                                icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}
                            >
                                <a>删除</a>
                            </Popconfirm>
                            <Popconfirm
                                title="确定要完成吗?"
                                onConfirm={() => handleComplete(record.id)}
                                icon={<ExclamationCircleOutlined style={{ color: 'green' }} />}
                            >
                                <a style={{ marginLeft: 10 }}>完成</a>
                            </Popconfirm>
                        </span>
                    )} />
                </Table>
                <Modal title="新增任务" open={visible} onOk={handleNewTask} onCancel={() => setVisible(false)}>
                    <Form form={form} layout="vertical" name="form_in_modal">
                        <Form.Item
                            name="description"
                            label="任务描述"
                            rules={[{ required: true, message: '请输入任务描述' }]}
                        >
                            <Input type="textarea" />
                        </Form.Item>
                        <Form.Item
                            name="expected_completion_time"
                            label="预计完成时间"
                            rules={[{ required: true, message: '请选择预计完成时间' }]}
                        >
                            <DatePicker showTime />
                        </Form.Item>
                    </Form>
                </Modal>
            </div>
        </ConfigProvider>
    );
}
export default App;

11.5 App.css #

src\App.css

.home {
    width: 80%;
    margin: 0px auto;
}

.home .header {
    display: flex;
    justify-content: space-around;
    align-items: center;
    border-bottom: 1px solid #ccc;
}

.home .tags {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
}

.home .tags span {
    margin: 10px;
    cursor: pointer;
}

11.6 task.js #

src\service\task.js

import service from '.';
let task = {
    //获取所有的任务 参数是status all 不给 1未完成 2已完成
    getTasks(){// http://localhost:3000/tasks?status=2
        return service.get('/tasks');
    },
    //创建任务
    createTask(description,expected_completion_time){
        return service.post('/tasks',{
            description,//任务描述
            expected_completion_time//预计完成时间
        });
    },
    //更新任务,就让任务完成
    updateTask(id){
        return service.put(`/tasks/${id}`);
    },
    //删除任务
    deleteTask(id){
        return service.delete(`/tasks/${id}`);
    }
}

export default task;

11.7 service\index.js #

src\service\index.js

import axios from 'axios';
import { message } from 'antd';

// 创建实例,封装公共配置
const service = axios.create({
    baseURL: 'http://todos.zhufengpeixun.com', // 基本的URL地址
    timeout: 5000 // 请求的超时时间
});

service.interceptors.request.use((config) => {
    let token = 'token';
    if (token) {
        config.headers['Authorization'] = `Bearer ` + token;
    }
    return config;
}, (error) => {
    // 可以在这里进行一些错误处理
    return Promise.reject(error);
});

service.interceptors.response.use((response) => {
    // 如果返回的状态码不是2XX,有些时候希望把不是2XX的状态码和响应当成错误来处理
   if (!/2\d{2}/.test(response.status)) {
        message.error('API Error' + response.data.message);
        throw new Error('API Error' + response.data.message);
   }
   return response.data;
}, (error) => {
    // 如果错误对象中有响应对象
    if (error.response) {
        message.error('Error' + error.response.status); // 状态码
    } else if (error.request) {
        message.error('Network Error'); // 客户端请求没有发出,网络错误
    } else {
        message.error('Error' + error.message);
    }
    return Promise.reject(error);
});

export default service;