使用 react hooks 搭建一个电影 App,该 demo 属于一个极简版,参考自一个外国哥们写的 《How to build a movie search app using React Hooks》,文章的核心围绕用 react hook 搭建一个电影 App 的关键步骤展开,没有多余的描述,代码可以开箱即用,欢迎食用。

1、 项目创建

1
2
3
4
5
npm install -g create-react-app

create-react-app <item-name>

yarn start

2、创建 components 目录

(1) 把 App.js 拉到该目录下,同时更新 index.js 下 App.js 的路径

(2) 创建 Header.js,header 组件,接收父组件传过来的标题,App-header 是 App.css 中的一个 class

1
2
3
4
5
6
7
8
9
10
11
import React from "react";

const Header = props => {
return (
<header className="App-header">
<h2>{props.text}</h2>
</header>
);
};

export default Header;

(3) 创建 Movie.js,该组件用于展示父组件传过来的电影相关数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from "react";

const DEFAULT_PLACEHOLDER_IMAGE =
"https://m.media-amazon.com/images/M/MV5BMTczNTI2ODUwOF5BMl5BanBnXkFtZTcwMTU0NTIzMw@@._V1_SX300.jpg";

const Movie = ({ movie }) => {
const poster =
movie.Poster === "N/A" ? DEFAULT_PLACEHOLDER_IMAGE : movie.Poster;
return (
<div className="movie">
<h2>{movie.Title}</h2>
<div>
<img
width="200"
alt={`The movie titled: ${movie.Title}`}
src={poster}
/>
</div>
<p>({movie.Year})</p>
</div>
);
};

export default Movie;

(4) 创建 Search.js,该组件实现搜索功能,其中用到了 react 的 useState 钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { useState } from "react";

const Search = props => {
const [searchValue, setSearchValue] = useState("");

const handleSearchInputChanges = e => {
setSearchValue(e.target.value);
};

const resetInputField = () => {
setSearchValue("");
};

const callSearchFunction = e => {
e.preventDefault();
props.search(searchValue);
resetInputField();
};

return (
<form className="search">
<input
value={searchValue}
onChange={handleSearchInputChanges}
type="text"
/>
<input onClick={callSearchFunction} type="submit" value="SEARCH" />
</form>
);
};

export default Search;

3、更新 App.js 文件,引用上述新组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import React, { useReducer, useEffect } from "react";
import "../App.css";
import Header from "./Header";
import Movie from "./Movie";
import Search from "./Search";

const MOVIE_API_URL = "https://www.omdbapi.com/?s=man&apikey=4a3b711b";

const initialState = {
loading: true,
movies: [],
errorMessage: null
};

const reducer = (state, action) => {
switch (action.type) {
case "SEARCH_MOVIES_REQUEST":
return {
...state,
loading: true,
errorMessage: null
};
case "SEARCH_MOVIES_SUCCESS":
return {
...state,
loading: false,
movies: action.payload
};
case "SEARCH_MOVIES_FAILURE":
return {
...state,
loading: false,
errorMessage: action.error
};
default:
return state;
}
};

const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);

useEffect(() => {
fetch(MOVIE_API_URL)
.then(response => response.json())
.then(jsonResponse => {
dispatch({
type: "SEARCH_MOVIES_SUCCESS",
payload: jsonResponse.Search
});
});
}, []);

const search = searchValue => {
dispatch({
type: "SEARCH_MOVIES_REQUEST"
});

fetch(`https://www.omdbapi.com/?s=${searchValue}&apikey=4a3b711b`)
.then(response => response.json())
.then(jsonResponse => {
if (jsonResponse.Response === "True") {
dispatch({
type: "SEARCH_MOVIES_SUCCESS",
payload: jsonResponse.Search
});
} else {
dispatch({
type: "SEARCH_MOVIES_FAILURE",
error: jsonResponse.Error
});
}
});
};

const { movies, errorMessage, loading } = state;

return (
<div className="App">
<Header text="REACT MOVIE APP" />
<Search search={search} />
<p className="App-intro">Sharing a few of our favourite movies</p>
<div className="movies">
{loading && !errorMessage ? (
<span>loading... </span>
) : errorMessage ? (
<div className="errorMessage">{errorMessage}</div>
) : (
movies.map((movie, index) => (
<Movie key={`${index}-${movie.Title}`} movie={movie} />
))
)}
</div>
</div>
);
};

export default App;

4、完结撒花