今日灵感:React Router
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
我们一直使用 React 开发单页应用程序 (SPA),这并不是说我们不喜欢 SPA,我们非常喜欢它们,而是因为我们往往会参与到需要多页面的更复杂的应用程序中,对吧?所以,如果应用程序有多页面,我们就需要做好准备,我们需要优雅地从一个路由跳转到另一个路由。而要优雅地从一个路由跳转到另一个路由,我们需要什么呢?你猜对了!
React Router
这次的任务简直轻而易举。我们将探索react-router-dom包,它与我们的 create-react-app 配合得天衣无缝,这省去了我们很多麻烦。
我们将练习 react-router-dom 中提供的四个基本组件:
-
浏览器路由器
我们需要将顶层或根元素封装在这个组件中,以便能够指定不同的路由。通常,由于我总是使用 App 组件作为应用程序的入口点,所以这里是一个不错的选择。
-
转变
就像你在编程过程中遇到的任何其他 switch 语句一样,它可以帮助我们比较多个选项,并告诉我们哪个选项是最终的赢家,我们用它来将当前 URL 与我们指定的路由进行匹配。
-
路线
我们的交换机需要这些选项作为路由组件,我们可以在该组件中指定我们选择的路径并将其与其对应的组件关联起来。
-
关联
我们用它来代替我们通常的锚点标签,当我们实际指向特定路径时,它会在后台处理一切,并为我们创建锚点标签。
文字难以充分描述 React Router 的功能,但构建 Web 应用程序就能说明一切!
建造什么?
我们都需要时不时地获得一些视觉灵感,而且自从疫情爆发以来,我们待在室内的时间比待在室外的时间要多,所以我决定在这个网页应用程序中把一些室外的景色带进室内。
我们将打造一个灵感搜寻器,为我们无聊的眼睛提供慰藉。它将通过 API 从unsplash.com获取一些随机图片,让我们拥有足够的视觉享受来度过一天。
点击此处获取每日灵感
计划是什么?
我们希望有三个主要页面:
-
首页
我们在这里展示我们获取的随机图像
-
单图页面
该页面会显示从首页点击的任何图片,并放大显示图片拍摄者的姓名。
-
摄影师页面
点击单张图片页面中摄影师的名字,即可查看摄影师的基本信息和最新作品。
太棒了!我们的页面都做好了。我们只需要再添加一个组件,因为我们注意到“首页”和“摄影师”页面都需要显示图片网格,我们就把它叫做“图片”吧!
前往代码世界!
家
在安装并初始化 create-react-app 之后,让我们开始创建我们的第一个页面,即首页。
我们计划从unsplash.com获取一些随机图片,然后将它们以网格形式显示出来。
要使用 API,我们需要一个免费应用密钥。过程很简单,您可以从Unsplash 官方文档中了解更多信息。
这次,我们的项目结构新增了页面(Pages)的概念。我们将页面与组件分离。它们仍然是普通的 React 组件,区别在于它们的存储位置。我们希望保持代码的条理性和清晰性,避免任何可能出现的混淆。
对于我们的页面,我们将创建一个类似于组件文件夹结构的文件夹结构。首先,在 src 文件夹下创建一个名为 pages 的文件夹,然后创建一个名为 Home 的文件夹,并将 Home.js 和 Home.css 文件放在该文件夹中。
在我们的 Home.js 中,我们将与 API 端点通信,并在 componentDidMount 中获取 12 张随机图片(就像我们习惯的那样),并将我们得到的响应设置到名为 images 的状态中。
import React from "react";
import "./Home.css";
class Home extends React.Component {
constructor() {
super();
this.state = {
images: [],
};
}
componentDidMount() {
fetch("https://api.unsplash.com/photos/random?count=12", {
headers: {
Authorization: `Client-ID YourApiKey`,
},
})
.then((response) => response.json())
.then((data) => {
this.setState({
images: data,
});
})
.catch((error) => console.error(error));
}
render() {
return
(<div className="home">
<h1 className="header">Inspiration Of The Day</h1>
</div>);
}
}
export default Home;
现在我们需要创建之前商定的 Images 组件来完成我们的页面,但首先,让我们在 App.js 文件中包含我们的主页,以便在进行更改时显示出来!
import React from "react";
import "./App.css";
import Home from "./pages/Home/Home";
class App extends React.Component {
render() {
return (
<div className="app">
<Home />
</div>
);
}
}
export default App;
图片
在 src 文件夹中创建 components 文件夹并在其中包含 Images 文件夹后,我们现在可以填充 Images.js 和 Image.css 文件来显示我们的灵感图片。
我们将使用一个简单的图像网格,并计划将这些图像作为数组通过 prop 传递给我们。
import React from "react";
import "./Images.css";
class Images extends React.Component {
render() {
return (
<div className="images">
{this.props.images.map((data) => (
<img key={data.id} alt={data.description} className="image" src={data.urls.small} />
))}
</div>
);
}
}
export default Images;
太棒了!现在,让我们把图片组件添加到首页,并将这些图片作为 prop 传递进去!
render() {
return (
<div className="home">
<h1 className="header">Inspiration Of The Day</h1>
<Images images={this.state.images} />
</div>
);
}
看起来不错!不过如果我们在这里加点 CSS,效果会更好。
Images.css
.images {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap: 2rem;
padding: 0 5% 5% 5%;
align-items: stretch;
}
.images .image {
width: 100%;
height: 100%;
max-height:350px;
border: teal 10px solid;
}
.image img{
width: 100%;
height: 100%;
}
App.css
@import url('https://fonts.googleapis.com/css2?family=Pacifico&display=swap');
* {
box-sizing: border-box;
}
html,
body,
#root,
.app {
height: 100%;
}
body {
background-color: #ff5c5c;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1600 800'%3E%3Cg stroke='%23000' stroke-width='65.5' stroke-opacity='0.1' %3E%3Ccircle fill='%23ff5c5c' cx='0' cy='0' r='1800'/%3E%3Ccircle fill='%23f3535d' cx='0' cy='0' r='1700'/%3E%3Ccircle fill='%23e64b5e' cx='0' cy='0' r='1600'/%3E%3Ccircle fill='%23d9435e' cx='0' cy='0' r='1500'/%3E%3Ccircle fill='%23cb3b5d' cx='0' cy='0' r='1400'/%3E%3Ccircle fill='%23be355c' cx='0' cy='0' r='1300'/%3E%3Ccircle fill='%23b02f5a' cx='0' cy='0' r='1200'/%3E%3Ccircle fill='%23a22958' cx='0' cy='0' r='1100'/%3E%3Ccircle fill='%23942455' cx='0' cy='0' r='1000'/%3E%3Ccircle fill='%23862052' cx='0' cy='0' r='900'/%3E%3Ccircle fill='%23781b4e' cx='0' cy='0' r='800'/%3E%3Ccircle fill='%236a1849' cx='0' cy='0' r='700'/%3E%3Ccircle fill='%235d1444' cx='0' cy='0' r='600'/%3E%3Ccircle fill='%2350103e' cx='0' cy='0' r='500'/%3E%3Ccircle fill='%23430d38' cx='0' cy='0' r='400'/%3E%3Ccircle fill='%23370a32' cx='0' cy='0' r='300'/%3E%3Ccircle fill='%232b062b' cx='0' cy='0' r='200'/%3E%3Ccircle fill='%23210024' cx='0' cy='0' r='100'/%3E%3C/g%3E%3C/svg%3E");
background-attachment: fixed;
background-size: cover;
/* background by SVGBackgrounds.com */
font-family: "Pacifico", cursive;
color: #c2bcc7;
}
首页.css
.home .header {
text-align: center;
font-size: 3rem;
text-shadow: -11px -1px 5px #210024;
}
太棒了!我们有了第一个页面!现在是时候开始着手处理最重要的部分——路由器了。
我们将首先在控制台中运行:
npm install react-router-dom
现在,让我们先让之前提到的四个组件中的三个正常工作,并在 App.js 的开头导入它们。
import { BrowserRouter, Switch, Route } from "react-router-dom";
按照约定,我们需要将应用内容封装在 BrowserRouter 组件中,以便路由器知道在哪里查找内容。此外,我们还需要创建一个 Switch 组件来封装 Route 组件,并添加多个路由的路径。
return (
<BrowserRouter basename={process.env.PUBLIC_URL}>
<div className="app">
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</div>
</BrowserRouter>
);
添加它basename可能不会对你的本地环境造成任何影响,你的代码即使不添加也能正常运行,但部署后你会感谢我的。create-react-app 会自动在 PUBLIC_URL 环境变量中设置正确的绝对路径,但除非我们把这个变量添加到每个路由路径的前面,或者把它传递给 BrowserRouter 组件,否则 react-router-dom 无法识别这个路径basename,它会自动帮我们添加路径前缀。所以,如果你不想让服务器上的 URL 变得混乱,请记住这一步。
现在我们使用了另一个关键字exact。当我们把这个关键字传递给路由组件时,就告诉它只响应指定的路径并返回指定的组件。如果我们没有添加这个关键字,即使路径中包含任意数量的单词或参数,它仍然会返回首页组件。
单幅图像
我们已经准备好了图片,现在应该开始创建 SingleImage 页面,以放大显示点击的图片。但在创建 SingleImage 页面之前,我们需要调整 Images 组件,并为每个图片添加我们提到的第四个组件:链接。
img我们将用 `<img>` 标签包裹我们的图片Link,并将该路径指向单张图片。此外,为了知道我们正在查看哪张图片,我们还会传递从获取的数据中获得的图片 ID。
import React from "react";
import "./Images.css";
import { Link } from "react-router-dom";
class Images extends React.Component {
render() {
return (
<div className="images">
{this.props.images.map((data) => (
<Link key={data.id} to={"/single-image/"+data.id} className="image" > <img alt={data.description} src={data.urls.small} /></Link>
))}
</div>
);
}
}
export default Images;
现在,我们来添加单张图片页面。页面上只会显示这张放大后的图片,并附上摄影师的链接。
我们将使用在链接中传递的参数来获取图像数据id。我们可以使用以下方式访问链接组件中发送的参数。this.props.match.params.ourParameterName
import React from "react";
import "./SingleImage.css";
import { Link } from "react-router-dom";
class SingleImage extends React.Component {
constructor(props) {
super(props);
this.state = {
image: {},
};
}
componentDidMount() {
fetch("https://api.unsplash.com/photos/" + this.props.match.params.id, {
headers: {
Authorization: `Client-ID ${process.env.REACT_APP_API_KEY}`,
},
})
.then((response) => response.json())
.then((data) => {
this.setState({
image: data,
});
})
.catch((error) => console.error(error));
}
render() {
if (this.state.image.user && this.state.image.urls) {
return (
<div className="single-image">
<figure>
<img
alt={this.state.image.description}
src={this.state.image.urls.full}
/>
<figcaption>
Photographed By{" "}
<span className="image-photographer">
<Link to={"/photographer/" + this.state.image.user.id}>
{this.state.image.user.name}
</Link>
</span>
</figcaption>
</figure>
</div>
);
}
return "";
}
}
export default SingleImage;
我遇到一个问题,有时嵌套对象是未定义的,所以我添加了一个 if 条件,确保只有在所有数据都准备就绪后才渲染。我还给摄影师添加了一个 Link 组件,就像我们给图像添加 Link 组件一样。
为了更清楚地看到所有内容,让我们在进入摄影师页面之前,先在 SingleImage.css 中添加我们的 CSS!
.single-image {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.single-image figure {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.single-image figcaption,
.single-image figcaption .image-photographer {
text-align: center;
color: #c2bcc7;
font-family: "Pacifico", cursive;
}
.single-image figcaption {
font-size: 2rem;
text-shadow: -11px -1px 5px #210024;
}
.single-image figcaption .image-photographer {
text-shadow: none;
font-size: 3rem;
}
.single-image figcaption .image-photographer a {
color: #c2bcc7;
}
.single-image figure img {
width: 70%;
height: 80%;
}
太棒了!我们可以正式在 App.js 文件中添加 Switch 组件的路径了!
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route path="/single-image/:id" component={SingleImage} />
<Route path="/" exact component={Home} />
</Switch>
</div>
</BrowserRouter>
);
}
成功了!
摄影师
对于我们的摄影师页面,我们需要获取两种不同的数据:摄影师的基本信息和摄影师的最新照片。
在 componentDidMount 中,我们将调用两个端点来实现此目的,并使用结果数据更新我们的状态。
import React from "react";
import "./Photographer.css";
import Images from "../../components/Images/Images";
class Photographer extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {},
images: [],
};
}
componentDidMount() {
fetch(
"https://api.unsplash.com/users/" + this.props.match.params.username,
{
headers: {
Authorization: `Client-ID ${process.env.REACT_APP_API_KEY}`,
},
}
)
.then((response) => response.json())
.then((data) => {
console.log(data);
this.setState({
user: data,
});
})
.catch((error) => console.error(error));
fetch(
"https://api.unsplash.com/users/" +
this.props.match.params.username +
"/photos?order_by=latest",
{
headers: {
Authorization: `Client-ID ${process.env.REACT_APP_API_KEY}`,
},
}
)
.then((response) => response.json())
.then((data) => {
console.log(data);
this.setState({
images: data,
});
})
.catch((error) => console.error(error));
}
render() {
return (
<div className="photographer">
<div className="info">
<h1 className="header">{this.state.user.name}</h1>
<div className="info-block">
<p className="header">Bio</p>
<p>{this.state.user.bio}</p>
</div>
<div className="info-block">
<p className="header">Location</p>
<p>{this.state.user.location}</p>
</div>
<div className="info-block">
<p className="header">Portfolio</p>
<p><a href={this.state.user.portfolio_url}>{this.state.user.portfolio_url}</a></p>
</div>
</div>
<div className="photographer-images">
<h1 className="header"> Latest Work</h1>
<Images images={this.state.images} />
</div>
</div>
);
}
}
export default Photographer;
运行正常,但 Photographer.css 中需要添加 CSS。
.photographer {
display: grid;
grid-template-columns: 2fr 8fr;
}
.photographer .info {
margin-left: 2rem;
}
.info .info-block .header {
font-weight: 900;
color: teal;
}
.info .info-block a {
color: #c2bcc7;
}
.photographer .photographer-images .header {
text-align: center;
}
搞定!我们每天都有灵感来源!
代码可以在这里找到
凭借这小小的灵感,我将结束迈向 React 卓越的第六个小步骤,期待我们下次再见。
任何反馈或建议都非常欢迎。可以通过这里、推特、其他任何方式联系我!
文章来源:https://dev.to/ranaemad/inspiration-of-the-day-react-router-46f3

