在本博客中,我们将逐步介绍使用 react 和 omdb api 构建 movie finder 网站的过程。该网站允许用户按复仇者联盟、星球大战和系列等类别浏览电影,并使用特定查询搜索电影。每部电影都有其详细页面,让您轻松探索更多有关您喜爱的电影。
电影查找网站使用户能够:
这是该项目的目录结构:
movie-finder/ ├── public/ ├── src/ │ ├── components/ │ │ └── navbar.js │ │ └── footer.js │ ├── pages/ │ │ └── home.js │ │ └── movies.js │ │ └── series.js │ │ └── searchresults.js │ │ └── moviedetail.js │ └── app.js │ └── app.css └── package.json
克隆存储库:
git clone https://github.com/abhishekgurjar-in/movie-finder.git cd movie-finder
安装依赖项:
npm install
从 omdb api 获取您的 api 密钥。
在项目根目录中创建一个 .env 文件并添加您的 api 密钥:
react_app_omdb_api_key=yourapikey
运行项目:
npm start
主页展示了两类电影:《复仇者联盟》和《星球大战》。当用户点击电影卡时,他们会被重定向到详细的电影页面。
来自 home.js 的代码片段:
import react, { useeffect, usestate } from "react"; import axios from "axios"; import { usenavigate } from "react-router-dom"; import movies from "./movies"; import series from "./series"; const home = () => { const [avengersmovies, setavengersmovies] = usestate([]); const [starwarsmovies, setstarwarsmovies] = usestate([]); const [loadingavengers, setloadingavengers] = usestate(true); const [loadingstarwars, setloadingstarwars] = usestate(true); const navigate = usenavigate(); useeffect(() => { fetchmovies("avengers", setavengersmovies, setloadingavengers); fetchmovies("star wars", setstarwarsmovies, setloadingstarwars); }, []); const fetchmovies = (query, setmovies, setloading) => { axios .get(`http://www.omdbapi.com/?s=${query}&apikey=you_api_key`) .then((response) => { if (response.data.search) { setmovies(response.data.search); } else { setmovies([]); // clear movies if no results } }) .catch((error) => { console.error(`there was an error fetching the ${query} movies!`, error); setmovies([]); // clear movies if there is an error }) .finally(() => { setloading(false); }); }; const handlecardclick = (id) => { navigate(`/movie/${id}`); }; const rendermovies = (movies, loading) => ( loading ? ( <div classname="loader"><div classname="load"></div></div> ) : ( <div classname="cards"> {movies.length > 0 ? ( movies.map((movie) => ( <div key={movie.imdbid} classname="card" onclick={() => handlecardclick(movie.imdbid)}> @@##@@ <h2>{movie.title}</h2> </div> )) ) : ( <p>no movies found.</p> )} </div> ) ); return ( <> <div classname="home"> <div classname="movie-category"> <h4>avengers movies</h4> {rendermovies(avengersmovies, loadingavengers)} </div> <br /> <br /> <div classname="movie-category"> <h4>star wars movies</h4> {rendermovies(starwarsmovies, loadingstarwars)} </div> </div> <movies /> <series /> </> ); }; export default home;
用户可以使用网站顶部的搜索栏搜索任何电影。搜索结果是根据用户的查询从 omdb api 获取的。
来自 searchresults.js 的代码片段:
import react, { useeffect, usestate } from "react"; import axios from "axios"; import { useparams, usenavigate } from "react-router-dom"; const searchresults = () => { const [movies, setmovies] = usestate([]); const [loading, setloading] = usestate(false); const { query } = useparams(); const navigate = usenavigate(); // add this line to use navigate useeffect(() => { if (query) { setloading(true); // set loading to true before starting the fetch axios .get(`http://www.omdbapi.com/?s=${query}&apikey=your_api_key`) .then((response) => { if (response.data.search) { setmovies(response.data.search); } else { setmovies([]); // clear movies if no results } }) .catch((error) => { console.error("there was an error fetching the movie data!", error); }) .finally(() => { setloading(false); // set loading to false once fetch is complete }); } }, [query]); const handlecardclick = (id) => { navigate(`/movie/${id}`); // navigate to movie detail page }; return ( <div classname="search-results"> <h4>search results for "{query}"</h4> {loading ? ( <div classname="loader"><div classname="load"></div></div> // loader ) : ( <div classname="cards"> {movies.length > 0 ? ( movies.map((movie) => ( <div key={movie.imdbid} classname="card" onclick={() => handlecardclick(movie.imdbid)}> @@##@@ <h2>{movie.title}</h2> </div> )) ) : ( <p>no results found.</p> )} </div> )} </div> ); }; export default searchresults;
当用户点击电影时,他们会被重定向到电影详细信息页面。此页面显示电影的标题、海报、情节、演员等。
来自 moviedetail.js 的代码片段:
import react, { useeffect, usestate } from 'react'; import axios from 'axios'; import { useparams } from 'react-router-dom'; const moviedetail = () => { const [movie, setmovie] = usestate(null); const [loading, setloading] = usestate(true); const { id } = useparams(); useeffect(() => { axios.get(`http://www.omdbapi.com/?i=${id}&apikey=your_api_key`) .then((response) => { setmovie(response.data); }) .catch((error) => { console.error("there was an error fetching the movie details!", error); }) .finally(() => { setloading(false); }); }, [id]); if (loading) return <div classname="loader"> <div classname="load"></div> </div>; if (!movie) return <div classname='loader'>no movie data found!</div>; return ( <div classname="movie-detail"> <div classname="detail-box"> <h1>{movie.title}</h1> <p><strong>year:</strong> {movie.year}</p> <p><strong>rating:</strong> {movie.imdbrating}</p> <p><strong>genre:</strong> {movie.genre}</p> <p><strong>director:</strong> {movie.director}</p> <p><strong>actors:</strong> {movie.actors}</p> <p><strong>plot:</strong> {movie.plot}</p> <p><strong>runtime:</strong> {movie.runtime}</p> <p><strong>language:</strong> {movie.language}</p> <p><strong>country:</strong> {movie.country}</p> <p><strong>awards:</strong> {movie.awards}</p> </div> <div classname="img-box"> @@##@@ </div> </div> ); }; export default moviedetail;
movies.js 和 series.js 页面分别列出电影和电视剧。
来自 movies.js 的代码片段:
import react, { useeffect, usestate } from "react"; import axios from "axios"; import { usenavigate } from "react-router-dom"; const movies = () => { const [movies, setmovies] = usestate([]); const navigate = usenavigate(); useeffect(() => { axios .get(`http://www.omdbapi.com/?s=avengers&type=movie&apikey=${process.env.react_app_omdb_api_key}`) .then(response => setmovies(response.data.search || [])) .catch(error => console.error(error)); }, []); const handlecardclick = (id) => { navigate(`/detail/${id}`); }; return ( <div classname="movies"> <h2>movies</h2> <div classname="cards"> {movies.map(movie => ( <div key={movie.imdbid} classname="card" onclick={() => handlecardclick(movie.imdbid)}> @@##@@ <h3>{movie.title}</h3> </div> ))} </div> </div> ); }; export default movies;
series.js 中的代码片段:
import react, { useeffect, usestate } from "react"; import axios from "axios"; import { usenavigate } from "react-router-dom"; const series = () => { const [series, setseries] = usestate([]); const navigate = usenavigate(); useeffect(() => { axios .get(`http://www.omdbapi.com/?s=star wars&type=series&apikey=${process.env.react_app_omdb_api_key}`) .then(response => setseries(response.data.search || [])) .catch(error => console.error(error)); }, []); const handlecardclick = (id) => { navigate(`/detail/${id}`); }; return ( <div classname="series"> <h2>tv series</h2> <div classname="cards"> {series.map(show => ( <div key={show.imdbid} classname="card" onclick={() => handlecardclick(show.imdbid)}> @@##@@ <h3>{show.title}</h3> </div> ))} </div> </div> ); }; export default series;
导航栏组件允许用户在不同页面之间导航并执行搜索。
import react, { usestate } from "react"; import { navlink, link } from "react-router-dom"; const navbar = () => { const [searchquery, setsearchquery] = usestate(""); const handlesearch = (event) => { if (event.key === 'enter' && searchquery.trim()) { document.getelementbyid('search-link').click(); } }; return ( <div classname="navbar"> <div classname="logo"> <h1>movie finder</h1> </div> <div classname="page-list"> <navlink to="/"> <h4>home</h4> </navlink> <navlink to="/movies"> <h4>movies</h4> </navlink> <navlink to="/series"> <h4>tv series</h4> </navlink> </div> <div classname="search-box"> <input type="text" placeholder="search for movies or series" value={searchquery} onchange={(e) => setsearchquery(e.target.value)} onkeydown={handlesearch} /> <link to={`/search/${searchquery}`} id="search-link"> <button>search</button> </link> </div> </div> ); }; export default navbar;
页脚组件提供简单的页脚消息。
import react from 'react'; const footer = () => { return ( <div classname='footer'> made with <span>❤️</span> by abhishek gurjar </div> ); }; export default footer;
*{ box-sizing: border-box; } body{ font-family: sans-serif; margin: 0; padding: 0; } .navbar { padding-inline: 100px; display: flex; align-items: center; justify-content: space-between; background-color: red; } .search-btn{ background-color: red; } .logo h1{ font-size: 25px; color:black; } .page-list { display: flex; align-items: center; gap: 40px; } .page-list a{ color: white; text-decoration: none; font-size: 20px; } .page-list a:hover{ color: black; } .page-list a.active{ color: black; } .search-box{ box-shadow: rgba(null, 0, 0, 0.35) 0px 5px 15px; background-color:white; color: gray; width: 250px; height: 40px; border-radius: 50px; overflow: hidden; } .search-box input{ width: 200px; height: 40px; margin-left: 10px; border: none; outline: none; } .home{ margin-block: 40px; margin-inline: 60px; } .home h4{ font-size: 16px; } .movies{ margin-block: 40px; margin-inline: 60px; } .movies h4{ font-size: 16px; } .cards{ display: flex; flex-wrap: wrap; align-items:center ; justify-content: space-between; gap: 10px; } .card{ width:200px; height:360px; border-radius: 10px; overflow: hidden; box-shadow: rgba(null, 0, 0, 0.35) 0px 5px 15px; } .card img{ width: 200px; height: 290px; object-fit: cover; } .card h2{ margin: 10px; font-size: 16px; text-align: center; } .series{ margin-block: 40px; margin-inline: 60px; } .series h4{ font-size: 16px; } .home{ margin-block: 40px; margin-inline: 60px; } .search-results{ margin-block: 40px; margin-inline: 60px; } .search-results h4{ font-size: 16px; } .loader{ min-height: 90vh; display: flex; align-items: center; justify-content: center; } /* HTML: <div class="loader"></div> */ .load { width: 50px; padding: 8px; aspect-ratio: 1; border-radius: 50%; background: #ff1900; --_m: conic-gradient(#0000 10%,#000), linear-gradient(#000 0 0) content-box; -webkit-mask: var(--_m); mask: var(--_m); -webkit-mask-composite: source-out; mask-composite: subtract; animation: l3 1s infinite linear; } @keyframes l3 {to{transform: rotate(1turn)}} .movie-detail { margin-block: 40px; margin-inline: 60px; display: flex; align-items: flex-start; justify-content: space-between; } img-box{ width: 50%; } .movie-detail img { border-radius: 10px; width: 330px; height: auto; object-fit: cover; box-shadow: rgba(null, 0, 0, 0.35) 0px 5px 15px; } .detail-box{ width: 50%; } .movie-detail p { font-size: 18px; margin: 10px 0; } .movie-detail a { display: inline-block; margin-top: 20px; color: #007bff; text-decoration: none; } .movie-detail a:hover { text-decoration: underline; } .footer{ width: 100%; background-color: red; text-align: center; color: white; padding: 20px; }
您可以在此处查看 movie finder 网站的现场演示。
在本博客中,我们学习了如何使用 react、react router 和 axios 创建 movie finder 网站。该项目演示了如何与公共 api 交互、在 react 中管理状态以及创建简单而实用的电影浏览体验。
随意定制设计并添加更多功能,例如用户评论或电影评分,使其更加动态!
abhishek gurjar 是一位专注的 web 开发人员,热衷于创建实用且功能性的 web 应用程序。在 github 上查看他的更多项目。
<img src="%7Bmovie.poster%7D" alt="{movie.title}"><img src="%7Bmovie.poster%7D" alt="{movie.title}"><img src="%7Bmovie.poster%7D" alt="{movie.title}"><img src="%7Bmovie.poster%7D" alt="{movie.title}"><img src="%7Bshow.poster%7D" alt="{show.title}">