重新探索歌词查找器的开发实现
1. 前端部分
1.1 用户界面搭建
为了创建 LyricsFinder V2 的用户界面,我们将安装并使用 React Bootstrap(https://react-bootstrap.github.io),这是一个基于广受欢迎的 Bootstrap(https://getbootstrap.com)CSS 库构建的 React UI 组件库。该库提供了近 30 个组件,足以构建新的用户界面。
当然,和 Angular 以及 Vue.js 一样,React 也有许多其他的 UI 工具包可供选择。这里我们选择了一个与之前不同的流行库,让你了解另一种界面组装方式。以下是一些其他的 React UI 工具包,你可以参考:
- https://dev.to/kayis/react-ui-kits-3fm2
- https://www.codeinwp.com/blog/react-ui-component-libraries-frameworks
- https://hackernoon.com/23-best-react-ui-component-libraries-and-frameworks-250a81b2ac42
为了处理页面之间的导航,我们将使用 React Router(https://reacttraining.com/react-router),这是一个流行的 React 开源路由器。后续会介绍如何安装和使用这些工具。
相关参考链接:
- React Bootstrap: https://react-bootstrap.github.io
- Bootstrap: https://getbootstrap.com
1.2 API 调用
前端应用将使用 Apollo GraphQL 客户端结合 React Apollo(https://github.com/apollographql/react-apollo)和 Apollo Boost(https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost)来获取艺术家、歌曲和歌词的信息。简单来说,我们会使用 GraphQL 客户端发送 GraphQL 查询并获取所需数据,后续章节会详细介绍。
2. 后端部分
2.1 后端技术栈
为了实现后端应用,我们将结合以下技术:
- 平台/运行时:Node.js
- 框架:NestJS(https://nestjs.com)
- 查询语言/API:GraphQL 结合 Apollo GraphQL
利用这些技术,我们将创建一个后端系统,通过 GraphQL API 公开艺术家、歌曲和歌词的信息。创建的 GraphQL API 实际上会使用之前版本应用中开发的 MusicService 从 MusixMatch API 获取数据,因此它类似于一个 API 网关层。
相关参考链接:
- GraphQL: https://graphql.org/learn
- NestJS: https://nestjs.com
- Node.js: https://nodejs.org/en
- Apollo: https://www.apollographql.com
- GraphQL 插件 for NestJS: https://github.com/nestjs/graphql
2.2 TypeScript 的使用
我们将在前端和后端都使用 TypeScript。TypeScript 不仅仅局限于前端应用开发,它是一种可以在技术栈的各个层面使用的编程语言。
3. 项目搭建
3.1 创建项目
我们准备了项目的初始骨架,可将其复制到工作区。骨架的目的是让我们专注于新元素,而不是重复之前的代码集成工作。
前端文件夹中是使用 create-react-app 并启用 TypeScript 创建的 React 应用,后端文件夹中是使用 NestJS CLI 创建的 NestJS 应用,并且已经包含了之前的 MusicService 类和相关代码。
由于 React 默认使用 yarn,本章也将使用它。你也可以使用 npm(如使用 npm ci 命令),但要注意可能需要一些调整,因为初始项目没有包含锁文件,可能会导致依赖版本不同,出现不可预测的行为。
如果你还没有安装 yarn,可以使用以下方法:
- 最简单的方法是利用已安装的 npm 全局安装 yarn:
npm install --global yarn
- 也可以参考 https://yarnpkg.com/lang/en/docs/install 中的其他方法。
如果你已经安装了 yarn,可以进行以下操作:
1. 将项目骨架文件夹复制到工作区。
2. 在前端和后端文件夹中分别使用
yarn install
安装项目依赖。
3.2 项目结构和约定
项目顶层有两个主要文件夹:backend 和 frontend,用于清晰分离前后端应用。
在实际项目中,可能会使用 Nrwl NX(https://github.com/nrwl/nx)来管理整个工作区,它可以方便地共享代码和优化构建,但这里暂不涉及。
前后端项目的源代码都放在 src 文件夹中,文件名使用小写连字符格式,React 组件也如此,除了 create-react-app 创建的默认文件,你也可以重命名以保持结构统一。
前端 React 应用的组件放在 src/components 文件夹中,这里只放无状态组件;对于有状态组件(即页面),将创建一个 pages 文件夹。
4. 后端应用创建与配置
4.1 后端项目创建
后端文件夹已经有一个起始点,我们使用 NestJS CLI 创建初始后端项目:
npx @nestjs/cli new backend
。为了方便构建后端,之前使用的 MusicService 类已复制到 backend/src/musixmatch 目录,并进行了一些 NestJS 适配。
4.2 启动开发服务器
此应用需要同时运行两个独立的服务器:
- 后端 Node/NestJS 应用服务器,用于公开 GraphQL API。
- 前端 React 应用服务器,用于消费 GraphQL API。
启动命令如下:
1. 在后端文件夹中使用
yarn start:dev
启动后端服务器。
2. 在前端文件夹中使用
yarn start
启动前端服务器。
注意,一开始后端应用可能无法正常工作,后续会解决这个问题。之后需要保持两个终端窗口打开,前端开始向后端发送请求时,两个服务器必须并行启动,应用才能正常工作。通常,前端服务器监听 3000 端口,后端服务器监听 4000 端口。
4.3 添加必要依赖
由于要使用 GraphQL,需要向后端添加一些依赖:
1. 进入后端文件夹。
2. 执行命令:
yarn add @nestjs/graphql apollo-server-express graphql-tools graphql type-graphql
。
这些包将用于实现 GraphQL API,同时我们安装了 NestJS 的 GraphQL 插件,方便开发。
4.4 实现 GraphQL API 与首个解析器
- 添加 MusixMatch API 密钥 :在开始编写代码前,需要将 MusixMatch API 密钥添加到 backend/src/api-key.ts 文件中,否则代码无法从 MusixMatch 获取数据。如果还没有密钥,需要获取一个。这是应用正常运行的必要步骤。
-
创建歌曲模块
:在终端进入后端文件夹,使用 NestJS CLI 执行命令:
npx @nestjs/cli generate module song,这将在项目中创建歌曲的 NestJS 模块,可用于清晰分离应用的不同部分。 -
创建歌曲 GraphQL 解析器
:使用 GraphQL 插件提供的命令:
npx @nestjs/cli g resolver song,该命令会生成一个空的 GraphQL 解析器,并将其注册到模块中。 - 创建歌曲 DTO 并为 GraphQL 装饰 :创建一个 song.dto.ts 文件,路径为 src/song,内容如下:
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class SongDto {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
artistId: string;
@Field(() => Boolean)
hasLyrics: boolean;
@Field(() => [String])
genres: string[];
}
这个类定义了歌曲的基本信息,使用
@ObjectType()
装饰器将其配置为 GraphQL 可识别的类型,每个字段使用
@Field
装饰器让 GraphQL 考虑这些字段。
- 实现歌曲解析器并利用 NestJS 特性 :创建并打开 backend/src/song/song.resolver.ts 文件,内容如下:
import { Args, Query, Resolver } from '@nestjs/graphql';
import { MusicService } from '../musixmatch/services';
import { Inject } from '@nestjs/common';
import { TYPES } from '../musixmatch/ioc/types';
import { SongDto } from './song.dto';
import { Observable } from 'rxjs';
@Resolver('Song')
export class SongResolver {
constructor(
@Inject(TYPES.MUSIC_SERVICE)
private readonly musicService: MusicService,
) {}
@Query(() => [SongDto])
songs(@Args('value') value: string): Observable<SongDto[]> {
return this.musicService.findSongs(value);
}
}
这里使用
@Inject()
装饰器注入 MusicService,
@Resolver
装饰器表明这是一个解析器,
@Query
描述了解析器解析的对象类型,
@Args
关联查询参数和解析器函数参数。解析器接收查询的搜索文本,传递给 MusicService 从 MusixMatch API 获取结果,整个过程是异步的。
4.5 导入和配置 GraphQL NestJS 模块
打开 backend/src/app.module.ts 文件,内容如下:
@Module({
imports: [
GraphQLModule.forRoot({
debug: true,
playground: true,
autoSchemaFile: 'schema.gql',
}),
// ...
],
// ...
})
export class AppModule {}
这里加载并配置了 GraphQLModule,启用了调试模式和 playground,方便在浏览器中调试和测试 GraphQL API。选择了 Code First 方法,通过
autoSchemaFile: 'schema.gql'
选项让 NestJS GraphQL 插件根据 DTO 和解析器中的装饰器自动生成 GraphQL 模式文件。
4.6 使用 GraphQL playground 测试 API
当 SongModule 正确定义且 GraphQLModule 加载到 NestJS 应用的 app 模块后,就可以测试 API 了。
1. 执行
yarn start:dev
启动后端服务器。
2. 打开浏览器,访问 http://localhost:4000/graphql,会看到之前启用的 playground。
3. 在左侧面板清空内容,输入以下查询:
{
songs(value: "never gonna give you up") {
id,
name,
hasLyrics
}
}
这个查询请求匹配歌曲标题的歌曲的 id、name 和 hasLyrics 属性。在 playground 中可以使用 Ctrl + 空格键(macOS 为 cmd + 空格键)进行自动补全。执行查询后,右侧会显示结果,证明 API 正常工作。
5. 后端添加艺术家和歌词支持
为了让 GraphQL API 支持查找艺术家和加载歌曲歌词,需要创建相应的模块和 GraphQL 解析器。
在后端文件夹中执行以下命令:
npx @nestjs/cli g mo artist
npx @nestjs/cli g r artist
npx @nestjs/cli g mo song-lyrics
npx @nestjs/cli g r song-lyrics
注意,
mo
是
module
的简写,
r
是
resolver
的简写。
接下来,需要为艺术家和歌曲歌词创建 DTO。
艺术家 DTO 文件 backend/src/artist/artist.dto.ts 内容如下:
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class ArtistDto {
@Field(() => ID)
id: string;
@Field()
name: string;
}
对应的解析器文件 backend/src/artist/artist.resolver.ts 内容如下:
import { Query, Resolver, Args } from '@nestjs/graphql';
import { TYPES } from '../musixmatch/ioc/types';
import { MusicService } from '../musixmatch/services';
import { ArtistDto } from './artist.dto';
import { Inject } from '@nestjs/common';
@Resolver('Artist')
export class ArtistResolver {
constructor(
@Inject(TYPES.MUSIC_SERVICE)
private readonly musicService: MusicService,
) {}
@Query(() => [ArtistDto])
async artists(@Args('name') name: string): Promise<ArtistDto[]> {
return this.musicService.findArtists(name).toPromise();
}
}
这里同样注入并使用 MusicService,使用了异步函数并返回 Promise。
歌曲歌词 DTO 文件 src/song-lyrics/song-lyrics.dto.ts 内容如下:
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class SongLyricsDto {
@Field(() => ID)
id: string;
@Field()
copyright: string;
@Field(() => Boolean)
explicit: boolean;
@Field()
// 原文档此处未完整,可根据实际情况补充
}
以下是整个开发流程的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(创建项目):::process
B --> C(配置前后端环境):::process
C --> D(后端添加依赖):::process
D --> E(创建后端模块和解析器):::process
E --> F(配置 GraphQL 模块):::process
F --> G(测试后端 API):::process
G --> H(前端搭建界面和配置路由):::process
H --> I(前端配置 API 调用):::process
I --> J([完成]):::startend
通过以上步骤,我们逐步完成了 LyricsFinder V2 的前后端开发,从项目搭建到后端 API 实现和测试,再到前端界面和 API 调用的配置,整个过程涵盖了多个技术点和工具的使用,希望能帮助你更好地理解和实践相关开发。
重新探索歌词查找器的开发实现
6. 前端开发与后端集成
6.1 前端界面开发
前端使用 React 结合 React Bootstrap 进行界面开发。首先,确保已经安装了所需的依赖:
yarn add react-bootstrap bootstrap
在
index.tsx
中引入 Bootstrap 的 CSS:
import 'bootstrap/dist/css/bootstrap.min.css';
接下来可以开始创建组件,例如创建一个搜索框组件
SearchBox.tsx
:
import React from 'react';
import { Form, InputGroup, Button } from 'react-bootstrap';
const SearchBox: React.FC = () => {
return (
<InputGroup className="mb-3">
<Form.Control
placeholder="搜索歌曲、艺术家"
aria-label="搜索歌曲、艺术家"
aria-describedby="basic-addon2"
/>
<Button variant="outline-secondary" id="button-addon2">
搜索
</Button>
</InputGroup>
);
};
export default SearchBox;
在
App.tsx
中使用该组件:
import React from 'react';
import SearchBox from './SearchBox';
const App: React.FC = () => {
return (
<div className="container">
<h1>歌词查找器</h1>
<SearchBox />
</div>
);
};
export default App;
6.2 前端配置 Apollo GraphQL 客户端
为了让前端能够与后端的 GraphQL API 进行交互,需要配置 Apollo GraphQL 客户端。安装所需依赖:
yarn add @apollo/client graphql
在
src
目录下创建
apolloClient.ts
文件:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
在
index.tsx
中使用该客户端:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ApolloProvider } from '@apollo/client';
import client from './apolloClient';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
6.3 前端发起 GraphQL 查询
在
SearchBox.tsx
中添加查询逻辑,当用户点击搜索按钮时发起查询。首先,定义查询:
import { gql } from '@apollo/client';
const SEARCH_SONGS = gql`
query SearchSongs($value: String!) {
songs(value: $value) {
id
name
hasLyrics
}
}
`;
然后修改
SearchBox.tsx
组件:
import React, { useState } from 'react';
import { Form, InputGroup, Button } from 'react-bootstrap';
import { useQuery } from '@apollo/client';
import { SEARCH_SONGS } from './queries';
const SearchBox: React.FC = () => {
const [searchValue, setSearchValue] = useState('');
const { loading, error, data } = useQuery(SEARCH_SONGS, {
variables: { value: searchValue },
skip: searchValue === '',
});
const handleSearch = () => {
// 这里可以添加更多逻辑,例如验证输入等
console.log('搜索值:', searchValue);
};
return (
<div>
<InputGroup className="mb-3">
<Form.Control
placeholder="搜索歌曲、艺术家"
aria-label="搜索歌曲、艺术家"
aria-describedby="basic-addon2"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
<Button variant="outline-secondary" id="button-addon2" onClick={handleSearch}>
搜索
</Button>
</InputGroup>
{loading && <p>加载中...</p>}
{error && <p>错误: {error.message}</p>}
{data && (
<ul>
{data.songs.map((song: any) => (
<li key={song.id}>
{song.name} - {song.hasLyrics ? '有歌词' : '无歌词'}
</li>
))}
</ul>
)}
</div>
);
};
export default SearchBox;
7. 优化与扩展
7.1 性能优化
-
缓存策略
:在 Apollo 客户端中,可以配置缓存策略,避免重复请求相同的数据。例如,在
apolloClient.ts中可以对缓存进行更精细的配置:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
songs: {
keyArgs: ['value'],
merge(existing, incoming) {
return incoming;
},
},
},
},
},
});
const client = new ApolloClient({
link: httpLink,
cache,
});
export default client;
- 代码分割 :使用 React.lazy 和 Suspense 进行代码分割,减少初始加载时间。例如,将一些组件进行懒加载:
const SearchBox = React.lazy(() => import('./SearchBox'));
const App: React.FC = () => {
return (
<div className="container">
<h1>歌词查找器</h1>
<React.Suspense fallback={<div>加载中...</div>}>
<SearchBox />
</React.Suspense>
</div>
);
};
7.2 功能扩展
- 添加分页功能 :在后端的 GraphQL API 中添加分页支持,前端根据分页信息展示不同页面的数据。
- 支持更多查询类型 :例如按艺术家、流派等进行查询,在后端添加相应的解析器和查询逻辑,前端添加对应的查询和界面元素。
以下是前端开发和优化的主要步骤表格:
| 步骤 | 操作 | 命令或代码 |
| ---- | ---- | ---- |
| 安装 React Bootstrap | 安装依赖并引入 CSS |
yarn add react-bootstrap bootstrap
,在
index.tsx
引入 CSS |
| 创建搜索框组件 | 创建
SearchBox.tsx
并在
App.tsx
使用 | 见
SearchBox.tsx
和
App.tsx
代码 |
| 配置 Apollo 客户端 | 安装依赖并创建
apolloClient.ts
|
yarn add @apollo/client graphql
,见
apolloClient.ts
代码 |
| 发起 GraphQL 查询 | 在
SearchBox.tsx
定义查询并添加查询逻辑 | 见
SearchBox.tsx
代码 |
| 性能优化 | 配置缓存策略、代码分割 | 见缓存配置和代码分割代码 |
| 功能扩展 | 添加分页、更多查询类型 | 后端添加解析器,前端添加查询和界面元素 |
以下是前端开发和优化的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始前端开发]):::startend --> B(安装 React Bootstrap):::process
B --> C(创建搜索框组件):::process
C --> D(配置 Apollo 客户端):::process
D --> E(发起 GraphQL 查询):::process
E --> F{是否需要优化?}:::decision
F -->|是| G(性能优化):::process
F -->|否| H{是否需要扩展功能?}:::decision
G --> H
H -->|是| I(功能扩展):::process
H -->|否| J([完成前端开发]):::startend
I --> J
通过以上前端开发、与后端集成以及优化扩展的步骤,我们完成了一个完整的歌词查找器应用的开发。这个应用可以让用户搜索歌曲、艺术家信息,并根据需要进行性能优化和功能扩展。
超级会员免费看
16

被折叠的 条评论
为什么被折叠?



