发布于 2026-01-06 0 阅读
0

小型项目反思:React、TypeScript 和 PokéAPI TypeScript 数学 PokéAPI 设计 下一步是什么?

小型项目反思:React、TypeScript 和 PokéAPI

TypeScript

数学

PokéAPI

设计

接下来会发生什么?

项目在此

100 天代码挑战 第 8-10 天

这是一篇个人反思帖,详细记录了我使用 PokéAPI 制作宝可梦树果属性页面的两天半项目过程!需要说明的是,我是一名初级程序员,所以如果您觉得我的做法有什么不妥之处,请务必告诉我。写这篇反思帖的目的是为了梳理我的思路,并巩固我所学到的知识。


目标

  • 使用 API 以可视化方式收集和显示数据。
  • 使用来自一个 API 的数据查找另一个条目并获取数据
  • 学习 TypeScript/将其用于 React

计划功能

  • 风味等级
  • 相对尺寸比例
  • 增长周期图表(带时间轴)?

漏洞与遗憾

  • Chrome浏览器上不显示每日生长小时数的计量器。
  • setInterval 的计时有问题吗?感觉像是用了什么黑科技才搞定的。
  • 不截取过程屏幕截图
  • 不使用 CSS 过渡效果/无交互性

TypeScript

这是我第一次没有直接按照教程来学习一个概念或语言。我已经掌握了 C# 的基础知识,所以我只是找到了一些在 React 环境中使用 TypeScript 的例子,然后通过查找遇到的错误来解决问题。以下是我理解的 TypeScript 工作原理。

在 TypeScript 中,你必须为所有变量定义类型。这听起来很简单,但你需要知道它们的类型。然而,实际情况并非总是如此简单。当我需要处理大量数据并将其传递给状态以进行 Berry 查询时,情况就变得十分繁琐,因为我必须定义状态中每个部分的内容。也许我的做法有问题,但就我目前所见,我基本上需要定义两次。一次是在接口中,另一次是在构造函数中赋予初始值。说实话,我并不了解接口的深层含义,它只是用于以有序的方式定义状态和属性。

增长周期组成部分:

import React, { Component } from 'react';

interface IProps {
  id: number,
  growth_time: number
}

interface IState {
  timer: any,
  stage: string,
  x: number
}


