现代Web开发:LyricsFinder V2实现与优化
1. 重访SongsList组件
1.1 修改Props接口
首先,回到
songs-list.tsx
文件,将
Props
接口修改为如下形式:
type Props = {
songs: FindSongs_songs[];
songSelected: (song: FindSongs_songs) => void;
};
同时添加导入语句:
import { FindSongs_songs } from '../generated/FindSongs';
1.2 渲染歌曲列表
使用
react-bootstrap
的
ListGroup
和
ListGroup.Item
组件来渲染歌曲列表。在
SongsList
组件内添加以下代码:
const songElements = props.songs
.filter((song: FindSongs_songs) => song.hasLyrics)
.map((song: FindSongs_songs) =>
<ListGroup.Item key={song.id} className='lf-song' action
style={{cursor: 'pointer'}}
onClick={() => props.songSelected(song)}>{song.name}
</ListGroup.Item>
);
添加
ListGroup
的导入语句:
import { ListGroup } from 'react-bootstrap';
需要注意的点:
- 为每个元素设置
key
以优化重新渲染操作。
- 设置
action
属性将列表项标记为可操作项。
- 添加
onClick
事件处理程序,调用
songSelected
回调函数。
- 过滤操作可考虑移至
Home
组件。
最后,更新组件的返回表达式:
return (
<Container className='lf-songs'>
<h3>Songs</h3>
<ListGroup>
{songElements}
</ListGroup>
</Container>
);
1.3 更新Home视图
打开
frontend/src/views/home.tsx
文件,在
Home
组件内添加以下常量:
const songSelected = (selectedSong: FindSongs_songs) => {
console.log('Home: song selected: ', selectedSong);
};
更新
Home
视图中的
SongsList
元素:
<SongsList songs={foundSongs} songSelected={songSelected} />
移除之前的
foundSongsList
常量和对应的
ul
标签。
2. 前端:加载歌词并跳转至歌词页面
2.1 处理歌曲选择事件
当用户选择一首歌曲时,我们要显示歌词页面并渲染歌词。由于已配置好 GraphQL 客户端,我们将在
Home
页面直接获取歌词,并通过路由参数传递歌曲和歌词。
2.2 修改Home页面
打开
frontend/src/pages/home.tsx
文件,进行以下操作:
1. 添加导入语句:
import { RouteComponentProps } from 'react-router';
-
声明
props参数:
export const Home = (props: RouteComponentProps)
2.3 适配
songSelected
函数
import { FindLyricsQuery } from '../graphql/queries'
import { FindLyrics, FindLyricsVariables } from '../generated/FindLyrics';
...
const songSelected = (selectedSong: FindSongs_songs) => {
console.log('Home: song selected: ', selectedSong);
apolloClient.query<FindLyrics, FindLyricsVariables>({
query: FindLyricsQuery,
variables: {
id: selectedSong.id,
},
}).then((result: ApolloQueryResult<FindLyrics>) => {
const songLyrics = result.data.songLyrics;
console.log(`Home: lyrics loaded for [${selectedSong.name}]: ${songLyrics.lyrics}`);
props.history.push('/lyrics', {
song: selectedSong,
songLyrics,
});
}).catch((error: any) => {
console.log('Home: error while loading lyrics: ', error);
});
};
3. 前端:实现歌词页面
3.1 修改歌词页面
打开
frontend/src/pages/lyrics.tsx
文件,进行如下修改:
import React, { ReactElement } from "react";
import { RouteComponentProps } from 'react-router';
import Container from 'react-bootstrap/Container';
import { Link } from 'react-router-dom';
import { FindLyrics_songLyrics } from '../generated/FindLyrics';
import { FindSongs_songs } from '../generated/FindSongs';
import Card from 'react-bootstrap/Card';
type LyricsLocationState = {
song: FindSongs_songs;
songLyrics: FindLyrics_songLyrics;
};
interface LyricsProps extends RouteComponentProps<any, any, LyricsLocationState> {
}
export const Lyrics = (props: LyricsProps) => {
const songLyrics = props.location.state.songLyrics;
const songLyricsLines: ReactElement[] = [];
if (songLyrics.lyrics) {
songLyrics.lyrics.split("\n").forEach((line, i) => {
songLyricsLines.push(
<span key={i}>
{line}
<br />
</span>
);
});
}
const song = props.location.state.song;
return (
<Container className='lf-lyrics'>
<Card>
<Card.Header>{song.name} (<Link to='/' title='Go back'>Go back</Link>)</Card.Header>
<Card.Body>
<Card.Text>
<span>{songLyricsLines}</span>
</Card.Text>
<h4>Copyright:</h4>
<span>{songLyrics.copyright}</span>
</Card.Body>
</Card>
</Container>
);
};
3.2 代码解释
-
定义
LyricsLocationState类型和LyricsProps接口,明确location关联的状态类型。 -
分割歌词字符串为多行
ReactElement。 -
使用
Card组件渲染歌词,在卡片头部添加返回主页的链接。
3.3 安全考虑
避免使用
dangerouslySetInnerHTML
属性直接绑定歌词,以防止跨站脚本攻击(XSS)。
以下是整个过程的流程图:
graph TD;
A[修改SongsList组件] --> B[更新Home视图];
B --> C[处理歌曲选择事件];
C --> D[实现歌词页面];
4. 后续优化建议
4.1 添加图标
可以引入
FontAwesome
图标库,为不同页面和组件添加图标。
4.2 创建ArtistsList组件
实现
ArtistsList
组件,并在
Home
页面并排渲染歌曲列表和艺术家列表,可使用手风琴或标签面板进行包裹。示例代码如下:
<Accordion>
<Card>
<Accordion.Toggle as={Card.Header} variant="link" eventKey="0">
<h3>Artists</h3>
</Accordion.Toggle>
<Accordion.Collapse eventKey="0">
<Card.Body>
<ul>
{foundArtistsList}
</ul>
</Card.Body>
</Accordion.Collapse>
<Accordion.Toggle as={Card.Header} variant="link" eventKey="1">
<h3>Songs</h3>
</Accordion.Toggle>
<Accordion.Collapse eventKey="1">
<Card.Body>
<SongsList songs={foundSongs} songSelected={songSelected}/>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
4.3 无歌曲或艺术家时显示消息
在
SongsList
和
ArtistsList
组件中使用条件渲染,当没有元素可显示时显示消息。示例代码如下:
<Container className='lf-songs'>
{songElements.length > 0? (
<ListGroup>
{songElements}
</ListGroup>
) : (
<span>No songs</span>
)}
</Container>
4.4 添加加载指示器
添加状态变量来保存艺术家和歌曲的加载状态,并在 JSX 代码中使用条件渲染显示和隐藏加载指示器。示例代码如下:
const [artistsLoading, setArtistsLoading] = useState(false);
setArtistsLoading(true);
apolloClient.query<FindArtists, FindArtistsVariables>({
...
}).then((result: ApolloQueryResult<FindArtists>) => {
...
}).finally(() => setArtistsLoading(false));
在 JSX 中显示加载指示器:
<Accordion.Toggle as={Card.Header} variant="link" eventKey="0">
<h3>Artists</h3>
{ artistsLoading &&
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
}
</Accordion.Toggle>
通过这些优化,可以进一步提升应用的用户体验和功能完整性。
5. 回顾与总结
5.1 前端技术发展回顾
前端软件开发生态系统在过去几年发生了显著变化。ECMAScript 规范新的年度发布节奏推动了语言的快速发展,赋予开发者更多能力。我们从安装必要的开发工具开始,如 Visual Studio Code、Git、Node.js、NPM 和 TypeScript。
5.2 TypeScript 基础学习
学习了 TypeScript 的基础概念、基本类型,掌握了函数的编写方法。通过 Hello World 程序实践后,深入了解了更多特性,如 lambda 表达式、迭代器和循环、类型声明、类型推断、数组、null 特殊类型以及 as 类型转换运算符。
5.3 项目实践与知识应用
在开发 TodoIt 应用的过程中,了解了 npm 的工作原理,学会了编写和执行脚本、管理依赖项,掌握了 TypeScript 编译器的配置和构建模式。同时,利用现代浏览器的开发者工具进行调试,学习了如何生成和使用源映射文件。
5.4 面向对象编程与 TypeScript 特性
在第三章中,回顾了面向对象编程(OOP)的主要概念,包括封装、抽象、继承、多态、接口和类。同时,学习了 TypeScript 中与 OOP 相关的特性,如类和继承、字段和可见性、构造函数和访问器、接口、类型注解、自定义类型、结构类型、readonly 关键字、可选参数和默认值。并利用这些知识构建了新的 TodoIt 应用,体会到 OOP 对代码结构优化的重要性。
5.5 更多 TypeScript 特性与应用
在开发 MediaMan 应用时,接触到了更多 TypeScript 特性,如泛型、枚举、字符串字面量类型、联合和交叉类型、类型定义和 @types、装饰器、any 和 never 特殊类型、keyof 关键字和映射类型。同时,学习了现代浏览器的存储 API,如 LocalStorage、SessionStorage 和 IndexedDB,并使用了 localForage 和 class-transformer 等库。
5.6 模块化开发
在开发 WorldExplorer 应用时,重点关注了模块化开发。了解了 JavaScript 模块化的发展历程,从 Revealing Module 模式到 AMD/CommonJS、UMD 和 TypeScript 模块。学习了 TypeScript 中与模块化相关的概念,如导入和导出、模块、重新导出和桶、命名空间和模块解析。
以下是学习过程的关键知识点总结表格:
| 阶段 | 关键知识点 |
| ---- | ---- |
| 前端技术发展 | ECMAScript 规范、开发工具安装 |
| TypeScript 基础 | 基本类型、函数编写、特性学习 |
| 项目实践 | npm 使用、编译器配置、调试技巧 |
| 面向对象编程 | OOP 概念、TypeScript 相关特性 |
| 更多特性 | 泛型、枚举、存储 API |
| 模块化开发 | JavaScript 模块化历程、TypeScript 模块化概念 |
6. 未来学习方向
6.1 深入学习现有技术
虽然已经对 Angular、Vue.js、React、NestJS 和 GraphQL 有了一定的了解,但这些技术还有很多深入的内容值得学习。例如,在 React 中,可以进一步研究高阶组件、自定义 Hooks 等高级特性;在 GraphQL 方面,可以学习如何优化查询性能、处理复杂的关联关系等。
6.2 探索新的技术领域
随着技术的不断发展,新的前端框架和后端技术不断涌现。可以关注一些新兴的技术,如 Svelte、Deno 等,了解它们的特点和优势,为未来的项目选择合适的技术栈。
6.3 提升项目实践能力
通过参与更多的实际项目,将所学的知识应用到实际开发中,积累项目经验。可以尝试开源项目贡献,与其他开发者交流合作,提高自己的团队协作和问题解决能力。
6.4 关注行业动态
保持对行业动态的关注,阅读技术博客、参加技术会议和线上研讨会,了解最新的技术趋势和最佳实践。这有助于拓宽视野,不断提升自己的技术水平。
以下是未来学习方向的流程图:
graph LR;
A[深入学习现有技术] --> B[探索新的技术领域];
B --> C[提升项目实践能力];
C --> D[关注行业动态];
通过不断学习和实践,我们可以在现代软件开发的道路上不断前进,掌握更多的技能和知识,为自己的职业生涯打下坚实的基础。
超级会员免费看

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



