react课程7-usepopcorn-1

Last updated on August 28, 2024 am

本次课程构建出一个电影清单界面,拥有更多的功能和更强的互动性🤓(NO这次先实现静态网页)。咱们就是争取这次不偷懒,不仅把代码打完把知识点也跟着记完吧。。。

一个小小的快捷键:鼠标悬停在某个组件,ctrl+左键=>直接去编辑这个组件(函数)

另一个小小的快捷键:cl=>console.log()

啊啊啊啊啊啊啊啊啊啊啊啊我为什么不早点切换到npm国内镜像!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

npm下载速度真的慢的我想刀人image-20240818164718178
npm config set registry=https://registry.npmmirror.com 命令一键切换

一、知识点讲解

(1)组件(component)分解

首先需要明确的是:不管使用一个很大的组件或者是拆分成很多很小的组件都会使代码变得糟糕。逻辑分离很重要,而且要注重代码重用!!!!!让代码变得高效易懂。

(2)组件分类

1、无状态/展示组件(Stateless/Presentational Components)

  • 定义: 主要负责展示 UI(用户界面),不处理应用的业务逻辑。
  • 特点:
    • 主要通过 props 接收数据。
    • 仅负责展示,不涉及业务逻辑或状态管理。
    • 由于没有业务逻辑,通常可以在多个地方复用。
    • 可以使用函数组件,也可以是无状态的类组件。
  1. Stateful Components(有状态组件)
  • 定义: 负责管理应用的状态和逻辑,并可能会将数据传递给展示组件。这些组件通常会使用 state 来管理其内部状态。

  • 特点:

    • 管理状态: 处理应用的状态(使用 state)。

    • 处理业务逻辑: 负责应用的逻辑,例如处理用户输入、调用 API、计算数据等。

    • 容器组件: 通常扮演容器组件的角色,将数据和逻辑传递给展示组件。

    • 实现: 可以使用类组件(具有 state 和生命周期方法)或使用 React Hooks 的函数组件。

  1. Structural Components(结构组件)
  • 定义: 主要负责应用的布局和结构,包括组织其他组件的方式。这些组件通常不关心具体的业务逻辑或数据展示,而是负责整体的界面布局和结构。

  • 特点:

    • 布局和结构: 负责定义组件的布局和结构,例如容器、网格、栅格系统等。

    • 包含其他组件: 通常会包含其他展示组件或有状态组件。

    • 不处理业务逻辑: 不直接处理应用的数据或业务逻辑,只负责组织和布局。

(3)什么叫做使用Composition来解决Prop Drilling的问题

Composition 是一种用来解决 Prop Drilling(属性传递)问题的设计模式。Prop Drilling 指的是将数据从父组件逐层传递到深层嵌套的子组件,这样可能会导致组件树中的中间组件不必要地接收和传递数据,使得代码变得复杂且难以维护。Composition 是指将多个组件组合在一起,以实现更复杂的功能或布局。通过这种方式,组件可以专注于其自身的功能,而不是接收和传递大量的 props

(4)default值的设置

为了增强组件的可重用性,通常会使用 props 传递数据、回调函数和行为,而不是将数据硬编码在组件内部。这样,组件可以根据传入的 props 动态渲染不同的内容或执行不同的操作。default 值用于为组件参数、变量或属性提供默认值,当未提供相应值时,程序将使用这些默认值。通过合理使用默认值,可以简化代码、提高可读性,并减少错误。PS:可以用default值来初始化状态

二、练习

(1)将下面的网页由一个巨大的组件分解成下图的组件树

image-20240817161437915

image-20240817161356298

(2)构建一个评分组件

