这是我自己系统整理的React系列博客,主要参考2023年3开放的最新版本react官网内容,欢迎你阅读本系列内容,希望能有所收货。
阅读完本文后,你将收获:
- 如何创建和嵌套使用react组件
- 如何添加标记和样式
- 如何展示数据
- 如何渲染条件和列表
- 如何响应事件和更新屏幕
- 如何在不同组件之间共享数据
1 如何创建和嵌套使用react组件
React应用是由组件(components)组成的,组件是用户界面(UI)的一部分,它有自己的逻辑和外观。组件的范围很大,可以是一个完整的页面,也可以是一个小小的按钮组件。从react16之后,建议你使用函数式组件结合hook完成react应用开发*。
Note:如果你还不知道什么是函数式组件和hook,先忽略这两个概念,后续我们会作介绍。
举个简单的栗子,下面的几行代码就定义了一个最简单的react组件:
function MyButton() {
return (
<button>我是一个按钮</button>
)
}
然后,你可以在其他react组件中使用这个MyButton
组件:
export default function MyApp() {
return (
<div>
<h1>欢迎学习React</h1>
<MyButton />
</div>
);
}
像上面的栗子所示,MyApp
是一个父组件,调用了MyButton
组件,因此MyButton
也就被称为子组件。运行上面的代码,你将看到:
特别说明:需要注意的是,在React中定义的组件名称是大写开头,且遵循大驼峰命名规则。
2 使用JSX编写标记(markup)
在前面的示例代码中出现的标记语法(markup syntax)被称为JSX(Javascript XML,javascript的扩展语言),主要特点是在js代码中编写标记语言。使用JSX编写React组件是可选的,但是大部分React项目都会使用JSX,因为用起来真的很香——很便捷(如果你用过optional API的话)。
相较于HTML,JSX更为严格。例如,你需要使用<br />
而不是<br>
,你的组件不能返回多个JSX标签——如果组件由多个并列的标签组成的话,需要在最外层使用<div> ... </div>
或者<> ... </>
包裹,如下面代码所示:
function AboutPage() {
return (
<>
<h1>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
3 给你的组件添加样式
在React组建中,可以使用className
给元素添加一个名字——就像在vue
的template
中给元素设置class
一样,只不过换了个名字:
function AboutPage() {
return (
<>
<h1 className="title">About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
如果你希望给这个组件添加样式,可以创建一个AboutPage.css
文件,然后在其中编写样式:
.title {
font-size: 24px;
color: green;
}
然后,在AboutPage.js
中引用css样式:
import "./AboutPage.css";
function AboutPage() {
...
}
除了这种写法,你也可以定义一个AboutPage.module.css
文件,其中内容和AboutPage.css
一样:
.title {
font-size: 24px;
color: green;
}
然后在AboutPage.js
中这样使用:
import styles from "./AboutPage.module.css";
function AboutPage() {
return (
<>
<h1 className={
styles.title}>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
这样做的好处是:由于使用了module css
,因为css重名导致的样式污染问题被避免了,我更喜欢使用这种写法。
4 展示数据
JSX允许你在Javascript代码中编写标记语言,而且还支持使用花括号{}
将数据再转义成Javascript,这样你就可以在JSX中嵌入一些变量:
return (
<h1>
{
user.name}
</h1>
);
使用花括号,你还可以将标记语言中的属性设置为变量,例如:className="avatar"
将"avatar"字符串作为CSS类名,但src={user.imageUrl}
将user.imageUrl
变量值作为img
标签的src属性:
return (
<img
className="avatar"
src={
user.imageUrl}
/>
);
更进一步,你还可以将object
类型的数据当做变量:
return (
<>
<h1>{
user.name}</h1>
<img
className="avatar"
src={
user.imageUrl}
alt={
'Photo of ' + user.name}
style={
{
width: user.imageSize,
height: user.imageSize
}}
/>
</>
);
}
注意:在这个例子里面,
style={ {}}
并不是一种特殊的语法,最里层的{}
只是一个对象外层的花括号而已。
5 条件渲染
在React中没有特别的语法用于编写条件判断(像vue
中可以使用v-if
, v-else
),如果你需要编写条件渲染语句,只需要像在Javascript中那样使用if-else
或者其他分支语句即可。如下面的例子:
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{
content}
</div>
);
当然,也可以使用三元表达式让代码看起来更优雅:
<div>
{
isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
如果你不需要使用else分支,你也可以像下面这样写:
<div>
{
isLoggedIn && <AdminPanel />}
{
isLogined ? <AdminPanel /> : null}
</div>
这些条件分支的写法,对于JSX中的属性同样管用。
6 渲染列表
加入你有如下一个产品数据列表:
const products = [
{
title: 'Cabbage', id: 1 },
{
title: 'Garlic', id: 2 },
{
title: 'Apple', id: 3 },
];
在React组件中,你可以使用map()
函数将这个数组转换成<li>
数组:
const listItems = products.map(product =>
<li key={
product.id}>
{
product.title}
</li>
);
return (
<ul>{
listItems}</ul>
);
注意:使用
map
遍历生成的<li>
组件有一个key
属性,**对于列表中的每个项,应该给key属性传入一个字符串或数字,在其兄弟项中唯一地标识该项。**通常,key的取值应该来自你的数据,比如数据库ID。如果你的组件在后续有插入、删除或重新排序项操作,React借助key能够分清楚谁是谁,从而优化渲染性能。
7 响应事件
你可以通过在组件中声明事件处理函数来响应事件:
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={
handleClick}>
Click me
</button>
);
}
注意:
onClick={handleClick}
中的handleClick
后面没有圆括号,你不需要在这里加上圆括号。
8 更新屏幕
通常情况下,你希望你的组件能够记住组件中的一些数据并且将其展示出来。例如,你希望计算一个按钮被点击的次数,为了实现这个目的,你需要给组件添加一个state
(状态),需要使用到第一个hook
函数——useState
:
import {
useState } from 'react';
然后你可以像下面这样声明一个变量count
和对应的更新函数setCount
,这些都是useState
提供的,只需要像下面这样写:
function MyButton() {
const [count, setCount] = useState(0);
然后,你就得到了两样东西:当前的状态(count
)和该状态的更新函数setCount
,你可以随意设置他们的名称,不过一般都是按照[something, setSomething]
的风格进行命名。
MyButon组件第一次被渲染时,将显示count
的默认值0
。如果你希望更新count
的值,就调用setCount()
方法更新其取值:
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={
handleClick}>
Clicked {
count} times
</button>
);
上面的代码已经实现了一个五脏俱全的带有state
的React组件。当你点击按钮时,count
的取值将会+1。如果你多次渲染同一个组件,每个组件都会有自己独立的状态,如下面的代码:
import {
useState } from 'react';
export default function MyApp() {
return (
<div>
<h1>Counters that update separately</h1>
<MyButton />
<MyButton />
</div>
);
}
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={
handleClick}>
Clicked {
count} times
</button>
);
}
运行结果:
9 使用Hooks
约定俗成,以use
开头的函数被称为Hooks
,useState
是一个React内置的钩子函数,在后续的学习中你会学到更多的钩子函数,也可以在API reference中查看。你也可以通过组合现有的hooks来编写自己的hook。
相较于其它函数,hooks
的限制更大:你只能在组件(或者其他hook)的顶部调用hook,如果你想在条件或者循环中使用useState
,需要提取一个新的组件并且将useState
放在正确的位置,否则React会给出警告甚至错误。
10 在组件中共享数据
在前面的栗子中,MyButton
组件独享其中的数据count
,因此点击其中一个MyButton
组件,并不会影响另外一个的取值:
那么问题来了,如果你希望让两个组件共享数据,应该怎么做呢?
如果希望两个兄弟MyButton
组件共享数据,你需要将state上移到他们的公共父组件MyApp
中:
现在,当你点击任意一个按钮,MyApp
组件中的count
就会更新,同时两个组件中的count
值都会同时更新。那么,代码要怎么写呢?看下面的代码:
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update separately</h1>
<MyButton count={
count} onClick={
handleClick} />
<MyButton count={
count} onClick={
handleClick} />
</div>
);
}
注意变动的内容:1、将count
和setCount
从MyButton
组件上移到了MyApp
中;2、在MyButton
组件中添加了count
和onClick
(这些传递的数据或者响应函数被称为props
),count
是父组件传递给子组件的count
的取值;onClick
是事件处理函数,一旦点击了MyButton
中的按钮,在父组件中就会执行一次handleClick
函数。
而MyButton
组件将会变成下面这样:
function MyButton({
count, onClick }) {
return (
<button onClick={
onClick}>
Clicked {
count} times
</button>
);
}
上述代码运行结果如下图:
本文完,(▽)。