读完React新文档后的8条收获
不久前,React官网进行了重构。新官网将hook作为介绍的重头戏,重新通读了一遍react文档,有了一些自己的感悟。在此分享出来与各位共勉。
1. 换个角度认识Props与State
Props
与State
是React中两个略有相似的概念。在一个React组件中,它们作为数据来源可能同时存在,但它们之间有着巨大不同:
Props
更像函数中的参数。它主要用于组件之间的信息传递,它可以是任意类型的数据:数字、文本、函数、甚至于组件等等。State
更像组件中的内存。它可以保存组件内部的状态,组件通过跟踪这些状态,从而渲染更新。
import React, { useState } from 'react';
// 父组件
const ParentComponent = () => {
const [count, setCount] = useState(0); // 使用state来追踪count的值
return (
<div>
<ChildComponent age={25} />
<p>Count: {count}</p>
</div>
);
};
// 子组件
const ChildComponent = (props) => {
const { age } = props; // 使用props来获取父组件传递的数据
return (
<div>
<p>Age: {age}</p>
</div>
);
};
2. 不要嵌套定义组件
在一个组件中直接定义其他组件,可以省去很多传递Props的工夫,看上去很好。但我们不应该嵌套定义组件,原因在于**嵌套定义组件会导致渲染速度变慢,也更容易出现BUG**。
我们在嵌套定义组件的情况下,当父组件更新时,内部嵌套的子组件也会重新生成并渲染,子组件内部的state也会丢失。针对这种情况,我们可以通过两种方式来解决:
- 为子组件包上
useMemo
,避免不必要的更新; - 不再嵌套定义,将子组件提至父组件外,通过Props传参。
这里更推荐第二种方法,因为这样代码更少,结构也更清晰,组件迁移维护都会更方便一些。
//🔴 Bad Case
export default function Gallery() {
function Profile() {
// ...
}
// ...
}
//✅ Good Case
function Profile() {
// ...
}
export default function Gallery() {
// ...
}
3. 尽量不要使用匿名函数组件
因为类似export default () => {}
的匿名函数组件书写虽然方便,但会让debug变得更困难。
如下是两种不同类型组件出错时的控制台的表现:
具名组件出错时的提示,可直接的指出错的函数组件名称:
匿名函数出错时的提示,无法直观确定页面内哪个组件出了错:
4. 使用逻辑运算符&&编写JSX时,左侧最好不要是数字
运算符&&
在JSX中的表现与JS略有不同:
- 在JS中,只有在
&&
左侧的值不为0
、null
、undefined
、false
、''
等假值时,才会返回右侧值; - 在JSX中,React对于
false
、null
、undefined
并不会做渲染处理,所以进行&&
运算后,如果左侧值为假被返回后,会出现与直觉不同的渲染结果:可能会碰到页面上莫名奇妙出现了一个0
的问题。为了避免出现这种情况,可以在书写JSX时,为左侧的值加上!!
来进行强制类型转换。
const flag = 0
//🔴 Bad Case
{
flag && <div>123</div>
}
//✅ Good Case 1
{
!!flag && <div>123</div>
}
//✅ Good Case 2
{
flag > 0 && <div>123</div>
}
关于JSX对各种常见假值的渲染,这里进行了总结:
null
、undefined
、false
:这些值在JSX中会被视为空,不会渲染任何内容。它们会被忽略,并且不会生成对应的DOM元素。0
、NaN
:这些值会在页面上渲染为字符串"0"、"NaN"。''
、[]
、{}
:空字符串会被渲染为什么都没有,不会显示任何内容。
注:这里感谢@小明家的bin的评论提醒,他的见解对我起到了很大的启发作用。
5.全写的 Fragment标签上可以添加属性key
在map循环添加组件时,需要为组件添加key
来优化性能。如果待循环的组件有多个时,可以在外层包裹<Fragment>...</Fragment>
标签,在其上添加key
添加在<Fragment>...</Fragment>
来避免创建额外的组件。
const list = [1,2,3]
//🔴 Bad Case
//不能添加key
{
list.map(v=><> <div>1-1</div> <div>1-2</div> </>)
}
//🔴 Bad Case
//创建了额外的div节点
{
list.map(v=><div key={v}> <div>1-1</div> <div>1-2</div> <div/>)
}
//✅ Good Case
{
list.map(v=><Fragment key={v}> <div>1-1</div> <div>1-2</div> </Fragment>)
}
注意简写的Fragment标签
<>...</>
上不支持添加key
6. 可以使用updater function,来在下一次渲染之前多次更新同一个state
React中处理状态更新时采用了批处理机制,在下一次渲染之前多次更新同一个state,可能不会达到我们想要的效果。如下是一个简单例子:
// 按照直觉一次点击后button中的文字应展示为3,但实际是1
function Demo(){
const [a,setA] = useState(0)
function handler(){
setA(a + 1);
setA(a + 1);
setA(a + 1);
}
return <button onclick={handler}>{a}</button>
}
在某些极端场景下,我们可能希望state的值能够及时发生变化,这时便可以采用setA(n => n + 1)
(这种形式被称为updater function)来进行更新:
// 一次点击后a的值会被更新为3
function Demo(){
const [a,setA] = useState(0)
function handler(){
setA(n => n + 1);
setA(n => n + 1);
setA(n => n + 1);
}
return <button onclick={handler}>{a}</button>
}
7. 管理状态的一些原则
更加结构化的状态有助于提高我们组件的健壮性,以下是一些管理组件内状态时可以参考的原则:
- 精简相关状态:如果在更新某个状态时总是需要同步更新其他状态变量,可以考虑将它们合并为一个状态变量。
- 避免矛盾状态:避免出现两个或多个状态的值互相矛盾的情况。
- 避免冗余状态:一个状态可以由其余状态计算而来时,其不应单独作为一个状态来进行管理。
- 避免状态重复:当相同的数据在多个状态变量之间或嵌套对象中重复时,很难使它们保持同步。尽可能减少重复。
- 避免深度嵌套状态:嵌套层级过深的状态更新时相对麻烦许多,尽量保持状态扁平化。
8. 使用useSyncExternalStore
订阅外部状态
useSyncExternalStore
是一个React18中新提供的一个Hook,可以用于订阅外部状态。
它的使用方式如下:
import { useSyncExternalStore } from 'react';
function MyComponent() {
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
// ...
}
useSyncExternalStore
接受三个参数:
subscribe
:一个订阅函数,用于订阅存储,并返回一个取消订阅的函数。getSnapshot
:一个从存储中获取数据快照的函数。在存储未发生变化时,重复调用getSnapshot
应该返回相同的值。当存储发生变化且返回值不同时,React会重新渲染组件。getServerSnapshot
(可选):一个在服务器端渲染时获取存储初始快照的函数。它仅在服务器端渲染和在客户端进行服务器呈现内容的hydration过程中使用。如果省略该参数,在服务器端渲染组件时会抛出错误。
举一个具体的例子,我们可能需要获取浏览器的联网状态,并期望在浏览器网络状态发生变化时做一些处理。使用useSyncExternalStore
可以很大程度上简化我们的代码,同时使逻辑更加清晰:
//🔴 Bad Case
function useOnlineStatus() {
// Not ideal: Manual store subscription in an Effect
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function updateState() {
setIsOnline(navigator.onLine);
}
updateState();
window.addEventListener('online', updateState);
window.addEventListener('offline', updateState);
return () => {
window.removeEventListener('online', updateState);
window.removeEventListener('offline', updateState);
};
}, []);
return isOnline;
}
function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
// ✅ GoodCase
function useOnlineStatus() {
return useSyncExternalStore(
subscribe, // React won't resubscribe for as long as you pass the same function
() => navigator.onLine, // How to get the value on the client
() => true // How to get the value on the server
);
}
function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}
结语
文章的最后,再来一次无废话总结:
- 更清晰地认识了Props与State之间的区别。Props更像是函数的参数,用于组件之间的信息传递;而State更像是组件内部的内存,用于保存组件的状态并进行渲染更新。
- 不推荐在一个组件内部嵌套定义其他组件,因为这样会导致渲染速度变慢并容易产生BUG。推荐将子组件提到父组件外部并通过Props传递数据。
- 尽量避免使用匿名函数组件,因为在出错时会增加调试的难度。具名组件的出错提示更加直观和准确。
- 在使用逻辑运算符&&编写JSX时,左侧最好不要是数字。在JSX中,0会被当作有效的值,而不是假值,为了避免出现问题,可以在左侧的值加上!!进行强制类型转换。
- 当在使用全写的Fragment标签时,可以给Fragment标签添加属性key,以优化性能和避免创建额外的组件。
- 使用updater function的方式进行状态更新,可以确保在下一次渲染之前多次更新同一个state。这样可以避免批处理机制带来的问题。
- 在管理组件内状态时,可以遵循一些原则,如精简相关状态、避免矛盾状态、避免冗余状态、避免状态重复等,以提高组件的健壮性和可维护性。
- 使用useSyncExternalStore可以订阅外部状态,它是React 18中新增的Hook。通过订阅函数、获取数据快照的函数以及获取服务器初始快照的函数,我们可以简化订阅外部状态的代码逻辑。
通过对React文档的深入学习和实践,我对React的理解更加深入了解,希望这些收获也能对大家在学习和使用React时有所帮助。
链接:https://juejin.cn/post/7233324859932442680
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。