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

使用 React Hooks 创建宝可梦图鉴

使用 React Hooks 创建宝可梦图鉴

大家好,我写这篇指南的目的是为了展示如何使用 React 开发一个简单的应用程序,并手动配置 Webpack 和 Babel。如果您是这些技术的初学者,或者计划在不久的将来使用它们,我希望这篇指南能对您有所帮助。

开始之前

开始编写代码之前,我们需要在计算机上安装一些软件:

  • 我们将使用 npm 来管理应用程序依赖项,因此我们需要安装nodejs
  • 代码编辑器方面,你可以选择自己喜欢的,但我推荐使用VS Code

就这样,现在我们可以开始开发这个应用程序了。

让我们开始吧

首先,我们需要初始化应用程序。为此,我们需要使用终端并切换到要创建应用程序的文件夹(可以使用命令 `\k` cd [directory])。进入该目录后,我们需要在控制台中输入以下命令:

npm init

此命令将打开一个提示框,要求您进行一些应用程序的初始配置,以便生成 package.json 文件:

替代文字

填写完信息并输入“yes”保存后,我们应该能够打开 VS Code 或我们选择的代码编辑器。打开文件夹后,我们需要在编辑器中打开嵌入式终端,然后从那里继续安装依赖项。

安装依赖项

首先我们需要安装应用程序依赖项,这里我们将使用 react、react-dom 和 axios,所以我们需要在终端中输入以下命令:

npm i react react-dom axios

接下来我们应该安装开发依赖项,这些依赖项只是为了能够拥有一个开发环境,以便测试应用程序、添加库、检查错误以及在本地主机环境中运行应用程序。

对于这个应用,我们将使用 webpack 和 babel 来生成 bundle,所以我们将在控制台中运行以下命令,将它们安装为开发依赖项:

npm i @babel/core@^7.12.3 babel-loader@^8.1.0 babel-preset-react-app@^7.0.2 css-loader@^5.0.0 html-webpack-plugin@^4.5.0 style-loader@^2.0.0 webpack@^4.44.2 webpack-cli@^3.3.12 webpack-dev-server@^3.11.0 --save-dev

* In this case I specify the library version to avoid problems when we will start configuring webpack and babel

安装完所有依赖项后,package.json 文件应如下所示:

{
  "name": "pokeapp",
  "version": "1.0.0",
  "description": "demo app",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "author": "PHD",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.20.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.3",
    "babel-loader": "^8.1.0",
    "babel-preset-react-app": "^7.0.2",
    "css-loader": "^5.0.0",
    "html-webpack-plugin": "^4.5.0",
    "style-loader": "^2.0.0",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "babel": {
    "presets": [
      "babel-preset-react-app"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

* We added some babel configuration to transpile the react app correctly.

"babel": {
    "presets": [
      "babel-preset-react-app"
    ]
  }
Enter fullscreen mode Exit fullscreen mode

* Also we add in the scripts section the command a script start the app when we'll finish the first configurations.

"scripts": {
    "start": "webpack-dev-server"
  },
Enter fullscreen mode Exit fullscreen mode

配置 webpack

现在我们已经准备好了依赖项,下一步是设置 webpack,为此我们需要在根文件夹中添加一个 webpack.config.js 文件,这一步只是为了更好地控制构建应用程序时发生的情况。

Webpack 需要一个选项列表来根据该配置生成打包文件,因此我们需要按以下方式导出这些选项:

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
    ... // webpack options goes here
};
Enter fullscreen mode Exit fullscreen mode

要正确配置 webpack,我们需要在 exports 部分设置以下选项:

  1. 模式。此设置用于启用 webpack 内置的、与每个环境(开发或生产)相对应的优化。

    mode: "development"
    
  2. 目标。在此选项中,我们可以选择部署是在服务器上还是在浏览器上,我们还可以进行更多配置,例如设置多个目标,但这超出了本指南的范围。

    target: "web"
    
  3. devtool。通过此选项,我们可以控制是否生成源映射以及使用哪种类型的源映射。源映射使我们能够在浏览器中轻松调试已编译的代码。

    devtool: "cheap-module-source-map"
    
  4. 入口点。此设置允许我们定义应用程序的入口点。

    entry: "./src/index"
    
  5. 输出。此键指示 webpack 应如何以及在哪里输出打包文件和资源。

    output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "pokebundle.js",
    }
    
  6. devServer。在本指南中,我们将使用 devServer 来开发应用程序,此选项允许我们配置此服务器在本地主机上的运行方式。

    devServer: {
    open: true,
    stats: "minimal",
    overlay: true,
    historyApiFallback: true,
    disableHostCheck: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    https: false,
    }
    
  7. 插件。此键用于配置 webpack 插件,这些插件可以帮助我们在打包应用程序时执行额外的操作,在本例中,我们将使用 HtmlWebpackPlugin 将一些 html 文件随应用程序一起打包提供。

    plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    ]
    
  8. 模块。此选项决定 webpack 如何处理应用程序的不同模块。

    module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"],
      },
    ],
    }
    

