1、用 0 做条件渲染
这可能是很多新手都遇到过的问题了吧!鄙人当年也犯过这个错误,但你说它是错误,也可以说是react的一个坑:0 是假值,却不能做条件渲染。
看下面这个例子:
ray-so-export (11).png
可能你想当然他会在items为空数组的时候显示ShoppingList组件。但实际上却显示了一个 0!这是因为 0 在JavaScript中是一个假值,&&操作符短路了,整个表达式被解析为 0。它就等价于:
function App() {
return (
{0}
);
}
复制代码
与其他假值(”、null、false等)不同list转json,数字 0 在JSX中是一个有效值。毕竟,在很多情况下,我们确实想打印数字 0!
正确的做法是:
function App() {
const [items, setItems] = React.useState([]);
return (
{items.length > 0 && (
)}
);
}
// 或者用三元运算符
function App() {
const [items, setItems] = React.useState([]);
return (
{items.length
?
: null}
);
}
复制代码
2、突变状态
先来看这个常见的页面:
image.png
代码:
ray-so-export (12).png
每当增加一个新项目时,handleAddItem函数就会被调用。但是,它并不起作用!当我们输入一个项目并提交表单时,该项目没有被添加到购物清单中。
问题就在于我们违反了也许是React中最核心的原则 —— 不可变状态。React依靠一个状态变量的地址来判断状态是否发生了变化。当我们把一个项目推入一个数组时,我们并没有改变该数组的地址,所以React无法判断该值已经改变。
正确的做法是:
function handleAddItem(value) {
const nextItems = [...items, value];
setItems(nextItems);
}
复制代码
不建议修改一个现有的数组,而是从头开始创建一个新的数组。这里的区别在于编辑一个现有的数组和创建一个新的数组之间的区别。
同样的,对于对象类型的数据也是:
// ❌ 不建议
function handleChangeEmail(nextEmail) {
user.email = nextEmail;
setUser(user);
}
// ✅ 推荐
function handleChangeEmail(email) {
const nextUser = { ...user, email: nextEmail };
setUser(nextUser);
}
复制代码
为什么react不推荐突变状态
3、唯一的 key
你肯定经常会在控制台看到一个警告:
Warning: Each child in a list should have a unique "key" prop.
复制代码
比如:
ray-so-export (13).png
控制台就会报警告:
image.png
每当我们渲染一个元素数组时list转json,我们需要向React提供一些额外的上下文,以便它能够识别每一个项目,通常就是需要一个唯一的标识符。
你可以这么做:
function ShoppingList({ items }) {
return (
{items.map((item, index) => {
return (
- {item}
);
})}
);
}
复制代码
但这并不是个推荐的做法。这种方法有时会奏效,但在有些情况下会造成一些相当大的问题。随着你对React的工作原理有了更深的了解,你就能根据具体情况来判断它是否没问题。
有一种绝对安全的方式来解决这个问题:
function handleAddItem(value) {
const nextItem = {
id: crypto.randomUUID(),
label: value,
};
const nextItems = [...items, nextItem];
setItems(nextItems);
}
复制代码
crypto.randomUUID是一个内置于浏览器的方法(它不是一个第三方包)。它在所有主要浏览器中都可用。这个方法会生成一个独特的字符串,比如:d9bb3c4c-0459-48b9-a94c-7ca3963f7bd0。通过在用户提交表单时动态生成一个ID,我们保证了购物清单中的每一个项目都有一个唯一的ID。
所以,更好的做法是:
function ShoppingList({ items }) {
return (
{items.map((item, index) => {
return (
{item.label}
);
})}
);
}
复制代码
你也最好不要耍小聪明,图方便,这么做:
// ❌ 不建议这么做
{item.label}
复制代码
像这样在JSX中生成它将导致key在每次渲染时都会改变。每当key发生变化时,React就会销毁并重新创建这些元素,这对性能会产生很大的负面影响。
这种模式,在第一次创建数据时生成key,可以应用于各种情况。例如,这里是我从服务器获取数据时创建唯一ID的方法:
async function retrieveData() {
const res = await fetch('/api/data');
const json = await res.json();
const dataWithId = json.data.map(item => {
return {
...item,
id: crypto.randomUUID(),
};
});
setData(dataWithId);
}
复制代码
4、空格缺失
你肯定遇到过这种场景:
import React from 'react';
function App() {
return (
Welcome to the new world!
<a href="/login">Log in to continue
);
}
export default App;
复制代码
image.png
注意“login in”前面,是没有空格的:
image.png
这是因为JSX编译器(将我们编写的JSX转化为对浏览器友好的JavaScript的工具)无法真正区分语法上的空白和我们为缩进/代码可读性而添加的空白。
正确的做法是:
Welcome to the new world!
{' '}
<a href="/login">Log in to continue
复制代码
tips:如果你使用 Prettier,它会自动为你添加这些空格字符!
5、改变状态后访问状态
这恐怕是react新手最常犯的错了吧:
import React from 'react';
function App() {
const [count, setCount] = React.useState(0);
function handleClick() {
setCount(count + 1);
console.log({ count });
}
return (
);
}
export default App;
复制代码
如果非要取怎么办呢?有办法:
function handleClick() {
const nextCount = count + 1;
setCount(nextCount);
console.log({ nextCount });
}
复制代码
6、返回多个元素
有时,一个组件需要返回多个顶层元素。
function LabeledInput({ id, label, ...delegated }) {
return (
<input
id={id}
{...delegated}
/>
);
}
export default LabeledInput;
复制代码
image.png
我们希望我们的LabeledInput组件能够返回两个元素:一个和一个。发生这种情况是因为JSX被编译成普通的JavaScript后是这样子:
function LabeledInput({ id, label, ...delegated }) {
return (
React.createElement('label', { htmlFor: id }, label)
React.createElement('input', { id: id, ...delegated })
);
}
复制代码
在JavaScript中,我们不允许像这样返回多个东西。这也是这个方法不可行的原因,就好比:
function addTwoNumbers(a, b) {
return (
"the answer is"
a + b
);
}
复制代码
正确的做法是:
function LabeledInput({ id, label, ...delegated }) {
return (
<input
id={id}
{...delegated}
/>
);
}
复制代码
7、非受控到受控的切换
来看一个比较典型的表单场景,将一个输入与一个React状态绑定:
import React from 'react';
function App() {
const [email, setEmail] = React.useState();
return (
<label htmlFor="email-input">
Email address
<input
id="email-input"
type="email"
value={email}
onChange={event => setEmail(event.target.value)}
/>
);
}
export default App;
复制代码
如果你开始键入,你会注意到一个控制台警告:
image.png
怎么解决这个问题?我们需要将我们的状态初始化为一个空字符串:
const [email, setEmail] = React.useState('');
复制代码
当我们设置了value属性时,等于就是告诉React,我们希望这是一个受控的组件。不过,这只有在我们传递给它一个定义好的值时才会起作用!通过将email初始化为一个空字符串,确保该值永远不会被设置为undefined。
8、行内样式缺少括号
JSX语法直观上与HTML很相似,但两者之间还是有一些不一样的地方。例如,如果你使用了class而不是className。还有就是样式,在HTML中,style被写成一个字符串:
<button style="color: red; font-size: 1.25rem">
Hello World
复制代码
然而,在JSX中,我们需要将其指定为一个对象,并使用camelCased(驼峰)属性名称。比如,你通常会这么做:
import React from 'react';
function App() {
return (
<button
style={ color: 'red', fontSize: '1.25rem' }
>
Hello World
);
}
export default App;
复制代码
然后那就会发现控制台报错了:
image.png
正确的做法是:
<button
// 用 "{{", 而不是 "{":
style={{ color: 'red', fontSize: '1.25rem' }}
>
Hello World
复制代码
为什么要这样做?在JSX中,我们可以把任何有效的JS表达式放在这个标签里。比如说:
<button className={isPrimary ? 'btn primary' : 'btn'}>
复制代码
无论我们在{}里面放了什么,都会被认为是JavaScript,结果将被设置为这个属性。className要么是’btn primary’,要么是’btn’。
如果我们把它分得更细一点,对象拉出来放到一个变量中会更清楚:
// 1. 创建一个样式属性对象
const btnStyles = { color: 'red', fontSize: '1.25rem' };
// 2. 把样式对象放到标签属性中
// 或者,一步到位
<button style={{ color: 'red', fontSize: '1.25rem' }}>
复制代码
9、useEffect 中的异步方法
假设我们在useEffect中请求API,从中获取一些服务端数据,通常需要将请求方法写成异步的,比如这样:
import React from 'react';
import { API } from './constants';
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json.user);
}, [userId]);
if (!user) {
return 'Loading…';
}
return (
- Name
- {user.name}
- Email
- {user.email}
);
}
export default UserProfile;
复制代码
然后你就会发现控制台这样了:
image.png
你肯定想,不就缺少个async关键字吗:
React.useEffect(async () => {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json);
}, [userId]);
复制代码
不幸的是,这仍然不起作用;你将会得到一个新的错误信息:
destroy is not a function
复制代码
正确的做法应该是:
React.useEffect(() => {
async function runEffect() {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json);
}
runEffect();
}, [userId]);
复制代码
10、未及时取消事件绑定
当使用useEffect()来管理副作用时,一定要记得自己手动清理一下。如果不这样做,会导致内存泄漏和其他问题。
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
复制代码
正确的做法是:
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}
}, []);
复制代码
结语
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,关注我,拉你进群,有5000多名前端小伙伴在等着一起学习哦 –>
模拟面试、简历指导可私信找我,已帮助100+名同学,脱胎换骨~
限时特惠:本站每日持续更新海量展厅资源,一年会员只需29.9元,全站资源免费下载
站长微信:zhanting688