React
会把大写开头的标签当做组件处理,小写开头的当做HTML
标签处理。
Vue
框架中组件名称可以小写,注意区别。
如果组件内容较少,一行可以写完的可以不加小括号。
如果组件内容较多,需要使用小括号包裹。
组件可以渲染其他组件,但是不要嵌套他们的定义。
如果嵌套定义组件,每次外部组件渲染时,都会创建新的内部组件,导致内部组件的状态丢失。
永远要将组件定义在最上层并且不要把它们的定义嵌套起来。
另外也建议一个文件只写一个组件,比较好维护。
如果想要在一个组件中包含多个元素,需要用一个父标签把它们包裹起来。
尽量不要增加一个额外的 <div>
,可能会影响页面样式,可以用 <></>
元素来代替。
注意:React.Fragment
是在 React 16.2 新增的新特性,旧版本并不支持。key
是唯一可以传递给 Fragment
的属性。
根本原因是:JSX
虽然看起来很像 HTML
,但在底层其实被转化为了 JavaScript
对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX
标签必须要用一个父元素或者 Fragment
来包裹。
父组件通过props
给子组件传值时,可以设置默认值,当父组件没有传递或者传递undefined
时,会使用默认值。
当传递null
或者0
时,不会使用默认值,所以需要特别注意一下。
JSX
允许你在 JavaScript
中编写类似 HTML
的标签,从而使渲染的逻辑和内容可以写在一起,在 JSX
的大括号内的代码会被当做 JavaScript
。
当使用 &&
表达式时,JavaScript
会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是 0
,整个表达式将变成左侧的值,React 此时则会渲染 0
而不是不进行渲染,导致页面显示错误。
为了使react diff
算法更高效,循环的jsx
代码需要添加key
属性,不加key
属性静态检查可能不报错,但控制台会报错,页面为空。
key
值在兄弟节点之间必须是唯一的- 不要在渲染时动态地生成
key
,否则就失去了使用key
的意义 key
值不要用index
纯函数概念:
- 只负责自己的任务。它不会更改在该函数调用前就已存在的对象或变量。
- 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果。
React
假设我们编写的所有组件都是纯函数。也就是说,对于相同的输入,我们所编写的 React 组件必须总是返回相同的 JSX
。
当组件正在渲染时,永远不应该改变预先存在的变量或对象。
React
提供了 “严格模式”,在严格模式下开发时,它将会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件。
严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,可以用 <React.StrictMode>
包裹根组件。
绑定的事件处理函数会捕获任何来自子组件的事件。通常,事件会沿着树结构向上“冒泡”或“传播”。
在 React
中所有事件都会传播,除了 onScroll
,它仅适用于附加到的 JSX
标签。
想要阻止事件冒泡使用e.stopPropagation()
想要在捕获阶段触发事件,可以通过在事件名称末尾添加Capture
,即使子组件阻止了冒泡,依然可以捕获到。捕获事件对于路由或数据分析之类的代码很有用。
组件的 state
在其表现出的特性上更像是一张快照。设置它不会更改你已有的 state
变量,但会等到事件处理函数中的所有代码都运行完毕,触发重新渲染。
设置 state
只会为下一次渲染变更 state
的值,在本次渲染中,调用 setState
之后,state
的值也仍然是旧的值。
一个 state
变量的值永远不会在某次渲染的过程中发生变化, 即使事件处理函数的代码是异步的。
渲染的过程是纯粹的。
如果需要在下次渲染之前多次更新同一个 state
,可以传入一个根据队列中的前一个 state
计算下一个 state
的函数,而不是直接传入下一个state
的值。
这样传入的这个函数成为更新函数,React
会将更新函数加入队列,在事件处理函数中的所有其他代码运行后,进行处理。
到了下一次渲染期间,React
会遍历更新函数的队列并计算出更新之后的最终state
state
中可以保存任意类型的 JavaScript
值,包括对象和数组。
对于引用类型,不应该直接修改。而创建一个新的对象或数组,用新的对象或数组更新State
。
- 可以使用展开语法复制对象
- 可以使用会返回新数组的数组方法,例如:
concat、[...arr]、filter、slice、map
- 可以使用
Immer
第三方库 (原理是proxy数据代理) - 深拷贝
各个组件的 state
是各自独立的。根据组件在 UI
树中的位置,React
可以跟踪哪些 state
属于哪个组件。
只要组件还被渲染在 UI
树的相同位置,React
就会保留它的 state
。注意是在 UI
树中的位置,而不是在 JSX
中的位置。
相同位置的不同组件会使 state
重置。
如果想在重新渲染时保留 state
,几次渲染中的树形结构就应该相互“匹配”。
重置state
的方法:
- 将组件渲染在不同的位置
- 给组件传递不同的
key
当希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,使用ref
ref
设置到DOM
节点时,可以通过ref
获取DOM
节点,对DOM
节点操作。
ref
绑定到自定义组件上时,会返回null
(注意与Vue
不同,Vue
绑定ref
给自定义组件,可以获取到组件实例)
如果需要操作子组件的DOM
,需要将ref
传递进子组件,使用useImperativeHandle
可以限制只暴露给父组件某些句柄
一般在事件处理回调函数里面读取ref
,渲染期间读取 ref.current
会导致代码不可靠,因为在第一次渲染期间,DOM
节点尚未创建,因此 ref.current
将为 null
。在渲染更新的过程中,DOM
节点还没有更新。所以读取它们还为时过早。
思考这样的功能,往列表中添加一个新的子项,并将屏幕向下滚动到列表的新增加的子项,如果直接在setState
后,加入ref
滑动到最后一项,无法实现这个功能,每次只能滑动到列表新添加的前面一项。
这是因为setState
后不会立即更新 DOM
。因此,将列表滚动到最后一个元素时,新的子项还没有添加到state
中。
此时需要使用flushSync
,强制同步更新State
,引起DOM
更到最新的状态,再进行滑动。
移除不必要的 Effect
可以让代码更容易理解,运行得更快,并且更少出错。
如果一个值可以基于现有的 props
或 state
计算得出,不要把它作为一个 state
,而是在渲染期间直接计算这个值。
如果计算很耗时,使用 useMemo Hook
优化。
React
已有一些内置 Hook
,比如useState
,useContext
,useEffect
等,有时可能需要一个用途更特殊的Hook
,这时可以根据应用需求创建自己的 Hook
。
是一种组件间共享逻辑的方法。
约定Hook
的名称必须以 后跟一个大写字母的use
单词开头,保证始终能一眼识别出它是自定义Hook
。并且自定义 Hook
名称应该有意义,尽量能让其他人很好地猜中自定义 Hook
的功能。
自定义 Hook
共享的是状态的逻辑,而不是状态本身。
高阶组件(HOC)是 React
中用于复用组件逻辑的一种技巧。HOC
自身不是 React API
的一部分,它是一种基于 React
的组合特性而形成的设计模式。作用也是共享逻辑。
高阶组件是参数为组件,返回值为新组件的函数。普通组件是将 props
转换为 UI
,而高阶组件是将组件转换为另一个组件。
自定义Hook和HOC都能用来进行逻辑复用,各自优缺点如下:
优点 | 缺点 | |
---|---|---|
HOC | 不影响被包裹的组件的内部逻辑 | 传递给被包裹组件的 props 如果重名的话,会发生覆盖 |
自定义Hook | 使用直观;不存在HOC的重名问题;能在return之外访问数据 | / |
React.lazy
函数提供像渲染常规组件一样处理动态引入(的组件)的方式。
React.lazy
接受一个函数,这个函数需要动态调用import()
。必须返回一个Promise
,该Promise
需要resolve
一个default export
的 React
组件。注意,React.lazy
目前只支持默认导出。
可以与Suspense
组件配合使用,在Suspense
中渲染lazy
组件,可以增加loading
效果。
使用import
语句,打包后会被分割到不同的包里,决定在哪引入代码分割需要确保选择的位置能够均匀地分割代码包而不会影响用户体验。一个比较好的实践基于路由进行代码分割,所以React.lazy
一般与React Router
这类的第三方库配合使用。
部分UI
的JavaScript
错误不应该导致整个应用崩溃,为了解决这个问题,React 16
引入了一个新的概念 —— 错误边界。
错误边界是一种React
组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript
错误,并打印这些错误,同时展示降级 UI
。
错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。
注意,错误边界无法捕获的错误:
- 事件处理中的错误
- 异步代码的错误,比如
setTimeout
或requestAnimationFrame
的回调 - 服务端渲染的错误
- 错误边界组件自身抛出的错误
错误边界组件目前只能编写在类组件中,在类组件中定义了static getDerivedStateFromError()
或componentDidCatch()
这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。
其中static getDerivedStateFromError()
用于渲染备用 UI
,componentDidCatch()
用于打印错误信息。
使用 <Profiler>
包裹组件树,可以测量其渲染性能。
id
:字符串,用于标识正在测量的UI
部分。onRender
:onRender
回调函数,当包裹的组件树更新时,React
都会调用它。它接收有关渲染内容和所花费时间的信息,有6个参数id
:字符串,为<Profiler>
树的id
属性phase
:为mount
、update
或nested-update
中之一。这可以让你知道组件树是首次挂载还是由于props
、state
或hook
的更改而重新渲染actualDuration
:在此次更新中,渲染<Profiler>
组件树的毫秒数。这可以显示子树在使用记忆化(例如memo
和useMemo
)后的效果如何。理想情况下,此值在挂载后应显著减少,因为许多后代组件只会在特定的props
变化时重新渲染。baseDuration
:估算在没有任何优化的情况下重新渲染整棵<Profiler>
子树所需的毫秒数。它通过累加树中每个组件的最近一次渲染持续时间来计算。此值估计了渲染的最差情况成本(例如初始挂载或没有使用记忆化的树)。将其与actualDuration
进行比较,以确定记忆化是否起作用。startTime
:当React
开始渲染此次更新时的时间戳endTime
:当React
提交此次更新时的时间戳。此值在提交的所有profiler
中共享,如果需要,可以对它们进行分组。
进行性能分析会增加一些额外的开销,仅在必要时使用,因此 在默认情况下,它在生产环境中是被禁用的。
<Profiler>
允许你编程式收集性能测量数据。如果你正在寻找一个交互式的性能分析工具,可以尝试使用 React
开发者工具 中的 Profiler
标签页。它提供了类似浏览器扩展程序的功能。
使用 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
包装的函数组件在其实现中有useState
、useReducer
或useContext
Hook
,那么当状态或上下文发生变化时,它仍然会重新渲染。useMemo()
是对一个值的记忆,传递一个create
函数和一个依赖数组,useMemo
仅当依赖项之一发生更改时才会重新计算记忆值,作用是减少不必要的计算- 相同注意点:React官网说可以依赖这两个开展性能优化,但不保证。
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 缓存函数本身。