讲师中心 微信公众号
AI工具推荐 视频效率加速

如何使用 React 和 Rest API 构建网站(React 基础知识解释)

胖枫姑娘_1416

胖枫姑娘_1416

发布时间:2024-11-07 08:57:21

|

703人浏览过

|

来源于dev.to

转载

如何使用 react 和 rest api 构建网站(react 基础知识解释)

react 和 typescript 是用于构建可扩展、可维护和安全网站的强大框架。 react 提供了灵活且基于组件的架构,而 typescript 在 javascript 中添加了静态类型,以实现干净且可读的代码。本文将指导您使用 react 和 typescript 设置一个简单的网站,涵盖入门所需的核心概念。

为什么选择 react 和 typescript?

typescript 在 javascript 开发人员中很受欢迎,因为它可以在开发过程中捕获错误并使代码更易于理解和重构。两者非常适合构建现代、快速的网站和应用程序,并具有可扩展的可维护代码。

** 在 github 上查看完整代码:https://github.com/alexiacismaru/techtopia/tree/main/frontend

基本的 react 概念以及如何使用它们来构建网站

让我们为一个名为 techtopia 的虚构游乐园建立一个网站。我们将显示景点等元素以及它们在地图上的位置、登陆页面或加载页面。此外,我们还将允许添加/删除页面元素或基于变量搜索它们。

设置

通过将其复制到终端来创建一个空的 react 项目。

npm create vite@latest reactproject --template react-ts

然后运行空项目,浏览器窗口中将打开一个新选项卡。

cd reactproject
npm run dev

最终项目结构概述

reactproject/
├── node_modules/
├── public/
├── src/
│   ├── assets/
│   ├── components/
│   ├── context/
│   ├── hooks/
│   ├── model/
│   ├── services/
│   ├── app.css
│   ├── app.tsx
│   ├── index.css
│   ├── vite-env.d.ts
├── .gitignore
├── package.json
└── tsconfig.json

成分

组件是也可以重用的网页元素。它们可以是网页的一部分,例如页眉或页脚,也可以是整个页面,例如用户列表。它就像一个 javascript 函数,但返回一个渲染的元素。

export function header() {
    return (
        <header style={{ display: 'block', width: '100%', top: 0, left: 0, zindex: 'var(--header-and-footer)' }}>
            <div style={{
                borderbottom: '1px solid white',
                boxshadow: '',
                backgroundcolor: 'transparent',
                paddingleft: '1rem',
                paddingright: '1rem',
                marginleft: 'auto',
                marginright: 'auto',
            }}>
                <div
                    style={{
                        display: 'flex',
                        justifycontent: 'space-between',
                        padding: '5px',
                        alignitems: 'baseline',
                    }}
                >
                    <a href='/techtopia' style={{
                        fontsize: '40px', fontfamily: 'marolla__', color: 'black',
                        fontweight: 'bold',
                    }}>techtopia</a>
                    <div style={{display: 'flex',
                        justifycontent: 'space-around',
                        padding: '5px',
                        alignitems: 'baseline',}}>
                        <a href='/refreshment-stands' style={{
                            marginright: '10px', color: 'black'
                        }}>refreshment stands</a>
                        <a href='/attractions' style={{ marginright: '10px', color: 'white'
                        }}>attractions</a>
                        <a href='/map' style={{ marginright: '60px', color: 'white'
                        }}>map</a>
                    </div>
                </div>
            </div>
        </header>
    )
}

jsx

jsx 是 javascript xml,允许用户在 .jsx 文件中编写类似 html 的代码。

<button sx={{padding: "10px", color: 'black'}} onclick={onclose}>x</button>

多伦多证券交易所

tsx 是包含 jsx 语法的 typescript 文件的文件扩展名。借助 tsx,您可以使用现有的 jsx 语法编写经过类型检查的代码。

interface refreshmentstand {
    id: string;
    name: string;
    isopen: boolean;
}

const reshfresment = (props: refreshmentstand) => {
  return (
    <div>
        <h1>{props.name}</h1>
        <p>{props.isopen}</p>
    </div>
  );
};

碎片

片段将多个元素返回给组件。它对元素列表进行分组,而不创建额外的 dom 节点。

我们可以使用它们从 java 后端获取数据(通过本文查看如何构建 java 应用程序:https://medium.com/@alexia.csmr/using-bounded-contexts-to-build -a-java-application-1c7995038d30)。首先安装 axios 并使用应用程序中的基本后端 url。然后,我们将创建一个使用 get 获取所有景点的片段。

import axios from 'axios'
import { poi } from '../model/poi'

const backend_url = 'http://localhost:8093/api'

export const getattractions = async () => {
    const url = backend_url + '/attractions'
    const response = await axios.get<poi[]>(url)
    return response.data
}

可以扩展到根据参数获取数据,post,delete等

export const addattraction = async (attractiondata: omit<poi, 'id'>) => {
    const url = backend_url + '/addattraction'
    const response = await axios.post(url, attractiondata)
    return response.data
}

export const getattraction = async (attractionid: string) => {
    const url = backend_url + '/attractions'
    const response = await axios.get<poi>(`${url}/${attractionid}`)
    return response.data
}

export const getattractionbytags = async (tags: string) => {
    const url = backend_url + '/attractions'
    const response = await axios.get<poi[]>(`${url}/tags/${tags}`)
    return response.data
}

状态

状态是一个 react 对象,包含有关组件的数据或信息。组件的状态可能会随着时间的推移而发生变化,当发生变化时,组件会重新渲染。

要根据参数从列表中获取单个元素,您可以使用 useparams() 挂钩。

const { id } = useparams()
const { isloading, iserror, attraction } = useattraction(id!)
const { tag } = useparams()
const { isloadingtag, iserrortag, attractions } = usetagsattractions(tag!)

挂钩

如上所示,我使用了_useattractions()和_usetagsattractions()。它们是钩子,可以进行个性化以获得您想要的任何数据。在此示例中,他们根据 id _或 _tags 获取景点。 hooks 只能在 react 函数组件内部调用,只能在组件的顶层调用,并且不能是有条件的。

import {usemutation, usequery, usequeryclient} from '@tanstack/react-query'
import {poi} from "../model/./poi.ts";
import { addattraction, getattractions } from '../services/api.ts'
import { usecontext } from 'react'

export function useattractions() {
    const queryclient = usequeryclient()
    const {
        isloading: isdoingget,
        iserror: iserrorget,
        data: attractions,
    } = usequery({
        querykey: ['attractions'],
        queryfn: () => getattractions(),
    })

    const {
        mutate,
        isloading: isdoingpost,
        iserror: iserrorpost,
    } = usemutation((item: omit<poi, 'id'>) => addattraction(item), {
        onsuccess: () => {
            queryclient.invalidatequeries(['attractions'])
        },
    });

    return {
        isloading: isdoingget || isdoingpost,
        iserror: iserrorget || iserrorpost,
        attractions: attractions || [],
        addattraction: mutate
    }
}

isloading 和 iserror

为了获得更好的 ui 体验,最好让用户知道发生了什么,即元素正在加载,或者执行此操作时出现错误。它们首先在钩子中声明,然后在组件中引入。

batch-git-url-replace
batch-git-url-replace

批量替换指定目录下所有 Git 仓库的远程地址(remote URL)。 当用户需要将 Git 仓库从一个服务器迁移到另一个服务器时使用。 触发词:git remote 替换、git url 批量修改、git 仓库迁移、更换 git 地址、批量修改 remote url。

下载
const navigate = usenavigate()
const { isloading, iserror, attractions, addattraction } = useattractions()

if (isloading) {
    return <loader />
}

if (iserror) {
    return <alert severity='error'>error</alert>
}

您还可以为更加自定义的网站创建单独的加载器或警报组件。

export default function loader() {
    return (
        <div>
            <img alt="loading..."
                 src="https://media0.giphy.com/media/rlqidjhbel1spmdlhz/giphy.gif?cid=6c09b9522vr2magrjgn620u5mfz1ymnqhpvg558dv13sd0g8&ep=v1_stickers_related&rid=giphy.gif&ct=s"/>
            <h3>loading...</h3>
        </div>
    )
}

现在,当页面加载时,用户将在屏幕上看到特殊的动画。

映射项目(列表和键)

如果你想显示列表中的所有元素,那么你需要映射所有元素。

import { usestate } from 'react'
import { usenavigate } from 'react-router-dom'
import { useattractions } from '../hooks/usepoi.ts'
import { poi } from '../model/./poi.ts'

export default function attractions() {
    const navigate = usenavigate()
    const { isloading, iserror, attractions, addattraction } = useattractions()

    return (
      <div style={{ margintop: '70px' }}>
          {filteredattractions
              .map(({ id, name, image }: poi) => (
                  <div onclick={() => navigate(`/attractions/${id}`)} >
                      <div>
                          <img src={image} alt={name}/>
                          <h3>{name}</h3>
                      </div>
                  </div>
              ))}
      </div>
    )
}

创建一个单独的文件,在其中声明 attraction 元素及其变量。

// ../model/poi.ts

export interface poi {
    id: string;
    name: string;
    description: string;
    tags: string;
    agegroup: string;
    image: string;
}

您可以在此处创建一个类型,以便稍后使用表单添加更多景点:

export type createpoi = omit<poi, 'id'>; # id is automatically generated so we don't need to manually add it

添加项目

我们已经创建了所需的片段和挂钩,因此现在我们可以制作一个表单,用户可以在其中写入属性并向网页添加新的吸引力。该表单是使用 mui 框架创建的。首先我将展示整个代码并分段解释。

import {createpoi} from "../model/./poi.ts";
import {z} from 'zod';
import {zodresolver} from "@hookform/resolvers/zod";
import {controller, useform} from "react-hook-form";
import {
    box,
    button,
    dialog,
    dialogactions,
    dialogcontent,
    dialogtitle,
    textfield,
} from '@mui/material'

interface attractiondialogprops {
    isopen: boolean;
    onsubmit: (attraction: createpoi) => void;
    onclose: () => void;
}

const itemschema: z.zodtype<createpoi> = z.object({
    name: z.string().min(2, 'name must be at least 2 characters'),
    description: z.string(),
    tags: z.string(),
    agegroup: z.string(),
    image: z.string().url(),
})

export function addattractiondialog({isopen, onsubmit, onclose}: attractiondialogprops) {
    const {
        handlesubmit,
        control,
        formstate: {errors},
    } = useform<createpoi>({
        resolver: zodresolver(itemschema),
        defaultvalues: {
            name: '',
            description: '',
            tags: '',
            agegroup: '',
            image: '',
        },
    });

    return (
        <dialog open={isopen} onclose={onclose}>
            <form
                onsubmit={handlesubmit((data) => {
                    onsubmit(data)
                    onclose()
                })} 
            >
                <div>
                    <dialogtitle>add attraction</dialogtitle>
                    <button onclick={onclose}>
                        x
                    </button>
                </div>
                <dialogcontent>
                    <box>
                        <controller
                            name="name"
                            control={control}
                            render={({field}) => (
                                <textfield
                                    {...field}
                                    label="name"
                                    error={!!errors.name}
                                    helpertext={errors.name?.message}
                                    required
                                />
                            )}
                        />
                        <controller
                            name="description"
                            control={control}
                            render={({field}) => (
                                <textfield
                                    {...field}
                                    label="description"
                                    error={!!errors.description}
                                    helpertext={errors.description?.message}
                                />
                            )}
                        />
                        <controller
                            name="tags"
                            control={control}
                            render={({field}) => (
                                <textfield
                                    {...field}
                                    label="tags"
                                    error={!!errors.tags}
                                    helpertext={errors.tags?.message}
                                    required
                                />
                            )}
                        />
                        <controller
                            name="agegroup"
                            control={control}
                            render={({field}) => (
                                <textfield
                                    {...field}
                                    label="age group"
                                    error={!!errors.agegroup}
                                    helpertext={errors.agegroup?.message}
                                    required
                                />
                            )}
                        />
                        <controller
                            name="image"
                            control={control}
                            render={({field}) => (
                                <textfield
                                    {...field}
                                    label="image"
                                    error={!!errors.image}
                                    helpertext={errors.image?.message}
                                    required
                                />
                            )}
                        />
                    </box>
                </dialogcontent>
                <dialogactions>
                    <button type="submit" variant="contained">
                        add
                    </button>
                </dialogactions>
            </form>
        </dialog>
    )
}

如果您想让表单成为弹出窗口而不是单独的页面,请添加 isopen()isclosed() 属性。 onsubmit() 是强制性的,因为这将触发 createpoi() 函数并将新对象添加到列表中。

interface attractiondialogprops {
    isopen: boolean;
    onsubmit: (attraction: createpoi) => void;
    onclose: () => void;
}

为了进行用户表单验证,我们将安装并导入 zod。在这里声明输入需要什么格式以及是否有最小或最大长度等要求。

const itemschema: z.zodtype<createpoi> = z.object({
    name: z.string().min(2, 'name must be at least 2 characters'),
    description: z.string(),
    tags: z.string(),
    agegroup: z.string(),
    image: z.string().url(),
})

在组件内部,我们需要实现提交和用户验证。

const {
    handlesubmit,
    control,
    formstate: {errors},
    } = useform<createpoi>({
    resolver: zodresolver(itemschema),
    defaultvalues: {
        name: '',
        description: '',
        tags: '',
        agegroup: '',
        image: '',
    },
});

错误将在表单的 textfield 中与任何其他属性一起实现。

<textfield
    {...field}
    label="name"
    error={!!errors.name}
    helpertext={errors.name?.message}
    required
/>

确保表单可以在开始时关闭并提交。

<dialog open={isopen} onclose={onclose}>
    <form
        onsubmit={handlesubmit((data) => {
            onsubmit(data)
            onclose()
        })} 
    >     
    </form>
</dialog>

您可以在另一个组件中实现此弹出窗口。

import { fab } from '@mui/material'
import addicon from '@mui/icons-material/add'

<fab
    size='large'
    aria-label='add' 
    onclick={() => setisdialogopen(true)}
>
    <addicon />
</fab>

删除项目

创建一个使用 delete 的钩子并在组件中实现它。

import { usemutation, usequery, usequeryclient } from '@tanstack/react-query'
import { deleterefreshmentstand, getrefreshmentstand } from '../services/api.ts'
import { usecontext } from 'react' 

export function userefreshmentstanditem(refreshmentstandid: string) { 
    const queryclient = usequeryclient()

    const {
        isloading: isdoingget,
        iserror: iserrorget,
        data: refreshmentstand,
    } = usequery({
        querykey: ['refreshmentstand'],
        queryfn: () => getrefreshmentstand(refreshmentstandid), 
    })

    const deleterefreshmentstandmutation = usemutation(() => deleterefreshmentstand(refreshmentstandid), {
        onsuccess: () => {
            queryclient.invalidatequeries(['refreshmentstands']);
        },
    });

    const handledeleterefreshmentstand = () => {
        deleterefreshmentstandmutation.mutate(); // trigger the delete mutation
    };

    return {
        isloading: isdoingget || deleterefreshmentstandmutation.isloading,
        iserror: iserrorget || deleterefreshmentstandmutation.iserror,
        refreshmentstand,
        deleterefreshmentstand: handledeleterefreshmentstand,
    };
}
export default function refreshmentstand() {
    const { id } = useparams()
    const { isloading, iserror, refreshmentstand, deleterefreshmentstand } = userefreshmentstanditem(id!)


    if (isloading) {
        return <loader />
    }

    if (iserror || !refreshmentstand) {
        return <alert severity='error'>error</alert>
    }

    return (
        <>
            <cardmedia component='img' image={background} alt='background' />
            <authheader />
            <div style={{ display: 'flex', alignitems: 'center' }}>
                <div>
                    <h1>{refreshmentstand.name}</h1>
                    <p>status: {refreshmentstand.isopen ? 'open' : 'closed'}</p>
                    /* implement the delete button */
                    <fab>
                        <deleteicon onclick={deleterefreshmentstand}/>
                    </fab>
                </div>
                <img src={refreshmentstand.image} alt='refreshmentstand image' />
            </div>
            <footer />
        </>
    )
}

过滤项目

在组件内部创建一个用于过滤文本输入的切换开关和一个根据年龄组或标签过滤景点的常量。可选链接 (?) 确保它处理 null 或未定义的值而不会出现错误。

const togglefilter = () => {
    setisfilteropen(!isfilteropen)
}

const filteredattractions = attractions
    .filter((attraction: poi) =>
        attraction.agegroup?.tolowercase().includes(agegroupfilter.tolowercase()),
    )
    .filter((attraction: poi) =>
        attraction.tags?.tolowercase().includes(tagsfilter.tolowercase()),
    )

在迭代项目列表时包含它。

{filteredAttractions
.filter((attraction: POI) =>
    searchText.trim() === '' ||
    attraction.name.toLowerCase().includes(searchText.toLowerCase()),
)
.map(({ id, name, image }: POI) => (
    <div>
        <div>
            <img
                src={image}
                alt={name}
            />
            <h3>{name}</h3>
        </div>
    </div>
))}

结论

将 react 与 typescript 结合使用使您能够构建易于维护和扩展的动态、安全的网站。 typescript 的类型检查可以防止运行时错误,而 react 基于组件的结构可以有效地组织项目。

热门AI工具

更多
Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

LibLibAI
LibLibAI Hot

一款AI视频创作工具,主要用于国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

相关专题

更多
TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

232

2026.02.13

TypeScript全栈项目架构与接口规范设计
TypeScript全栈项目架构与接口规范设计

本专题面向全栈开发者,系统讲解基于 TypeScript 构建前后端统一技术栈的工程化实践。内容涵盖项目分层设计、接口协议规范、类型共享机制、错误码体系设计、接口自动化生成与文档维护方案。通过完整项目示例,帮助开发者构建结构清晰、类型安全、易维护的现代全栈应用架构。

460

2026.02.25

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

311

2026.03.13

TypeScript 全栈开发进阶指南
TypeScript 全栈开发进阶指南

面向有 JavaScript 基础的开发者,深入讲解 TypeScript 的类型系统与全栈开发实践。

226

2026.06.03

TypeScript Node.js 全栈工程化与Monorepo架构实践
TypeScript Node.js 全栈工程化与Monorepo架构实践

本专题围绕 TypeScript 在 Node.js 全栈开发中的工程化实践展开,系统讲解 Monorepo 架构设计、包管理策略、模块复用机制以及服务端与前端统一类型系统的构建方法。通过真实项目案例,帮助开发者提升大型全栈项目的可维护性与协作效率。

458

2026.06.16

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

509

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1638

2024.03.01

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

5704

2023.10.24

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

0

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 1.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 5.6万人学习

CSS教程
CSS教程

共754课时 | 93.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn