Skip to content

edith-lee/react-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React实践总结

1、组件名称必须大写

React会把大写开头的标签当做组件处理,小写开头的当做HTML标签处理。

Vue框架中组件名称可以小写,注意区别。

2、组件return语句需要同行

如果组件内容较少,一行可以写完的可以不加小括号。

如果组件内容较多,需要使用小括号包裹。

3、在顶层定义组件,不用嵌套定义组件

组件可以渲染其他组件,但是不要嵌套他们的定义。

如果嵌套定义组件,每次外部组件渲染时,都会创建新的内部组件,导致内部组件的状态丢失。

永远要将组件定义在最上层并且不要把它们的定义嵌套起来。

另外也建议一个文件只写一个组件,比较好维护。

4、只能返回一个根元素

如果想要在一个组件中包含多个元素,需要用一个父标签把它们包裹起来。

尽量不要增加一个额外的 <div>,可能会影响页面样式,可以用 <></> 元素来代替。

注意:React.Fragment 是在 React 16.2 新增的新特性,旧版本并不支持。key 是唯一可以传递给 Fragment 的属性。

根本原因是:JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

5、props传递参数使用默认值

父组件通过props给子组件传值时,可以设置默认值,当父组件没有传递或者传递undefined时,会使用默认值。

当传递null或者0时,不会使用默认值,所以需要特别注意一下。

6、在JSX中切勿将数字放在 && 左侧

JSX 允许你在 JavaScript 中编写类似 HTML 的标签,从而使渲染的逻辑和内容可以写在一起,在 JSX 的大括号内的代码会被当做 JavaScript

当使用 && 表达式时,JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是 0,整个表达式将变成左侧的值,React 此时则会渲染 0 而不是不进行渲染,导致页面显示错误。

7、循环的jsx要加key

为了使react diff算法更高效,循环的jsx代码需要添加key属性,不加key属性静态检查可能不报错,但控制台会报错,页面为空。

  • key 值在兄弟节点之间必须是唯一的
  • 不要在渲染时动态地生成 key,否则就失去了使用 key 的意义
  • key值不要用index

8、使用严格模式检测不纯的计算

纯函数概念:

  • 只负责自己的任务。它不会更改在该函数调用前就已存在的对象或变量。
  • 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果。

React 假设我们编写的所有组件都是纯函数。也就是说,对于相同的输入,我们所编写的 React 组件必须总是返回相同的 JSX

当组件正在渲染时,永远不应该改变预先存在的变量或对象。

React 提供了 “严格模式”,在严格模式下开发时,它将会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件。

严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,可以用 <React.StrictMode> 包裹根组件。

9、阻止事件冒泡和在捕获阶段触发事件

绑定的事件处理函数会捕获任何来自子组件的事件。通常,事件会沿着树结构向上“冒泡”或“传播”。

React 中所有事件都会传播,除了 onScroll,它仅适用于附加到的 JSX 标签。

想要阻止事件冒泡使用e.stopPropagation()

想要在捕获阶段触发事件,可以通过在事件名称末尾添加Capture,即使子组件阻止了冒泡,依然可以捕获到。捕获事件对于路由或数据分析之类的代码很有用。

10、state 如同快照,setState表现出异步特征

组件的 state 在其表现出的特性上更像是一张快照。设置它不会更改你已有的 state 变量,但会等到事件处理函数中的所有代码都运行完毕,触发重新渲染。

设置 state 只会为下一次渲染变更 state 的值,在本次渲染中,调用 setState 之后,state 的值也仍然是旧的值。

一个 state 变量的值永远不会在某次渲染的过程中发生变化, 即使事件处理函数的代码是异步的。

渲染的过程是纯粹的。

如果需要在下次渲染之前多次更新同一个 state,可以传入一个根据队列中的前一个 state 计算下一个 state 的函数,而不是直接传入下一个state的值。

这样传入的这个函数成为更新函数,React 会将更新函数加入队列,在事件处理函数中的所有其他代码运行后,进行处理。

到了下一次渲染期间,React 会遍历更新函数的队列并计算出更新之后的最终state

11、setState更新引用类型

state 中可以保存任意类型的 JavaScript 值,包括对象和数组。

