React Hooks+TypeScript案例教程之学生注册页面的实现


由微软推出的TypeScript自从一推出就广受关注,现在无论开发前端React/Vue,还是后端API,很多项目中都广泛接受了TypeScript。这个系列通过一个案例:学生注册页面来讲解如何结合React Hooks使用TypeScript。

TypeScript

这个小项目的最终目标是实现学生的添加和删除功能:

TypeScript

创建项目

运行如下命令:

npx create-react-app react-hooks-student-reg --template typescript
cd react-hooks-student-reg
yarn start

定义类型

在使用TS的时候,最重要的一步就是定义类型。首先创建src/typings/index.ts:

export interface IStudent {
    id: number;
    name: string;
    address: string
}

export interface IState {
    studentList: IStudent[];
}

export enum STUDENT_ACTION_TYPE {
    INIT = 'init',
    ADD = 'add',
    REMOVE = 'remove'
}

export interface IStudentAction {
    actionType: STUDENT_ACTION_TYPE;
    payload: IStudent | IStudent[] | number
}

定义操作类型的相关方法

添加文件src/reducers/StudentReducer.ts:

import { IState, IStudent, IStudentAction, STUDENT_ACTION_TYPE } from "../typings";

function studentReducer(state: IState, action: IStudentAction): IState {
    const { actionType, payload } = action;
    switch(actionType) {
        case STUDENT_ACTION_TYPE.INIT: 
            // 注意:{...state, studentList: xxx}这种写法的含义是:展开state所有属性,
            // 同时更新其中的studentList属性
            return {
                ...state,
                studentList: payload as IStudent[]
            }
        case STUDENT_ACTION_TYPE.ADD:
            return {
                ...state, 
                studentList: [...state.studentList, payload as IStudent]
            }
        case STUDENT_ACTION_TYPE.REMOVE:
            return {
                ...state,
                studentList: state.studentList.filter(student => student.id !== payload as number)
            }   
        case STUDENT_ACTION_TYPE.UPDATE:
            const {id, name, address} = payload as IStudent;
            return {
                ...state,
                studentList: state.studentList.map(stu => {
                    return stu.id === id? {
                        ...stu,
                        name,
                        address
                    }: {
                        ...stu
                    }
                })
            }    
        default:
            return state;    
    }
}

export {
    studentReducer
}

实现注册功能组件

添加文件src/components/Student/Registration.ts:

import { useRef, FC, ReactElement } from 'react';
import { IStudent } from '../../typings';

interface IProps {
    addStudent: (student: IStudent) => void;
    studentList: IStudent[];
}

const Registration: FC<IProps> = ({
    addStudent,
    studentList
}): ReactElement => {

    const nameRef = useRef<HTMLInputElement>(null);
    const addressRef = useRef<HTMLInputElement>(null);

    const add = (): void => {
        const name: string = nameRef.current!.value.trim();
        const address: string = addressRef.current!.value.trim();
        if(name.length > 0 && address.length > 0) {
            const isExist = studentList.find(stu => stu.name===name && stu.address===address)
            if(isExist) {
                alert("重复数据");
                return;
            }else{
                addStudent({
                    id: new Date().getTime(),
                    name: name,
                    address: address
                })
                nameRef.current!.value = '';
                addressRef.current!.value = '';
            }
        }else{
            alert("请输入姓名和住址");
        }
    }

    return(
        <>
            <div className="field">
                <label>姓名</label>
                <input ref={ nameRef } type="text" placeholder="学生姓名" id="name"/>
            </div>
            <div className="field">
                <label>住址</label>
                <input ref={ addressRef } type="text"  placeholder="学生住址" id="address"/>
            </div>   
            <button onClick = { add }>注册</button>
        </>
    )
}

export default Registration;

学生列表组件

添加文件src/components/List.ts:

import { FC } from 'react';
import { IStudent } from "../../typings";
import SingleStudent from "./SingleStudent";

interface IProps {
    studentList: IStudent[];
    removeStudent(id: number): void;
}

const List: FC<IProps> = ({ studentList, removeStudent }) => {
    return(
        <>
            <h2>学生列表</h2>
            <ul>
            {
                studentList.map((student: IStudent) => {
                    return <SingleStudent key={student.id} student={student} removeStudent={removeStudent}/>
                })
            }
            </ul>
        </>
    )
}

export default List;

单一学生组件

添加文件src/components/Student/SingleStudent.tsx:

import { FC } from 'react';
import { IStudent } from "../../typings";

interface IProps {
    student: IStudent;
    removeStudent(id: number): void;
}

const SingleStudent: FC<IProps> = ({
    student,
    removeStudent
}) => {
    const { id, name, address } = student;

    return(
        <li>
            <h4>{name}</h4>
            <p>{address}</p>
            <p><button onClick={() => removeStudent(id)}>删除</button></p>
        </li>
    )
}

export default SingleStudent;

入口页面

添加文件src/components/Student/index.tsx:

import { useEffect, useReducer } from 'react';
import Registration from './Registration';
import List from './List';
import { IStudent, IState, STUDENT_ACTION_TYPE } from '../../typings';
import { studentReducer } from '../../reducers/StudentReducer';

const initialState: IState = {
  studentList: []
}

function Index() {
  const [state, dispatch] = useReducer(studentReducer, initialState);

  // 用于初始化
  useEffect(() => {
    const studentList = JSON.parse(localStorage.getItem('studentList') || '[]');
    dispatch({
      actionType: STUDENT_ACTION_TYPE.INIT,
      payload: studentList
    })
  }, []);

  // 用于监测studentList的变化,并保存在localStorage
  useEffect(() => {
    localStorage.setItem('studentList', JSON.stringify(state.studentList));
  }, [state.studentList]);

  const addStudent = (student: IStudent) => {
    dispatch({
      actionType: STUDENT_ACTION_TYPE.ADD, 
      payload: student
    })
  }

  const removeStudent = (id: number) => {
    dispatch({
      actionType: STUDENT_ACTION_TYPE.REMOVE, 
      payload: id
    })
  }

  return (
    <div className="wrapper">
      <Registration studentList = {state.studentList} addStudent = { addStudent } /> 
      <List studentList = {state.studentList} removeStudent = {removeStudent}/>
    </div>
  );
}

export default Index;

App.tsx

import './App.css';
import StudentIndex from './components/Student';

function App() {
  
  return (
    <>
      <StudentIndex/>
    </>
  );
}

export default App;

文章作者: 逻思
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 逻思 !
  目录