image-20240818142751400

  • 特点:鼠标悬停在星星上时会显示暂时星数,星星会随之被颜色填满或仍未空心。点击某颗星星会固定星数和满星的数量。

  • 拓展:增强组件的可重用性。

    • 1、将星星的大小、颜色、字体的大小、颜色、最大星星数、默认星星数、显示的内容等等都通过props(参数)传递。(参数太多会增加代码的复杂性

    • 2、🌟🌟考虑到用户在想要使用这个组件的时候,想要拿到一个重要的参数:星数。因此我们在某个组件中应用这个组件时,需要设置一个状态(比如):movieRating。然后把setMovieRating函数通过参数传递给评分组件。在组件中的设置星数的函数中将movieRating也设置为Rating。

  • 参数类型检查:

  • 1、PropType:PropTypes 是 React 内置的一个库,用于在运行时检查组件接收到的 props 是否符合指定的类型。尽管在 React 17 之后,PropTypes 被移出了核心库,但它仍然是进行简单类型检查的常用工具。

  • 2、TypeScript:在 TypeScript 中,类型检查是通过静态类型系统在编译时完成的,这比 PropTypes 更加严格和安全。

三、代码

(1)usepopcorn

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import { useState } from "react";

const tempMovieData = [
{
imdbID: "tt1375666",
Title: "Inception",
Year: "2010",
Poster:
"https://m.media-amazon.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
},
{
imdbID: "tt0133093",
Title: "The Matrix",
Year: "1999",
Poster:
"https://m.media-amazon.com/images/M/MV5BNzQzOTk3OTAtNDQ0Zi00ZTVkLWI0MTEtMDllZjNkYzNjNTc4L2ltYWdlXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg",
},
{
imdbID: "tt6751668",
Title: "Parasite",
Year: "2019",
Poster:
"https://m.media-amazon.com/images/M/MV5BYWZjMjk3ZTItODQ2ZC00NTY5LWE0ZDYtZTI3MjcwN2Q5NTVkXkEyXkFqcGdeQXVyODk4OTc3MTY@._V1_SX300.jpg",
},
];

const tempWatchedData = [
{
imdbID: "tt1375666",
Title: "Inception",
Year: "2010",
Poster:
"https://m.media-amazon.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
runtime: 148,
imdbRating: 8.8,
userRating: 10,
},
{
imdbID: "tt0088763",
Title: "Back to the Future",
Year: "1985",
Poster:
"https://m.media-amazon.com/images/M/MV5BZmU0M2Y1OGUtZjIxNi00ZjBkLTg1MjgtOWIyNThiZWIwYjRiXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg",
runtime: 116,
imdbRating: 8.5,
userRating: 9,
},
];

const average = (arr) =>
arr.reduce((acc, cur, i, arr) => acc + cur / arr.length, 0);

export default function App() {
const [movies, setMovies] = useState(tempMovieData);
const [watched, setWatched] = useState(tempWatchedData);
return (
<>
<NavBar>
<Search />
<NumResults movies={movies} />
</NavBar>

<Main>
<Box>
<MovieList movies={movies} />
</Box>
<Box>
<WatchedSummary watched={watched} />
<WatchedMovieList watched={watched} />
</Box>
</Main>
</>
);
}

function NavBar({ children }) {
return (
<nav className="nav-bar">
{" "}
<Logo />
{children}
</nav>
);
}

function Logo() {
return (
<div className="logo">
<span role="img">🍿</span>
<h1>usePopcorn</h1>
</div>
);
}

function Search() {
const [query, setQuery] = useState("");
return (
<input
className="search"
type="text"
placeholder="Search movies..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}

function NumResults({ movies }) {
return (
<p className="num-results">
Found <strong>{movies.length}</strong> results
</p>
);
}

function Main({ children }) {
return <main className="main">{children}</main>;
}

function Box({ children }) {
const [isOpen, setIsOpen] = useState(true);
return (
<div className="box">
<button className="btn-toggle" onClick={() => setIsOpen((open) => !open)}>
{isOpen ? "–" : "+"}
</button>
{isOpen && children}
</div>
);
}

function MovieList({ movies }) {
return (
<ul className="list">
{movies?.map((movie) => (
<Movie movie={movie} key={movie.imdbID} />
))}
</ul>
);
}

function Movie({ movie }) {
return (
<li key={movie.imdbID}>
<img src={movie.Poster} alt={`${movie.Title} poster`} />
<h3>{movie.Title}</h3>
<div>
<p>
<span>🗓</span>
<span>{movie.Year}</span>
</p>
</div>
</li>
);
}

function WatchedSummary({ watched }) {
const avgImdbRating = average(watched.map((movie) => movie.imdbRating));
const avgUserRating = average(watched.map((movie) => movie.userRating));
const avgRuntime = average(watched.map((movie) => movie.runtime));
return (
<div className="summary">
<h2>Movies you watched</h2>
<div>
<p>
<span>#️⃣</span>
<span>{watched.length} movies</span>
</p>
<p>
<span>⭐️</span>
<span>{avgImdbRating}</span>
</p>
<p>
<span>🌟</span>
<span>{avgUserRating}</span>
</p>
<p>
<span></span>
<span>{avgRuntime} min</span>
</p>
</div>
</div>
);
}

function WatchedMovieList({ watched }) {
return (
<ul className="list">
{watched.map((movie) => (
<WatchedMovie movie={movie} />
))}
</ul>
);
}

function WatchedMovie({ movie }) {
return (
<li key={movie.imdbID}>
<img src={movie.Poster} alt={`${movie.Title} poster`} />
<h3>{movie.Title}</h3>
<div>
<p>
<span>⭐️</span>
<span>{movie.imdbRating}</span>
</p>
<p>
<span>🌟</span>
<span>{movie.userRating}</span>
</p>
<p>
<span></span>
<span>{movie.runtime} min</span>
</p>
</div>
</li>
);
}

(2)StarRating.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
99
100
101
102
import { useState } from "react";
const containerStyle = {
display: "flex",
alignItems: "center",
gap: "16px",
};

const starContainerStyle = {
display: "flex",
//gap: "4px",
};

export default function StarRating({
maxRating = 5,
color = "#e3af40",
size = 48,
className = "",
messages = [],
defaultRating = 0,
}) {
const [rating, setRating] = useState(defaultRating);
const [tempRating, setTempRating] = useState(0);

function handleRating(rating) {
setRating(rating);
}

const textStyle = {
lineHeight: "1",
margin: "0",
color,
fontSize: `${size / 2.5}px`,
};

return (
<div style={containerStyle} className={className}>
<div style={starContainerStyle}>
{Array.from({ length: maxRating }, (_, i) => (
<Star
key={i}
onRate={() => handleRating(i + 1)}
full={tempRating ? tempRating >= i + 1 : rating >= i + 1}
onHoverIn={() => setTempRating(i + 1)}
onHoverOut={() => setTempRating(0)}
color={color}
size={size}
/>
))}
</div>
<p style={textStyle}>
{maxRating === messages.length
? messages[tempRating ? tempRating - 1 : rating - 1]
: tempRating || rating || ""}
</p>
</div>
);
}

function Star({ onRate, full, onHoverIn, onHoverOut, color, size }) {
const starStyle = {
width: `${size / 1.5}px`,
height: `${size / 1.5}px`,
display: "block",
cursor: "pointer",
};

return (
<span
role="button"
style={starStyle}
onClick={onRate}
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
>
{full ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill={color}
stroke={color}
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke={color}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="{2}"
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
/>
</svg>
)}
</span>
);
}

(3)可折叠会话(可重用小组件)

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
import React from "react";
import ReactDOM from "react-dom/client";
//import "./index.css";
//import App from "./App";
import StarRating from "./StarRating";
import { useState } from "react";
import "./style.css";

function Text() {
return <StarRating color="blue" />;
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
{/* <App /> */}
<StarRating
maxRating={5}
messages={["Terrible", "Bad", "Okay", "Good", "Amazing"]}
/>
<StarRating size={35} color="red" className="test" defaultRating={3} />
<Text />
<TextExpander>
Space travel is the ultimate adventure! Imagine soaring past the stars and
exploring new worlds. It's the stuff of dreams and science fiction, but
believe it or not, space travel is a real thing. Humans and robots are
constantly venturing out into the cosmos to uncover its secrets and push
the boundaries of what's possible.
</TextExpander>

<TextExpander
collapsedNumWords={20}
expandButtonText="Show text"
collapseButtonText="Collapse text"
buttonColor="#ff6622"
>
Space travel requires some seriously amazing technology and collaboration
between countries, private companies, and international space
organizations. And while it's not always easy (or cheap), the results are
out of this world. Think about the first time humans stepped foot on the
moon or when rovers were sent to roam around on Mars.
</TextExpander>

<TextExpander expanded={true} className="box">
Space missions have given us incredible insights into our universe and
have inspired future generations to keep reaching for the stars. Space
travel is a pretty cool thing to think about. Who knows what we'll
discover next!
</TextExpander>
</React.StrictMode>
);

function TextExpander({
collapsedNumWords = 10,
expandButtonText = "show more",
collapseButtonText = "show less",
buttonColor = "red",
expanded = false,
className,
children,
}) {
const [isExpanded, setIsExpanded] = useState(expanded);
const displayText = isExpanded
? children
: children.split(" ").slice(0, collapsedNumWords).join(" ") + "...";

const buttonStyle = {
background: "none",
border: "none",
font: "inherit",
cursor: "pointer",
marginLeft: "6px",
color: buttonColor,
};
return (
<div className={className}>
<span>{displayText}</span>
<button onClick={() => setIsExpanded((exp) => !exp)} style={buttonStyle}>
{isExpanded ? collapseButtonText : expandButtonText}
</button>
</div>
);
}


react课程7-usepopcorn-1
http://example.com/2024/08/17/react课程7-usepopcorn/
Author
Yaodeer
Posted on
August 17, 2024
Licensed under