对于引用类型,不应该直接修改。而创建一个新的对象或数组,用新的对象或数组更新State

  • 可以使用展开语法复制对象
  • 可以使用会返回新数组的数组方法,例如:concat、[...arr]、filter、slice、map
  • 可以使用 Immer 第三方库 (原理是proxy数据代理)
  • 深拷贝

12、state保留和重置

各个组件的 state 是各自独立的。根据组件在 UI 树中的位置,React 可以跟踪哪些 state 属于哪个组件。

只要组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。注意是在 UI 树中的位置,而不是在 JSX 中的位置。

相同位置的不同组件会使 state 重置。

如果想在重新渲染时保留 state,几次渲染中的树形结构就应该相互“匹配”。

重置state的方法:

  • 将组件渲染在不同的位置
  • 给组件传递不同的key

13、ref的使用

当希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,使用ref

ref设置到DOM节点时,可以通过ref获取DOM节点,对DOM节点操作。

ref绑定到自定义组件上时,会返回null(注意与Vue不同,Vue绑定ref给自定义组件,可以获取到组件实例)

如果需要操作子组件的DOM,需要将ref传递进子组件,使用useImperativeHandle可以限制只暴露给父组件某些句柄

一般在事件处理回调函数里面读取ref,渲染期间读取 ref.current 会导致代码不可靠,因为在第一次渲染期间,DOM 节点尚未创建,因此 ref.current 将为 null。在渲染更新的过程中,DOM 节点还没有更新。所以读取它们还为时过早。

14、flushSync 强制同步更新state

思考这样的功能,往列表中添加一个新的子项,并将屏幕向下滚动到列表的新增加的子项,如果直接在setState后,加入ref滑动到最后一项,无法实现这个功能,每次只能滑动到列表新添加的前面一项。

这是因为setState后不会立即更新 DOM。因此,将列表滚动到最后一个元素时,新的子项还没有添加到state中。

此时需要使用flushSync,强制同步更新State,引起DOM更到最新的状态,再进行滑动。

15、尽量减少State的数量

移除不必要的 Effect 可以让代码更容易理解,运行得更快,并且更少出错。

如果一个值可以基于现有的 propsstate 计算得出,不要把它作为一个 state,而是在渲染期间直接计算这个值。

如果计算很耗时,使用 useMemo Hook优化。

16、自定义Hook

React 已有一些内置 Hook,比如useStateuseContextuseEffect等,有时可能需要一个用途更特殊的Hook,这时可以根据应用需求创建自己的 Hook。 是一种组件间共享逻辑的方法。

约定Hook 的名称必须以 后跟一个大写字母的use单词开头,保证始终能一眼识别出它是自定义Hook。并且自定义 Hook 名称应该有意义,尽量能让其他人很好地猜中自定义 Hook 的功能。

自定义 Hook 共享的是状态的逻辑,而不是状态本身。

17、高阶组件HOC

高阶组件(HOC)是 React 中用于复用组件逻辑的一种技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。作用也是共享逻辑。

高阶组件是参数为组件,返回值为新组件的函数。普通组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

自定义Hook和HOC都能用来进行逻辑复用,各自优缺点如下:

优点 缺点
HOC 不影响被包裹的组件的内部逻辑 传递给被包裹组件的 props 如果重名的话,会发生覆盖
自定义Hook 使用直观;不存在HOC的重名问题;能在return之外访问数据 /

18、异步组件React.lazy

React.lazy 函数提供像渲染常规组件一样处理动态引入(的组件)的方式。

React.lazy 接受一个函数,这个函数需要动态调用import()。必须返回一个Promise,该Promise 需要resolve一个default exportReact 组件。注意,React.lazy目前只支持默认导出。

可以与Suspense组件配合使用,在Suspense中渲染lazy组件,可以增加loading效果。

使用import语句,打包后会被分割到不同的包里,决定在哪引入代码分割需要确保选择的位置能够均匀地分割代码包而不会影响用户体验。一个比较好的实践基于路由进行代码分割,所以React.lazy一般与React Router这类的第三方库配合使用。

19、错误边界组件

部分UIJavaScript错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。

错误边界是一种React组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI

错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。

