歌词查找器开发全流程:从后端到前端的实现
在开发应用程序时,我们通常会从后端开始构建,然后逐步过渡到前端。本篇博客将详细介绍歌词查找器(LyricsFinder)应用的后端和前端开发过程,包括后端的 GraphQL 解析器实现、前端的 React 框架搭建、路由设置、组件开发以及与后端 API 的集成。
后端实现
后端的核心是实现一个 GraphQL 解析器,用于处理歌词查询请求。以下是具体的代码和步骤:
-
定义解析器文件
:解析器文件位于
src/song-lyrics/song-lyrics.resolver.ts,代码如下:
import { Query, Resolver, Args } from '@nestjs/graphql';
import { SongLyricsDto } from './song-lyrics.dto';
import { MusicService } from '../musixmatch/services';
import { Inject } from '@nestjs/common';
import { TYPES } from '../musixmatch/ioc/types';
@Resolver('SongLyrics')
export class SongLyricsResolver {
constructor(
@Inject(TYPES.MUSIC_SERVICE)
private readonly musicService: MusicService,
) {}
@Query(() => SongLyricsDto)
songLyrics(@Args('id') id: string) {
return this.musicService.findLyrics(id);
}
}
-
功能解释
:该解析器接收一个
id参数,调用MusicService的findLyrics方法来查找歌词。
在切换到前端开发之前,建议在 GraphQL playground 中练习编写 GraphQL 查询,以熟悉相关操作。
前端开发
前端开发主要使用 React 框架,并结合 React Bootstrap 和 React Router 来构建用户界面和实现路由功能。
安装 React Bootstrap
- 安装依赖 :进入前端项目文件夹,执行以下命令:
yarn add react-bootstrap bootstrap
-
加载样式表
:打开
frontend/src/index.tsx文件,在顶部添加以下导入语句:
import 'bootstrap/dist/css/bootstrap.min.css';
-
验证安装
:打开
frontend/src/App.tsx文件,替换内容如下:
import React from 'react';
import './App.css';
import Jumbotron from 'react-bootstrap/Jumbotron';
import Button from 'react-bootstrap/Button';
const App: React.FC = () => {
return (
<Jumbotron>
<h1>LyricsFinder v2</h1>
<p>
This will become the number one source of lyrics on the
Interwebs.
</p>
<p>
<Button variant="primary">Learn more</Button>
</p>
</Jumbotron>
);
};
export default App;
-
清理不必要的文件
:清空
frontend/src/App.css和frontend/src/index.css文件的内容,并删除frontend/src/logo.svg文件。
通过以上步骤,我们成功安装并验证了 React Bootstrap 的使用。
安装 React Router
- 安装依赖 :执行以下命令安装 React Router 和其类型定义:
yarn add react-router-dom @types/react-router-dom
-
添加路由出口
:打开
frontend/src/App.tsx文件,进行如下修改:
import { BrowserRouter } from 'react-router-dom';
const App: React.FC = () => {
return (
<React.Fragment>
<Jumbotron>
...
</Jumbotron>
<BrowserRouter>
...
</BrowserRouter>
</React.Fragment>
);
};
-
创建首页视图
:
-
在
frontend/src下创建pages文件夹。 -
在
pages文件夹中创建home.tsx文件,内容如下:
-
在
import React from 'react';
export const Home = () => {
return <h2>Home</h2>;
};
- 在 `App.tsx` 文件中添加路由配置:
import { BrowserRouter, Route } from 'react-router-dom';
import { Home } from './pages/home';
const App: React.FC = () => {
return (
<React.Fragment>
<Jumbotron>
...
</Jumbotron>
<BrowserRouter>
<Route exact path='/' component={Home} />
</BrowserRouter>
</React.Fragment>
);
};
-
创建歌词视图
:
-
在
frontend/src/pages下创建lyrics.tsx文件,内容如下:
-
在
import React from 'react';
export const Lyrics = () => {
return <h2>Lyrics</h2>;
};
- 在 `App.tsx` 文件中添加路由配置:
import { Lyrics } from './pages/lyrics';
const App: React.FC = () => {
return (
<React.Fragment>
<Jumbotron>
...
</Jumbotron>
<BrowserRouter>
<Route exact path='/' component={Home} />
<Route path='/lyrics' component={Lyrics} />
</BrowserRouter>
</React.Fragment>
);
};
通过以上步骤,我们完成了 React Router 的安装和路由配置。
创建搜索组件
搜索组件是应用的重要组成部分,用于用户输入搜索关键词并触发搜索操作。
-
创建组件文件夹
:在
frontend/src下创建components文件夹。 -
创建搜索组件文件
:在
components文件夹中创建search.tsx文件,初始代码如下:
import React, { useEffect } from 'react';
import Container from 'react-bootstrap/Container';
type Props = {
};
export const Search = (props: Props) => {
useEffect(() => {
console.log('Search component rendered');
});
return (
<Container className='lf-search'>
TODO add content here
</Container>
);
};
-
添加组件到首页
:打开
frontend/src/pages/home.tsx文件,进行如下修改:
import { Search } from '../components/search';
const Home = () => {
return <Search />;
};
export default Home;
-
定义组件层次结构
:在
search.tsx文件中,替换Container组件如下:
import Button from 'react-bootstrap/Button';
import FormGroup from 'react-bootstrap/FormGroup';
import FormControl from 'react-bootstrap/FormControl';
import InputGroup from 'react-bootstrap/InputGroup';
<Container className='lf-search'>
<FormGroup id='searchForm'>
<InputGroup size='lg'>
<FormControl id='searchText' type='text'
placeholder='Artist or song to search for'
aria-label='Artist or song to search for'/>
<InputGroup.Append>
<Button variant='secondary' aria-label='Clear the search text'>X</Button>
<Button variant='primary' aria-label='Search'>Search</Button>
</InputGroup.Append>
</InputGroup>
</FormGroup>
</Container>
-
添加状态和绑定输入
:在
search.tsx文件中,添加状态和事件处理函数:
import React, { useEffect, useState } from 'react';
import { FormEvent } from 'react';
import { FormControlProps } from 'react-bootstrap/FormControl';
const [searchText, updateSearchText] = useState('');
const handleSearchTextInputChange = (event: FormEvent<FormControlProps>): void => {
const searchInputNewValue: string = event.currentTarget.value || "";
updateSearchText(searchInputNewValue);
console.log(`Search: search text changed to [${searchInputNewValue}]`);
};
<FormControl id='searchText' type='text'
placeholder='Artist or song to search for'
aria-label='Artist or song to search for'
value={searchText}
onChange={handleSearchTextInputChange}
/>
-
清空输入
:在
search.tsx文件中,添加清空输入的处理函数:
const clearHandler = () => {
updateSearchText('');
};
<Button variant='secondary' aria-label='Clear the search text' onClick={clearHandler}>X</Button>
-
添加搜索处理函数
:在
search.tsx文件中,添加搜索处理函数:
const searchHandler: VoidFunction = () => {
console.log('Search: search handler called. Search text: ', searchText);
if (searchText === '') {
clearHandler();
}
}
<Button variant='primary' aria-label='Search' onClick={searchHandler}>Search</Button>
<FormControl id='searchText' type='text'
placeholder='Artist or song to search for'
aria-label='Artist or song to search for'
value={searchText}
onChange={handleSearchTextInputChange}
onKeyUp={(e: React.KeyboardEvent) => e.key === 'Enter' ? searchHandler() : null}
/>
-
添加属性与外部通信
:在
search.tsx文件中,修改Props接口并更新处理函数:
type Props = {
searchTriggered: (searchText: string) => void;
searchCleared: VoidFunction;
};
const searchHandler: VoidFunction = () => {
console.log('Search: search handler called. Search text: ', searchText);
if (searchText === '') {
clearHandler();
} else {
props.searchTriggered(searchText);
}
}
const clearHandler = () => {
updateSearchText('');
props.searchCleared();
};
-
在首页使用搜索组件
:打开
frontend/src/pages/home.tsx文件,添加处理函数并传递给搜索组件:
const search = (text: string) => {
console.log(`Home: searching for ${text}`);
};
const searchInputCleared = () => {
console.log('Home: search cleared');
};
<Search searchCleared={searchInputCleared} searchTriggered={search}/>
通过以上步骤,我们完成了搜索组件的开发。
与后端 API 集成
在搜索组件开发完成后,我们需要将 React 应用与后端 GraphQL API 集成,以获取数据。
- 安装依赖 :进入前端项目文件夹,执行以下命令:
yarn add apollo apollo-client apollo-boost react-apollo graphql
- 启动服务器 :从现在开始,需要同时启动后端和前端服务器,以确保应用正常工作。
通过以上步骤,我们完成了歌词查找器应用的后端和前端开发,并实现了前后端的集成。
总结
本文详细介绍了歌词查找器应用的开发过程,包括后端的 GraphQL 解析器实现、前端的 React 框架搭建、路由设置、组件开发以及与后端 API 的集成。通过逐步实现各个功能模块,我们最终完成了一个完整的应用程序。在开发过程中,我们使用了多种技术和工具,如 React Bootstrap、React Router、Apollo GraphQL 等,这些技术和工具为我们的开发提供了便利和支持。希望本文对您的开发工作有所帮助。
歌词查找器开发全流程:从后端到前端的实现
深入理解前后端交互流程
为了更清晰地展示歌词查找器应用前后端的交互过程,我们可以通过一个 mermaid 流程图来呈现:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(用户输入搜索关键词):::process --> B(前端搜索组件处理):::process
B --> C{关键词是否为空?}:::process
C -->|是| D(清空输入并提示):::process
C -->|否| E(触发搜索事件):::process
E --> F(前端调用后端 GraphQL API):::process
F --> G(后端 GraphQL 解析器接收请求):::process
G --> H(后端音乐服务查找歌词):::process
H --> I(后端返回歌词数据):::process
I --> J(前端接收数据并展示):::process
这个流程图展示了从用户输入搜索关键词到最终看到歌词结果的整个过程。当用户在前端输入关键词后,前端组件会进行初步处理,判断关键词是否为空。如果不为空,则会触发搜索事件,调用后端的 GraphQL API。后端的解析器接收到请求后,会调用音乐服务来查找歌词,并将结果返回给前端,最后前端将歌词数据展示给用户。
代码优化与错误处理
在实际开发中,我们还需要对代码进行优化,并添加错误处理机制,以提高应用的健壮性。
后端代码优化
在后端的
SongLyricsResolver
中,我们可以添加错误处理逻辑,当查找歌词失败时,返回合适的错误信息。以下是优化后的代码:
import { Query, Resolver, Args, BadRequestException } from '@nestjs/graphql';
import { SongLyricsDto } from './song-lyrics.dto';
import { MusicService } from '../musixmatch/services';
import { Inject } from '@nestjs/common';
import { TYPES } from '../musixmatch/ioc/types';
@Resolver('SongLyrics')
export class SongLyricsResolver {
constructor(
@Inject(TYPES.MUSIC_SERVICE)
private readonly musicService: MusicService,
) {}
@Query(() => SongLyricsDto)
async songLyrics(@Args('id') id: string) {
try {
const lyrics = await this.musicService.findLyrics(id);
if (!lyrics) {
throw new BadRequestException('Lyrics not found');
}
return lyrics;
} catch (error) {
throw new BadRequestException('Error fetching lyrics', error.message);
}
}
}
在这个优化后的代码中,我们使用了
try...catch
块来捕获可能出现的错误。如果查找歌词失败,会抛出
BadRequestException
并返回相应的错误信息。
前端代码优化与错误处理
在前端与后端 API 集成时,我们也需要处理可能出现的错误。以下是优化后的前端代码示例:
import React, { useEffect, useState } from 'react';
import { useQuery } from 'react-apollo';
import gql from 'graphql-tag';
const GET_LYRICS = gql`
query GetLyrics($id: String!) {
songLyrics(id: $id) {
lyrics
}
}
`;
const LyricsPage = ({ id }) => {
const { loading, error, data } = useQuery(GET_LYRICS, {
variables: { id },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>Lyrics</h2>
<p>{data.songLyrics.lyrics}</p>
</div>
);
};
export default LyricsPage;
在这个代码中,我们使用了
useQuery
钩子来发起 GraphQL 查询。通过检查
loading
和
error
状态,我们可以在加载数据时显示加载提示,在出现错误时显示错误信息。
性能优化建议
为了提高应用的性能,我们可以采取以下措施:
- 缓存机制 :在前端和后端都可以实现缓存机制。前端可以使用浏览器的本地存储或会话存储来缓存已经请求过的歌词数据,避免重复请求。后端可以使用内存缓存(如 Redis)来缓存频繁查询的歌词数据,减少数据库或音乐服务的访问次数。
- 代码分割 :对于前端应用,我们可以使用 React 的代码分割功能,将应用拆分成多个小的代码块,按需加载。这样可以减少初始加载时间,提高用户体验。
- 优化 GraphQL 查询 :在后端,我们可以优化 GraphQL 查询,避免返回不必要的数据。可以使用字段选择器来只返回用户需要的字段,减少数据传输量。
总结与展望
通过本文的介绍,我们详细了解了歌词查找器应用从后端到前端的开发过程,包括后端的 GraphQL 解析器实现、前端的 React 框架搭建、路由设置、组件开发以及与后端 API 的集成。同时,我们还通过流程图深入理解了前后端的交互过程,并对代码进行了优化和错误处理,提出了性能优化建议。
在未来的开发中,我们可以进一步扩展歌词查找器应用的功能,例如添加用户登录和收藏功能、支持更多的音乐平台、优化用户界面等。通过不断地改进和优化,我们可以打造一个更加完善和强大的歌词查找器应用。
希望本文能够为您在开发类似应用时提供有价值的参考和指导。如果您在开发过程中遇到任何问题或有更好的建议,欢迎留言讨论。
超级会员免费看
10万+

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