class Growth extends Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      stage: '/growth/AllTreeSeed.png',
      timer: setInterval(this.setTimer, 0),
      x: 1
    }
  }

  render() {
    const { stage } = this.state;
    return (
      <img src={stage} />
    )
  }

  setTimer = () => {
    if (this.props.growth_time != 0) {
      clearInterval(this.state.timer);
      this.setState({ timer: setInterval(this.changeDisplay, 1000 * this.props.growth_time) });
    }
  }

  changeDisplay = () => {
    if (this.state.x >= 5) {
      this.setState({ x: 0 });
    }
    switch (this.state.x) {
      case 0: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/AllTreeSeed.png` }); break;
      case 1: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/AllTreeSprout.png` }); break;
      case 2: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Taller.png` }); break;
      case 3: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Bloom.png` }); break;
      case 4: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Berry.png` }); break;
    }
    this.setState({ x: this.state.x + 1 })
  }

  componentWillUnmount() {
    clearInterval(this.state.timer);
  }
}

export default Growth;
Enter fullscreen mode Exit fullscreen mode

setInterval 和 Hacks

这样做的目的是让生长周期图表的更新速度根据生长所需的时间而减慢。每个“小时”对应1秒。

刚才解决了之前遇到的问题,写这篇文章的时候实在没法让它一直存在。最初我把状态的计时器设置为 5000 * this.props.growth_time,但问题在于我的容器组件在初始化值时,growth_time 被设置为 0。所以当组件启动时,计时器的更新间隔被设置为每 0 毫秒更新一次,并且会疯狂地更新,直到我们清除这个计时器并启动一个新的计时器。这个问题之前就存在,因为我需要在接收到正确的 props 后更新一个函数,但是现在 componentDidUpdate() 不允许我设置状态,因为它会导致递归,所以已经失效了。同样地,虽然不会完全报错,但在 render 函数中调用 setTimer 会发出警告,提示每次渲染时都尝试更新数据。

解决方案(感觉像个权宜之计)是不断调用 setInterval(因为原始计时器设置为 0),直到我们收到 growth_time 属性不为 0 为止,一旦它有了属性(或者更确切地说,属性是我想要的),就清除间隔,因为我发现即使我更改计时器状态,它也会继续运行,然后为 changeDisplay 设置一个新的间隔。


数学

哎,数学。我以前总说自己笨得学不会数学,但当初我对编程也是这么说的,现在看看我学得多好!不过,即便是一些很简单的问题,我还是很难找到最佳解决方案,所以我们一步一步来吧。

API 会返回一个数值/40 的强度值。对于每种浆果口味,这个数值要么是第 10 位,要么是第 5 位,所以根据我们的表示方法,最终可能会出现半颗星的情况。一颗星值 10 分,因此我们取出强度值并除以 10,只保留个位数,从而计算出完整星和半颗星的数量(尽管它们被命名为 fullStars)。星星的生成是通过 for 循环实现的,所以即使数量超过 0.5,我们仍然会生成一颗新的星星。

为了判断我们是否有半颗星,我们使用取模运算符,看看除以 10 后是否有余数。使用三元运算符(相当于一个简化的 if 语句),如果余数为 0(假),则表示没有半颗星。如果余数是任何非零数字(真),则表示我们有一个半颗星。

我们用剩余部分计算空星的数量,然后使用另一个三元运算符来检查是否存在半星。如果存在,我们就用它替换一个完整的星。


    let potency = this.props.potency;
    let fullStars: number = potency / 10;
    let halfStars: number = potency % 10 ? 0 : 1;
    let emptyStars: number = 4 - fullStars;
    halfStars ? null : fullStars--;

Enter fullscreen mode Exit fullscreen mode

PokéAPI

我使用PokéAPI收集了大部分树果数据。我手动保存了 200 多张图片,用于展示树果的生长周期和大小对比,但树果图标实际上是通过 axios 链式调用 GET 请求获取的。树果信息与树果的物品列表是分开的,物品列表中包含的是该树果物品图片的 URL。

  fetchBerry() {
    axios.get(`https://pokeapi.co/api/v2/berry/${this.props.berryID}`)
      .then(res => {
        this.setState({ info: res.data });
        return axios.get(`${this.state.info.item.url}`)
      }).then(res => {
        this.setState({ item: res.data });
      })
  }
Enter fullscreen mode Exit fullscreen mode

设计

我看到一些关于 CSS 应该直接放在代码里还是放在单独的文件里的讨论。我个人比较倾向于放在单独的文件里,但我发现直接在代码里声明动态样式也很方便。对于每个浆果的大小,我们得到了一个数值,我直接把它转换成了图片的高度,然后除以 3,这样一些最大的浆果就不会破坏整体设计。这样一来,最终的浆果仍然非常小,但只要它们之间的比例大小保持一致,我觉得只要我们有图标精灵图来展示预期的设计,就没问题。

        <img 
          src={`${process.env.PUBLIC_URL}/size/${this.props.id}.png`}
          style={{ height: `${this.props.size / 3}px` }}
        />
Enter fullscreen mode Exit fullscreen mode

接下来会发生什么?

我需要提升我的 CSS 动画技能,所以需要一些交互式的东西,但我还没想好具体做什么。我采纳了这里评论里的一个建议,开始使用符号系统,并记录下我想学习的内容以及我和朋友们的项目想法。我了解了 VSCode 的 Live Share 功能,并和我的好朋友/导师一起试用了一下,感觉非常棒!

我们已经进行了一段时间的结对编程,虽然没有用到它,但我真的很想用它和她一起做一个项目,我只需要想出一个好主意!

文章来源:https://dev.to/misnina/small-project-reflection-react-typescript-and-pokeapi-2gmk