注意,错误边界无法捕获的错误:

  • 事件处理中的错误
  • 异步代码的错误,比如setTimeoutrequestAnimationFrame的回调
  • 服务端渲染的错误
  • 错误边界组件自身抛出的错误

错误边界组件目前只能编写在类组件中,在类组件中定义了static getDerivedStateFromError()componentDidCatch()这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。

其中static getDerivedStateFromError() 用于渲染备用 UIcomponentDidCatch()用于打印错误信息。

20、Profiler测量性能

使用 <Profiler> 包裹组件树,可以测量其渲染性能。

  • id:字符串,用于标识正在测量的 UI 部分。
  • onRenderonRender 回调函数,当包裹的组件树更新时,React 都会调用它。它接收有关渲染内容和所花费时间的信息,有6个参数
    • id:字符串,为 <Profiler> 树的 id 属性
    • phase:为 mountupdatenested-update 中之一。这可以让你知道组件树是首次挂载还是由于 propsstatehook 的更改而重新渲染
    • actualDuration:在此次更新中,渲染 <Profiler> 组件树的毫秒数。这可以显示子树在使用记忆化(例如 memouseMemo)后的效果如何。理想情况下,此值在挂载后应显著减少,因为许多后代组件只会在特定的 props 变化时重新渲染。
    • baseDuration:估算在没有任何优化的情况下重新渲染整棵 <Profiler> 子树所需的毫秒数。它通过累加树中每个组件的最近一次渲染持续时间来计算。此值估计了渲染的最差情况成本(例如初始挂载或没有使用记忆化的树)。将其与 actualDuration 进行比较,以确定记忆化是否起作用。
    • startTime:当 React 开始渲染此次更新时的时间戳
    • endTime:当 React 提交此次更新时的时间戳。此值在提交的所有 profiler 中共享,如果需要,可以对它们进行分组。

进行性能分析会增加一些额外的开销,仅在必要时使用,因此 在默认情况下,它在生产环境中是被禁用的。

<Profiler> 允许你编程式收集性能测量数据。如果你正在寻找一个交互式的性能分析工具,可以尝试使用 React 开发者工具 中的 Profiler 标签页。它提供了类似浏览器扩展程序的功能。

21、使用memo减少渲染

使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证.

两个参数:

  • Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。
  • 可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop

React.memo()useMemo()对比:

  • React.memo本质是一个高阶组件,作用是给定相同的 props 的情况下呈现相同的结果,减少不必要的渲染,它只检查 prop 的变化,并且默认是浅比较(如果需要深比较,可以使用第二个参数)。如果你用 React.memo 包装的函数组件在其实现中有 useStateuseReduceruseContext Hook,那么当状态或上下文发生变化时,它仍然会重新渲染。
  • useMemo()是对一个值的记忆,传递一个create函数和一个依赖数组,useMemo仅当依赖项之一发生更改时才会重新计算记忆值,作用是减少不必要的计算
  • 相同注意点:React官网说可以依赖这两个开展性能优化,但不保证。

22、使用useCallback缓存函数

useCallback 是一个允许你在多次渲染中缓存函数的 React Hook

两个参数:

  • fn:想要缓存的函数。React 将会在初次渲染而非调用时返回该函数。当进行下一次渲染时,如果 dependencies 相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数。你可以自己决定何时调用以及是否调用。
  • dependencies:更新 fn 的依赖列表。依赖的响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。

注意:不必在任何方法都添加 useCallback,会降低代码可读性。

可以使用useCallback情况:

  • 传递给子组件的方法,每次父组件状态变化,会引起子组件的渲染,可以使用memo优化,但是父组件每次渲染,都生成一个函数,导致memo的优化不会生效,用useCallback处理传递给子组件的函数,就可以确保它在多次重新渲染之间是相同的函数(前提是依赖项不变)
  • Effect 中调用的函数:Effect中使用的每个响应值都必须写清依赖。但是如果将某个函数声明为Effect的依赖,它会导致Effect不断触发。
  • 优化自定义 Hook:如果编写一个 自定义 Hook,建议将它返回的任何函数包裹在 useCallback 中,这确保了 Hook 的使用者在需要时能够优化自己的代码。

useMemo和useCallback对比:

  • useMemo 缓存函数调用的结果。
  • useCallback 缓存函数本身。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages