本文主要介绍了 React 的基本使用方法,可以帮助你快速入门 React 的开发。本文适合对 React 有兴趣但没有太多经验的小白读者,也可以作为Vue转React的学习指南。希望本文能让你对 React 有一个基本概念,并且能直接进入实战开发。
文件结构
先概览一下React的tsx组件文件,相关用法后面细说
这是一个简单的React组件文件
-
【定义组件】React 组件是常规的 JavaScript 函数,但 组件的名称必须以大写字母开头,否则它们将无法运行!
示例中定义了两个组件,并且在
Gallery
组件中使用了Profile
组件 -
【导出组件】通过
export default
标签一个文件中的主要函数以便你以后可以从其他文件引入它。这个文件导出了一个
Gallery
组件 -
【组件嵌套】可以将多个组件保存在同一份文件中。当组件相对较小或彼此紧密相关时,这是一种省事的处理方式。如果这个文件变得臃肿,也可以随时将
Profile
移动到单独的文件中。组件可以渲染其他组件,但是 请不要嵌套他们的定义:
export default function Gallery() { // 🔴 永远不要在组件中定义组件 function Profile() {// ...} // ... }
应该在顶层定义每个组件
export default function Gallery() {// ...} // ✅ 在顶层声明组件 function Profile() {// ...}
UI界面
数据显示
JSX 中,大括号{}让你可以将 JavaScript 的逻辑和变量带入到标签中。 动态指定 src 或 alt 的值
export default function Avatar() {
const avatar = 'https://i.imgur.com/7vQD0fPs.jpg';
const description = 'Gregorio Y. Zara';
return (
<img
className="avatar"
src={avatar}
alt={description}
/>
);
}
在JSX中使用【变量】或者直接调用【方法】
const today = new Date();
function formatDate(date) {
return new Intl.DateTimeFormat(
'zh-CN',
{ weekday: 'long' }
).format(date);
}
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
// 引入变量
<h1>{name}'s To Do List</h1>
// 调用方法
<h1>To Do List for {formatDate(today)}</h1>
);
}
给标签加style ,注意这边有两个花括号。
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}
为什么style是{{ }} ?
{{ 和 }} 并不是什么特殊的语法:它只是包在 JSX 大括号内的 JavaScript 对象。
{ backgroundColor: 'black', color: 'pink' }
条件渲染
使用if语句
function Item({ name, isPass }) {
if (isPass) {
return <div className="item">通过:{name} </div>;
}
return <div className="item">未通过:{name} </div>;
}
export default function PackingList() {
return (
<section>
<Item
isPass={true}
name="张三"
/>
</section>
);
}
一个组件必须返回一些东西,如果不想有任何东西进行渲染,可以直接返回 null
if (isPass) { return null; } return <div className="item">{name}</div>;
三目运算符(? :)
{cond ? <A /> : <B />} 表示 “当 cond 为真值时, 渲染 <A />,否则 <B />”
// 用三目运算符改写上面的代码
return (
<>
{isPass
?
(<div className="item">通过:{name} </div>):
(<div className="item">未通过:{name} </div>)
}
</>
);
与运算符(&&)
{cond && <A />} 表示 “当 cond 为真值时, 渲染 <A />,否则不进行渲染”。
//如果只有一个结果,可以直接使用&&做判断
return (
<>
{isPass&&(<div className="item">通过:{name} </div>)}
</>
);
注意!!!,使用&&时,如果左侧是0,React 此时的渲染会是0,例如:
messageCount && <p>New messages</p>
// ❌ messageCount为0,结果会变成0,而不是渲染p标签。
messageCount > 0 && <p>New messages</p>
// ✅ 可以将左侧的值改成布尔类型
列表渲染
使用map循环
可以直接在html中使用map循环DOM,只需要加一个大括号。
const people=[{id:0,name:'凯瑟琳'},{id:1,name:'马里奥'},{id:2,name:'穆罕'}]
export default function List() {
return(
<ul>
{
people.map(person =><li key={person.id}>{person.name}</li>)
}
</ul>);
}
直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值!——它可以是字符串或数字的形式,只要能唯一标识出各个数组项就行
如果循环的时候不想写额外的DOM,可以使用<Fragment>
function Blog() {
return posts.map(post =>
<Fragment key={post.id}>
<PostTitle title={post.title} />
<PostBody body={post.body} />
</Fragment>
);
}
通常使用 <>...</> 代替,它们都允许你在不添加额外节点的情况下将子元素组合。
- 但是如果要传递 key 给一个 ,则不能使用 <>...</> 代替,从 'react' 中导入 Fragment 且表示为
<Fragment key={yourKey}>...</Fragment>
function Post() { return ( <> <PostTitle /> <PostBody /> </> ); }
关于key
1、key怎么来?
- 后端直接返回。
- 前端自己生成。
2、key 需要满足的条件
- ❌ 同一个数组的key不能重复
- ❌ key的值不能改变,不要在渲染时动态地生成 key,如直接使用数组下标,或者像是 key={Math.random()} 这种方式。这会导致每次重新渲染后的 key 值都不一样,从而使得所有的组件和 DOM 元素每次都要重新创建。
3、为什么需要key?
key是为了帮助React识别元素的唯一性,以便更有效地渲染和更新列表。
- 如果不使用key,当你对列表进行修改时,React会尝试重新渲染整个列表。因为如果没有key,React无法确定哪个元素发生了变化,哪个元素被删除或添加了。这可能导致不必要的性能问题,并且可能会导致界面上的错误。
- 如果使用了key,React就可以根据每个元素的唯一标识符来准确地识别哪些元素发生了变化,哪些元素被删除或添加了。这样,React只会重新渲染需要更新的部分,从而提高了性能并确保界面的正确显示。
如果没有显式地指定 key 值,React 默认会把数组项的索引当作 key 值来用,但是数组项的顺序在插入、删除或者重新排序等操作中会发生改变。
使用filter过滤
现在我们使用map和filter,只显示出化学家的人
const people=[{id:0,name:'凯瑟琳',profession:'化学家'},{id:1,name:'马里奥',profession:'数学家'},{id:2,name:'穆罕',profession:'化学家'}]
const chemists = people.filter(person =>
person.profession === '化学家'
);
export default function List() {
return(
<ul>
{
chemists.map(person =><li key={person.id}>{`化学家:${person.name}`}</li>)
}
</ul>);
}
添加交互
添加事件
普通用法
export default function Button() {
function handleClick() {
alert('你点击了我!');
}
return (
<button onClick={handleClick}>
点我
</button>
);
}
定义内联事件
<button onClick={function handleClick() {
alert('你点击了我!');
}}>
箭头函数,可以利用这种方式给函数传参
<button onClick={() => handleClick(name)}}>
注意:这样调用是错误的
❌
<button onClick={handleClick()}>
handleClick() 中最后的 () 会在 渲染 过程中 立即 触发函数,即使没有任何点击
父子组件传参
父传子
父组件写prop,子组件函数接收props
// 子组件,接收props对象,即props.message
// onClick执行【父组件】的方法
function AlertButton({ message, onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
export default function Toolbar() {
function handlePlayClick() {
alert(`事件传递!`);
}
return (
<div>
// 父组件,传递自定义属性message。
// 事件方法也可以直接传递
<AlertButton message="正在播放!" onClick={handlePlayClick}>
播放电影
</AlertButton>
<AlertButton message="正在上传!" onClick={handlePlayClick}>
上传图片
</AlertButton>
</div>
);
}
组件的事件可以自定义名字,按照惯例,事件处理函数 props 应该以 on 开头,后跟一个大写字母。 例如:
// 这边注意名字要改成自定义的名字,onSmash function AlertButton({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } // 这边我们把点击事件改名为onSmash,最后结果是一样的 ... <AlertButton message="正在播放!" onSmash={handlePlayClick}> 播放电影 </AlertButton> ...
数据更新
基本类型
在react中添加一个变量要这样做
/*
* state 变量 (index) 会保存上次渲染的值。
* state setter 函数 (setIndex) 可以更新 state 变量并触发 React 重新渲染组件。
*/
const [index, setIndex] = useState(0); //等同于 let index = 0;
值更新要这样做
function handleClick() {
setIndex(index + 1);
}
关于命名,可以随意命名但是不要这么做,约定的命名方式为
const [thing, setThing] // thing 代表自定义的变量名
使用useState,才能保证值和页面渲染同时更新。
在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook
useState,是react自带的hook
更新对象
当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。
普通对象
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
// 假如要更新对象中的firstName字段,应该这样做
setPerson({
...person, // 复制上一个 person 中的所有字段
firstName: e.target.value // 覆盖 firstName 字段
});
... 展开语法本质是是“浅拷贝”——它只会复制一层。也就是说你想要更新一个嵌套属性时,必须得多次使用展开语法。
嵌套对象
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
// 想要更新 person.artwork.city 的值,需要这样做
setPerson({
...person, // 复制其它字段的数据
artwork: { // 替换 artwork 字段
...person.artwork, // 复制之前 person.artwork 中的数据
city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!
}
});
如果觉得这样写太麻烦,可以引入Immer
注意!!
- 你应该 把所有存放在 state 中的 JavaScript 对象都视为只读的。
更新数组
添加数组元素
const [artists, setArtists] = useState([]);
setArtists( // 替换 state
[ // 是通过传入一个新数组实现的
...artists, // 新数组包含原数组的所有元素
{ id: nextId++, name: name } // 并在末尾添加了一个新的元素
]
);
删除数组元素
const [artists, setArtists] = useState(
[
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye'},
{ id: 2, name: 'Louise Nevelson'},
]
);
setArtists(
artists.filter(a => a.id !== artist.id)
);
插入元素
const [artists, setArtists] = useState(
[
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye'},
{ id: 2, name: 'Louise Nevelson'},
]
);
const nextArtists = [
// 插入点之前的元素:
...artists.slice(0, insertAt),
// 新的元素:
{ id: nextId++, name: name },
// 插入点之后的元素:
...artists.slice(insertAt)
]
setArtists(nextArtists);
同样可以直接使用引入Immer遍写简洁的更新逻辑
减少没必要的state变量
并不是所有变量都需要使用state
例如:以下示例的期望是,当按钮被点击时,询问用户的名字,然后显示一个 alert 欢迎他们
// ❌ 执行下面代码会发现,始终显示“Hello, !”。
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState('');
function handleClick() {
setName(prompt('What is your name?'));
alert(`Hello, ${name}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}
上面的代码为什么不起作用? 说明
// ✅ 修改,直接使用普通变量 name 即可
export default function FeedbackForm() {
function handleClick() {
const name = prompt('What is your name?'); //普通变量
alert(`Hello, ${name}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}
State 变量仅用于在组件重渲染时保存信息。
- 在单个事件处理函数中,普通变量就足够了。
- 当普通变量运行良好时,不要引入 state 变量。
关于hook的调用
Hooks ——以 use 开头的函数——只能在组件或自定义Hook的最顶层调用。 不能在条件语句、循环语句或其他嵌套函数内调用 Hook
例如:
// ❌ 以下这段代码,点击提交时,它会发生崩溃并显示错误消息“渲染的 hooks 比预期的少”
import { useState } from 'react';
export default function FeedbackForm() {
const [isSent, setIsSent] = useState(false);
if (isSent) {
return <h1>Thank you!</h1>;
} else {
// ❌ 不能在这里定义变量
const [message, setMessage] = useState('');
return (
<form onSubmit={e => {
e.preventDefault();
alert(`Sending: "${message}"`);
setIsSent(true);
}}>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<br />
<button type="submit">Send</button>
</form>
);
}
}
Error Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
问题分析:上面报错的原因是,Hook 只能在组件函数的顶层调用。但是 message 的定义位于一个条件语句中。
// ✅ 将其移出条件语句以解决问题
import { useState } from 'react';
export default function FeedbackForm() {
const [isSent, setIsSent] = useState(false);
const [message, setMessage] = useState('');
...
}
保证对 Hook 的所有调用都发生在第一个 return 前
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐
👉 Vite基础入门