完整的 webpack.config.js 文件应该如下所示:

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
  mode: "development",
  target: "web",
  devtool: "cheap-module-source-map",
  entry: "./src/index",
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "pokebundle.js",
  },
  devServer: {
    open: true,
    stats: "minimal",
    overlay: true,
    historyApiFallback: true,
    disableHostCheck: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    https: false,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

运行该应用程序

我们将使用简单的文件夹结构,只有一个src主文件夹,里面还有两个子文件夹apicomponents我们将把所有文件都放在这两个子文件夹里:

替代文字

现在是时候开始编写代码了。

我们需要添加的第一个文件是应用程序的主组件,为此,请转到components文件夹并创建一个名为 App.js 的文件,然后将以下代码放入该文件中:

import React from "react";
function App() {
  return (
    <div className="container">
      Pokedex goes here
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

这个 React Hook 返回一个简单的组件,该组件渲染一个包含一些文本的 div 元素。

为了正确渲染此组件,我们需要向应用程序添加入口点。为此,请转到src文件夹并创建 index.js 文件,然后输入以下代码:

import React from "react";
import { render } from "react-dom";
import App from "./components/App";

document.addEventListener("DOMContentLoaded", () => {
  render(<App />, document.getElementById("app"));
});
Enter fullscreen mode Exit fullscreen mode

这段代码用于在 html 页面中渲染应用程序,渲染函数在 DOM 内容加载完毕后查找 id 为“app”的元素,然后尝试在那里渲染我们的组件。

但是我们目前还没有任何 html 页面,所以我们需要在src文件夹中添加一个 html 页面作为应用程序的模板,请创建一个名为 index.html 的文件,并将以下内容放入其中:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Pokedex</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
      integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
      crossorigin="anonymous"
    />
    <script
      src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
      integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
      crossorigin="anonymous"
    ></script>
  </head>

  <body>
    <div id="app"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

注意我们是如何向 html 头部添加一些样式表的,这样做是为了在应用程序中使用Bootstrap 。

现在我们准备运行该应用程序,只需转到控制台并输入npm start此命令,即可打包应用程序并打开默认浏览器以运行该应用程序。

获取宝可梦信息

为了获取宝可梦信息,我们将使用宝可梦 API。该 API 提供了一些端点,我们可以利用这些端点获取应用程序所需的所有信息,但首先我们需要创建一些文件,以便将应用程序连接到任何 REST API。

将以下文件添加到api文件夹 ApiService.js 和 ApiUtils.js 中,然后将以下代码放入 ApiService.js 文件中:

import axios from "axios"; // this library is to fetch data from REST APIs
import { handleError, handleResponse } from "./ApiUtils";

const httpRequest = (method, url, request, headers) => {
  // return a promise
  return axios({
    method,
    url,
    data: request,
    headers,
  })
    .then((res) => {
      const result = handleResponse(res);
      return Promise.resolve(result);
    })
    .catch((err) => {
      return Promise.reject(handleError(err));
    });
};

const get = (url, request, headers) => {
  let queryString = "";
  if (request && Object.keys(request).length > 0) {
    queryString += "?";
    let len = Object.keys(request).length,
      cnt = 0;

    // transform the request object in a query string
    for (let key in request) {
      cnt++;
      queryString += `${key}=${request[key].toString()}`;
      if (len > cnt) queryString += "&";
    }
  }
  return httpRequest("get", `${url}${queryString}`, request, headers);
};

const deleteRequest = (url, request, headers) => {
  return httpRequest("delete", url, request, headers);
};

const post = (url, request, headers) => {
  return httpRequest("post", url, request, headers);
};

const put = (url, request, headers) => {
  return httpRequest("put", url, request, headers);
};

const patch = (url, request, headers) => {
  return httpRequest("patch", url, request, headers);
};

const Api = {
  get,
  delete: deleteRequest,
  post,
  put,
  patch,
};

export default Api;
Enter fullscreen mode Exit fullscreen mode

在这个文件中,我们用来axios执行 REST 请求,我们使用另外两个函数来处理响应handleResponsehandleError这些方法都是从 ApiUtils.js 文件导入的。此外,我们还向该get方法添加了一些逻辑,以便以一致的方式执行任何 REST 请求。在文件的末尾,我们将所有方法导出到一个 Api 对象中。

我们需要在 ApiUtils.js 文件中编写以下代码,以便正确处理服务器响应:

export function handleResponse(response) {
    if (
      response.status === 200 ||
      response.status === 202 ||
      response.statusText === "OK" ||
      response.statusText === "Created"
    )
      return response.data;
    if (response.status === 400) {
      // So, a server-side validation error occurred.
      // Server side validation returns a string error message, so parse as text instead of json.
      const error = response.statusText();
      throw new Error(error);
    }
    throw new Error("Network response was not ok.");
  }

  // In a real app, would likely call an error logging service.
  export function handleError(error) {
    console.error("API call failed. " + error);
    throw error;
  }
Enter fullscreen mode Exit fullscreen mode

现在是时候将应用程序连接到宝可梦 API 了,我们需要在api文件夹内创建一个 PokemonService.js 文件,在这个文件中,我们将添加所有获取宝可梦信息的方法。

首先,我们需要将 API 依赖项导入到服务中:

import ApiService from "./ApiService";
Enter fullscreen mode Exit fullscreen mode

然后我们可以定义我们将要使用的三个异步方法:

  1. getKantoPokemon。此方法将获取所有关都地区宝可梦的列表,有了这个列表,我们就可以获取所有宝可梦的更多数据。

    export const getKantoPokemon = async () => {
    try {
    let response = await ApiService.get(`https://pokeapi.co/api/v2/pokemon`, {
      limit: 151,
    });
    return response.results;
    } catch (err) {
    throw err;
    }
    };
    
  2. getPokemonData 方法用于获取宝可梦的详细信息,此方法需要一个 URL 来获取宝可梦信息。

    export const getPokemonData = async (url) => {
    try {
    let response = await ApiService.get(url);
    return response;
    } catch (err) {
    throw err;
    }
    };
    
  3. getPokemonKantoData。此方法使用前两个方法,第一个方法用于获取所有关都地区的宝可梦,第二个方法用于获取第一个调用响应中所有宝可梦的详细信息。

    export const getPokemonKantoData = async () => {
    try {
    //get pokemon list
    let pokemons = await getKantoPokemon();
    
    //get promises to obtain data for all pokemon in the list
    let pokemonPromises = pokemons.map((p) => getPokemonData(p.url));
    
    //return all the pokemon data
    return await Promise.all(pokemonPromises);
    } catch (err) {
    throw err;
    }
    };
    

该文件的完整代码如下:

import ApiService from "./ApiService";

export const getKantoPokemon = async () => {
  try {
    let response = await ApiService.get(`https://pokeapi.co/api/v2/pokemon`, {
      limit: 151,
    });
    return response.results;
  } catch (err) {
    throw err;
  }
};

export const getPokemonData = async (url) => {
  try {
    let response = await ApiService.get(url);
    return response;
  } catch (err) {
    throw err;
  }
};

export const getPokemonKantoData = async () => {
  try {
    //get pokemon list
    let pokemons = await getKantoPokemon();

    //get promises to obtain data for all pokemon in the list
    let pokemonPromises = pokemons.map((p) => getPokemonData(p.url));

    //return all the pokemon data
    return await Promise.all(pokemonPromises);
  } catch (err) {
    throw err;
  }
};
Enter fullscreen mode Exit fullscreen mode

创建宝可梦图鉴组件

我们将使用三个组件,需要home在其中创建文件夹components,然后继续创建以下文件:

  1. HomeContainer.js 这个组件将作为我们的容器。

  2. PokemonList.js 这个组件将显示所有宝可梦的列表。

  3. 在这个组件中,PokemonDetail.js 将在用户点击列表中的某个元素后显示宝可梦的详细信息。

此外,我们还需要添加一些 CSS 样式,因此为了在一个文件中处理这些样式,我们需要pokemon.css在该文件夹中创建该文件src

PokemonList 组件

在这个功能组件中,我们需要接收宝可梦列表和选定的宝可梦作为属性,第一个属性用于以友好的方式显示所有宝可梦,第二个属性用于突出显示选定的宝可梦。

首先,我们需要导入我们将要使用的模块:

import React from "react";
import "../../pokemon.css";
Enter fullscreen mode Exit fullscreen mode

接下来我们需要创建功能组件:

function PokemonList({ pokemons, selectPokemon }) {
    ... // draw Pokemon function goes here
    ... // return goes here
};
Enter fullscreen mode Exit fullscreen mode

如果pokemons数组属性包含记录,我们将为<li>数组中的每个对象返回一个项,在这个标签中,我们可以正确渲染这些项,以友好的方式显示它们:

  const drawPokemon = () => {
    return pokemons.map((p, id) => (
      <li
        key={id}
        onClick={() => selectPokemon(p.id)}
        className={
          p.selected
            ? "list-group-item d-flex pokemon-item-list selected"
            : "list-group-item d-flex pokemon-item-list"
        }
      >
        <img className="col-3" src={p.sprites.front_default} />
        <p className="col-4 pokemon-text-list">N.º {p.id}</p>
        <p className="col-5 pokemon-text-list">{p.name}</p>
      </li>
    ));
  };
Enter fullscreen mode Exit fullscreen mode

在组件的返回值中,我们需要检查pokemonsprop 的长度是否大于 0,因为我们将从服务器获取数据,而当组件渲染到屏幕上时,此 prop 将没有数据:

return <ul className="list-group">{pokemons.length > 0 && drawPokemon()}</ul>;
Enter fullscreen mode Exit fullscreen mode

最后,别忘了导出组件才能使用它:

export default PokemonList;
Enter fullscreen mode Exit fullscreen mode

完整的文件组件应如下所示:

import React from "react";
import "../../pokemon.css";

function PokemonList({ pokemons, selectPokemon }) {
  const drawPokemon = () => {
    return pokemons.map((p, id) => (
      <li
        key={id}
        onClick={() => selectPokemon(p.id)}
        className={
          p.selected
            ? "list-group-item d-flex pokemon-item-list selected" // the selected class is to highlight the Pokemon selected
            : "list-group-item d-flex pokemon-item-list"
        }
      >
        <img className="col-3" src={p.sprites.front_default} />
        <p className="col-4 pokemon-text-list">N.º {p.id}</p>
        <p className="col-5 pokemon-text-list">{p.name}</p>
      </li>
    ));
  };

  return <ul className="list-group">{pokemons.length > 0 && drawPokemon()}</ul>;
}

export default PokemonList;
Enter fullscreen mode Exit fullscreen mode

宝可梦细节组件

此功能组件将渲染所选宝可梦的详细信息,包括名称、图片、宝可梦类型等。

首先我们需要导入将要使用的库:

import React from "react";
Enter fullscreen mode Exit fullscreen mode

接下来我们需要创建组件主体:

function PokemonDetail({ pokemon }) {
    ... // getTypeStyleFunction goes here
    ... // return goes here
}
Enter fullscreen mode Exit fullscreen mode

在这个组件中,我们使用 getTypeStyle 函数,该函数用于获取一些依赖于宝可梦类型的 CSS 样式:

const getTypeStyle = (type) => {
    let backgroundColor = "";
    switch (type) {
      case "grass":
        backgroundColor = "#9bcc50";
        break;
      case "poison":
        backgroundColor = "#b97fc9";
        break;
      case "fire":
        backgroundColor = "#fd7d24";
        break;
      case "flying":
        backgroundColor = "#3dc7ef";
        break;
      case "water":
        backgroundColor = "#4592c4";
        break;
      case "bug":
        backgroundColor = "#729f3f";
        break;
      case "normal":
        backgroundColor = "#a4acaf";
        break;
      case "electric":
        backgroundColor = "#eed535";
        break;
      case "ground":
        backgroundColor = "#ab9842";
        break;
      case "fairy":
        backgroundColor = "#fdb9e9";
        break;
      case "fighting":
        backgroundColor = "#d56723";
        break;
      case "psychic":
        backgroundColor = "#f366b9";
        break;
      case "rock":
        backgroundColor = "#a38c21";
        break;
      case "steel":
        backgroundColor = "#9eb7b8";
        break;
      case "ghost":
        backgroundColor = "#7b62a3";
        break;
      case "ice":
        backgroundColor = "#51c4e7";
      case "dragon":
        backgroundColor = "#f16e57";

      default:
        backgroundColor = "#000";
        break;
    }
    return { backgroundColor, color: "#FFF", margin: "5px" };
  };
Enter fullscreen mode Exit fullscreen mode

然后,在返回结果中,我们渲染一些 html 代码,以友好的方式显示所选的宝可梦:

return (
    <div className="pokemon-image-container">
      <h1 className="text-center">
        N.º {pokemon.id} {pokemon.name}
      </h1>
      <img
        src={`https://pokeres.bastionbot.org/images/pokemon/${pokemon.id}.png`}
        className="img-fluid pokemon-image-detail d-block mx-auto"
      />
      <div className="pokemon-box-details">
        <ul className="list-group list-group-horizontal justify-content-center">
          {pokemon.types.length > 0 &&
            pokemon.types.map((t, idx) => (
              <li
                key={idx}
                className="list-group-item d-flex pokemon-list-details"
                style={getTypeStyle(t.type.name)}
              >
                {t.type.name}
              </li>
            ))}
        </ul>
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

最后别忘了导出组件:

export default PokemonDetail;
Enter fullscreen mode Exit fullscreen mode

完整的文件组件应如下所示:

import React from "react";

function PokemonDetail({ pokemon }) {
  const getTypeStyle = (type) => {
    let backgroundColor = "";
    switch (type) {
      case "grass":
        backgroundColor = "#9bcc50";
        break;
      case "poison":
        backgroundColor = "#b97fc9";
        break;
      case "fire":
        backgroundColor = "#fd7d24";
        break;
      case "flying":
        backgroundColor = "#3dc7ef";
        break;
      case "water":
        backgroundColor = "#4592c4";
        break;
      case "bug":
        backgroundColor = "#729f3f";
        break;
      case "normal":
        backgroundColor = "#a4acaf";
        break;
      case "electric":
        backgroundColor = "#eed535";
        break;
      case "ground":
        backgroundColor = "#ab9842";
        break;
      case "fairy":
        backgroundColor = "#fdb9e9";
        break;
      case "fighting":
        backgroundColor = "#d56723";
        break;
      case "psychic":
        backgroundColor = "#f366b9";
        break;
      case "rock":
        backgroundColor = "#a38c21";
        break;
      case "steel":
        backgroundColor = "#9eb7b8";
        break;
      case "ghost":
        backgroundColor = "#7b62a3";
        break;
      case "ice":
        backgroundColor = "#51c4e7";
      case "dragon":
        backgroundColor = "#f16e57";

      default:
        backgroundColor = "#000";
        break;
    }
    return { backgroundColor, color: "#FFF", margin: "5px" };
  };

  return (
    <div className="pokemon-image-container">
      <h1 className="text-center">
        N.º {pokemon.id} {pokemon.name}
      </h1>
      <img
        src={`https://pokeres.bastionbot.org/images/pokemon/${pokemon.id}.png`}
        className="img-fluid pokemon-image-detail d-block mx-auto"
      />
      <div className="pokemon-box-details">
        <ul className="list-group list-group-horizontal justify-content-center">
          {pokemon.types.length > 0 &&
            pokemon.types.map((t, idx) => (
              <li
                key={idx}
                className="list-group-item d-flex pokemon-list-details"
                style={getTypeStyle(t.type.name)}
              >
                {t.type.name}
              </li>
            ))}
        </ul>
      </div>
    </div>
  );
}

export default PokemonDetail;
Enter fullscreen mode Exit fullscreen mode

HomeContainer 组件

这个函数组件充当容器,因此在这个组件中,我们将导入其他两个组件,我们将访问 API,我们还将使用一些钩子,例如 useEffect 在屏幕加载时获取宝可梦列表,useState 来处理组件的状态,并将该状态作为 props 传递给子组件。

首先,我们需要导入将要使用的库和组件:

import React, { useEffect, useState } from "react";
import PokemonList from "./PokemonList";
import PokemonDetail from "./PokemonDetail";
import { getPokemonKantoData } from "../../api/PokemonService";
Enter fullscreen mode Exit fullscreen mode

接下来我们需要创建组件主体:

function HomeContainer() {
    ...// state declarations goes here
    ...// use effect goes here
    ...// functions goes here
    ...// return goes here
}
Enter fullscreen mode Exit fullscreen mode

我们需要用到的州如下

  • pokeList。用于处理完整的宝可梦列表。
  • filteredPokeList。用于处理已筛选的宝可梦列表。
  • 筛选器。用于设置要筛选的宝可梦。
  • pokemonSelected。用于处理选定的宝可梦。
  const [pokeList, setPokeList] = useState([]);
  const [filteredPokeList, setFilteredPokeList] = useState([]);
  const [filter, setFilter] = useState("");
  const [pokemonSelected, setPokemonSelected] = useState(null);
Enter fullscreen mode Exit fullscreen mode

然后我们需要在应用加载时获取宝可梦列表,为此我们需要使用 useEffect 钩子来调用获取信息的 API:

  useEffect(async () => {
    try {
      let pokemons = await getPokemonKantoData();
      setFilteredPokeList(pokemons);
      setPokeList(pokemons);
    } catch (err) {
      alert("an error occurs");
      console.error(err);
    }
  }, []);
Enter fullscreen mode Exit fullscreen mode

filteredPokeList为了实现过滤功能,我们可以使用一个函数,根据接收到的值来设置状态:

  const filterPokemon = (value) => {
    setFilter(value); // set the filter value
    setFilteredPokeList(
      pokeList.filter((p) => p.name.toLowerCase().includes(value.toLowerCase()))
    ); // set the pokemons that match with the value
  };
Enter fullscreen mode Exit fullscreen mode

为了突出显示选中的宝可梦,并显示宝可梦的详细信息,我们需要创建一个设置pokemonSelected状态的函数:

  const handleSelect = (pokemonId) => {
    setPokemonSelected(pokeList.filter((p) => p.id === pokemonId)[0]); // set the selected Pokemon to display the details
    setFilteredPokeList(
      filteredPokeList.map((p) =>
        p.id === pokemonId
          ? { ...p, selected: true }
          : { ...p, selected: false }
      )
    ); // filter the list of pokemons to display
  };
Enter fullscreen mode Exit fullscreen mode

最后,我们需要返回容器结构以显示应用程序:

return (
    <div className="row pokemon-app-container">
      <div className="col-6">
        {pokemonSelected && <PokemonDetail pokemon={pokemonSelected} />}
      </div>
      <div className="col-6 pokemon-list-container">
        <div style={{ height: "10%" }}>
          <div className="form-group">
            <label>Search</label>
            <input
              type="text"
              className="form-control"
              placeholder="Type to search a pokemon..."
              value={filter}
              onChange={(event) => {
                let { value } = event.target;
                filterPokemon(value);
              }}
            />
          </div>
        </div>
        <div style={{ height: "90%", overflowY: "auto" }}>
          <PokemonList
            pokemons={filteredPokeList}
            selectPokemon={handleSelect}
          />
        </div>
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

最后导出组件以便能够使用它:

export default HomeContainer;
Enter fullscreen mode Exit fullscreen mode

该组件的完整代码应如下所示:

import React, { useEffect, useState } from "react";
import PokemonList from "./PokemonList";
import PokemonDetail from "./PokemonDetail";
import { getPokemonKantoData } from "../../api/PokemonService";

function HomeContainer() {
  useEffect(async () => {
    try {
      let pokemons = await getPokemonKantoData();
      console.log(pokemons);
      setFilteredPokeList(pokemons);
      setPokeList(pokemons);
    } catch (err) {
      alert("an error occurs");
      console.error(err);
    }
  }, []);

  const [pokeList, setPokeList] = useState([]);
  const [filteredPokeList, setFilteredPokeList] = useState([]);
  const [pokemonSelected, setPokemonSelected] = useState(null);
  const [filter, setFilter] = useState("");

  const handleSelect = (pokemonId) => {
    setPokemonSelected(pokeList.filter((p) => p.id === pokemonId)[0]);
    setFilteredPokeList(
      filteredPokeList.map((p) =>
        p.id === pokemonId
          ? { ...p, selected: true }
          : { ...p, selected: false }
      )
    );
  };

  const filterPokemon = (value) => {
    setFilter(value);
    setFilteredPokeList(
      pokeList.filter((p) => p.name.toLowerCase().includes(value.toLowerCase()))
    );
  };
  return (
    <div className="row pokemon-app-container">
      <div className="col-6">
        {pokemonSelected && <PokemonDetail pokemon={pokemonSelected} />}
      </div>
      <div className="col-6 pokemon-list-container">
        <div style={{ height: "10%" }}>
          <div className="form-group">
            <label>Search</label>
            <input
              type="text"
              className="form-control"
              placeholder="Type to search a pokemon..."
              value={filter}
              onChange={(event) => {
                let { value } = event.target;
                filterPokemon(value);
              }}
            />
          </div>
        </div>
        <div style={{ height: "90%", overflowY: "auto" }}>
          <PokemonList
            pokemons={filteredPokeList}
            selectPokemon={handleSelect}
          />
        </div>
      </div>
    </div>
  );
}

export default HomeContainer;
Enter fullscreen mode Exit fullscreen mode

宝可梦 CSS 样式表

我不想深入探讨 CSS,因为我认为这超出了本指南的范围,所以我只在这里添加样式表:

.pokemon-item-list {
  border-radius: 40px !important;
  margin-top: 10px;
  margin-bottom: 10px;
  border-width: 0px;
}

.pokemon-item-list.selected {
  background-color: #e3350d;
  color: white;
  border-width: 1px;
}

.pokemon-item-list:hover {
  border-width: 1px;
  background-color: #E2E2E2;
  color: white;
}

.pokemon-text-list {
  font-size: 24px;
  margin-top: 20px;
}

.pokemon-app-container {
  height: 100vh;
}

.pokemon-list-container {
  height: 100%;
  overflow-y: auto;
}

.pokemon-image-container {
  margin-top: 4rem;
  border: 1px solid #F2F2F2;
  background-color: #F2F2F2;
  border-radius: 20px;
  padding: 10px;
}

.pokemon-image-detail {
  height: 400px;
}

.pokemon-list-details {
  margin-top: 20px;
  border-width: 0px;
}

.pokemon-box-details {
  margin-top: 10px;
}
Enter fullscreen mode Exit fullscreen mode

结束应用

最后,我们需要更新App.js此文件以加载我们创建的组件:

import React from "react";
import Home from "./Home/HomeContainer"; // import the container component

// return the Home component
function App() {
  return (
    <div className="container">
      <Home /> 
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

这样一来,应用程序就应该完成了,但是我们还可以改进其中的许多部分,例如添加属性类型、使用 Redux、重构部分代码、优化打包等等。

您可以在以下代码库PokeApp中获取完整代码。

如果您读到这里,我想对您说声非常感谢,如果您有任何意见或建议,我将不胜感激。

文章来源:https://dev.to/pes/create-pokedex-with-react-hooks-46n1