EXIT

00:00:00

回顾React

[toc]


# React异步更新与更新函数

React的更新是异步进行的,也就是说,当你在连续的更改state时(或者说是在UI更新之前连续更改state时),就会触发React的批处理异步更新.

例如:

const [number, setNumber] = useState(0); ...... <button onClick={() => { setNumber(number + 5); setNumber(number + 10); }} > 增加数字 </button> ......

从上面的代码可以看出,在按钮点击之后会连续更改state,这就会触发批处理异步更新.

setNumber(number + 5) : 执行时,number为0,所以更新后应该number是5. 但是此时还没有UI更新,因此React会延迟该值的更新,所以当前的number仍旧是0.

setNumber(number + 10) : 同理,由于上一步的延迟更新,原本number为0,执行完该句应该为10,但是延迟更新,该number值仍然为0;

":" :UI更新,此时number为最后一次更新的值10.

更新函数:

setNumber(n => n + 1) :该更新函数的作用就是可以根据上一次更新的值进行更新.

例如:

const [number, setNumber] = useState(0); ...... <button onClick={() => { setNumber((n)=>n+5); setNumber((n)=>n+10); }} > 增加数字 </button> ......

从上面的代码可以看出:

setNumber((n)=>n+5): 执行时,会将number更新为5,当然,React仍旧会延迟该值的更新,所以此刻的number值仍旧为0;

setNumber((n)=>n+10) : 执行时,会根据上一次更新的值进行更新,上一次更新的值是5,所以此刻计算获得的值是15.当然,由于异步更新与批更新的机制,此刻的number仍旧为0.

":" :UI更新,此时number为最后一次更新的值15.

再看一个例子:

const [number, setNumber] = useState(0); ...... <button onClick={() => { setNumber(number + 5); setNumber((n) => n + 1); setNumber(number + 10); }} > 增加数字 </button> ......

从上面的代码可以看出:

setNumber(number + 5) : 执行时,会将number更新为5,当然,React仍旧会延迟该值的更新,所以此刻的number值仍旧为0;

setNumber((n) => n + 1) :执行时,会根据上一次更新的值进行更新,上一次更新的值是5,所以此刻计算获得的值是15.当然,由于异步更新与批更新的机制,此刻的number仍旧为0.

setNumber(number + 10) :执行时,会将number更新为10,因为此刻的number值为0,0+10=10,他并不会根据上一次值的更新而更新,当然,React仍旧会延迟该值的更新,所以此刻的number值仍旧为0;

":" :UI更新,此时number为最后一次更新的值10.

总结

因此,React出于对性能的考虑, 提供了异步更新与批更新的机制.

更新函数与普通的更新可以看作如下区别:

更新函数: setState((n)=>n);

普通更新: setState(10);

其实普通更新等价于: setState(()=>10); 只不过他的更新函数没有传入上一次更新的值(n).

对于一个如下的代码,其可以表示为:

const [number, setNumber] = useState(0); ...... <button onClick={() => { setNumber(number + 5); setNumber((n) => n + 1); setNumber(number + 10); setNumber((n) => n + 2); setNumber((n) => n + number); }} > ADD </button> ......

在下一次渲染期间,React 会遍历 state 队列:

更新队列(初始number=0)n(上一次的返回值)返回值本次执行的代码
() => number + 50(未使用)5setNumber(number + 5)
(n) => n + 155 + 1 = 6setNumber((n) => n + 1)
() => number + 106(未使用)10setNumber(number + 10)
(n) => n + 21012setNumber((n) => n + 2)
(n) => n + number1212setNumber((n) => n + number)

最终结果为12.

const [number, setNumber] = useState(0);

const anotherNumber = number;

此刻,anotherNumber也是响应式的.

命名惯例

通常可以通过相应 state 变量的第一个字母来命名更新函数的参数:

setEnabled(e => !e);

setLastName(ln => ln.reverse());

setFriendCount(fc => fc * 2);

如果你喜欢更冗长的代码,另一个常见的惯例是重复使用完整的 state 变量名称,如 setEnabled(enabled => !enabled),或使用前缀,如 setEnabled(prevEnabled => !prevEnabled)

# 更新state中的对象

state 中可以保存任意类型的 JavaScript 值,包括对象。但是,你不应该直接修改存放在 React state 中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。

虽然严格来说 React state 中存放的对象是可变的,但你应该像处理数字、布尔值、字符串一样将它们视为不可变的。因此你应该替换它们的值,而不是对它们进行修改。

换句话说,你应该 把所有存放在 state 中的 JavaScript 对象都视为只读的

所以,如果只是更新对象中的某个属性,可以采用展开语法复制对象:

setPerson({ firstName: e.target.value, // 从 input 中获取新的 first name lastName: person.lastName, email: person.email });

更改为:

setPerson({ ...person, // 复制上一个 person 中的所有字段 firstName: e.target.value // 但是覆盖 firstName 字段 });

当然,对于更改嵌套对象的属性,则需要:

setPerson({ ...person, // 复制其它字段的数据 artwork: { // 替换 artwork 字段 ...person.artwork, // 复制之前 person.artwork 中的数据 city: 'New Delhi' // 但是将 city 的值替换为 New Delhi! } });

**使用 Immer 编写简洁的更新逻辑 **

使用:

  1. 运行 npm install use-immer 添加 Immer 依赖
  2. import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'
import { useImmer } from 'use-immer'; const [person, updatePerson] = useImmer({ name: 'Niki de Saint Phalle', artwork: { title: 'Blue Nana', city: 'Hamburg', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { updatePerson(draft => { draft.name = e.target.value; }); }

由 Immer 提供的 draft 是一种特殊类型的对象,被称为 Proxy,它会记录你用它所进行的操作。这就是你能够随心所欲地直接修改对象的原因所在!从原理上说,Immer 会弄清楚 draft 对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。

# 更新state中的数组

数组是另外一种可以存储在 state 中的 JavaScript 对象,它虽然是可变的,但是却应该被视为不可变。同对象一样,当你想要更新存储于 state 中的数组时,你需要创建一个新的数组(或者创建一份已有数组的拷贝值),并使用新数组设置 state。

避免使用 (会改变原始数组)推荐使用 (会返回一个新数组)
添加元素pushunshiftconcat[...arr] 展开语法
删除元素popshiftsplicefilterslice
替换元素splicearr[i] = ... 赋值map
排序reversesort先将数组复制一份

或者,你可以使用 Immer ,这样你便可以使用表格中的所有方法了。

# 组件的保留与重置

只要一个组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。 如果它被移除,或者一个不同的组件被渲染在相同的位置,那么 React 就会丢掉它的 state。

相同位置的相同组件会使得 state 被保留下来. 对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置!

React 不知道你的函数里是如何进行条件判断的,它只会“看到”你返回的树。你可以认为它们有相同的“地址”:根组件的第一个子组件的第一个子组件。不管你的逻辑是怎么组织的,这就是 React 在前后两次渲染之间将它们进行匹配的方式。

由于切换时,是两个相同的组件进行切换,因此切换时不会组件重置.

<div> {isFancy ? ( <div> <Counter isFancy={true} /> </div> ) : ( <div> <Counter isFancy={false} /> </div> )} </div>

但是! 当你在不同位置渲染即使相同组件时,组件仍旧会被重置,(即使第一个已被销毁,但他实际位置仍旧为1).

<div> {isFancy && ( <div> <Counter isFancy={true} /> </div> ) } {!isFancy && ( <div> <Counter isFancy={false} /> </div> )} </div>

但是! 当你在相同位置渲染不同的组件时,组件的整个子树都会被重置

<div> {isFancy ? ( <div> <Counter isFancy={true} /> </div> ) : ( <section> <Counter isFancy={false} /> </section> )} </div>

由于div 和 section不一样,因此里面的Counter会在切换时销毁.

# 使用reducer整合状态逻辑

当一个state被频繁的不同状态的更改,且逻辑复杂时,便可以考虑使用reducer整合状态逻辑.

使用 reducers 管理状态与直接设置状态略有不同。它不是通过设置状态来告诉 React “要做什么”,而是通过事件处理程序 dispatch 一个 “action” 来指明 “用户刚刚做了什么”。(而状态更新逻辑则保存在其他地方!)因此,我们不再通过事件处理器直接 “设置 task”,而是 dispatch 一个 “添加/修改/删除任务” 的 action。这更加符合用户的思维。

action 对象可以有多种结构。

按照惯例,我们通常会添加一个字符串类型的 type 字段来描述发生了什么,并通过其它字段传递额外的信息。type 是特定于组件的.选一个能描述清楚发生的事件的名字!

dispatch({ // 针对特定的组件 type: 'what_happened', // 其它字段放这里 });

$ 编写一个Reducer函数:

reducer 函数就是你放置状态逻辑的地方。它接受两个参数,分别为当前 stateaction 对象,并且返回的是更新后的 state:

例如,对于一个需要增删改查的表单,其reducer函数应该如此写:

taskReducer.js:

export default const tastReducer = (state,action) => { switch (action.type) { case 'added': { return [ ...state, { id: action.id, text: action.text, done: false, }, ]; } case 'changed': { return state.map((t) => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return state.filter((t) => t.id !== action.id); } default: { throw Error('未知 action: ' + action.type); } } }

在每一个case里面都要将更新后的state return出去.

$ 在组件中使用reducer

App.js

import { useReducer } from 'react'; import tasksReducer from './tasksReducer.js'; export default function TaskApp() { //用useReducer替换useState //const [tasks, setSasks] = useState(initialTasks); const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task, }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId, }); } return ( <> <h1>布拉格的行程安排</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); }

当然,在reducer里面对对象和数组如果觉得一直需要...tasks比较麻烦,我们仍旧可以使用 Immer 这个库来简化 reducer.

useImmerReducer让你可以通过 pusharr[i] = 来修改 state.

function tasksReducer(draft, action) { switch (action.type) { case 'added': { draft.push({ id: action.id, text: action.text, done: false, }); break; } case 'changed': { const index = draft.findIndex((t) => t.id === action.task.id); draft[index] = action.task; break; } case 'deleted': { return draft.filter((t) => t.id !== action.id); } default: { throw Error('未知 action:' + action.type); } } }

# React中的Vue-Slot

React也可以实现类似Vue中Slot的功能,具体就是:

... <Card> <Avatar /> <Avatar /> </Card> ...

那么,在Card这个组件中,便可以获得Avatar这个组件作为props.

Card.tsx:

export default function Card({ children }) { return ( <section className="section"> {children} </section> ); }

此时,children就为那两个Avatar组件.(不止可以传组件,也可以传普通DOM元素).

当然,如果有其他的props,需要将其他props放在children前面.

//{ ...otherProps,children }为结构语法, 原本prop = { ...otherProps,children }; export default function Card({ ...otherProps,children }){ ... }

# Props透传 -- Context

context允许父组件向其下层无论多深的任何组件提供信息,并且无需通过props进行显示传递.

Context 可以让父节点,甚至是很远的父节点都可以为其内部的整个组件树提供数据。

如何实现props透传?

  • 创建一个context.

    levelContext.js:

    import { createContext } from "react"; export const LevelContetext = createContext(1);

    其中,创建的这个1就表示 levelContext 这个context的默认值.

  • 在指定数据的组件中提供这个context. 我们需要用 context provider 将子组件进行包裹起来以提供levelContext.

    propvideData.jsx:

    import { levelContext } from "./levelContext.js" export default function ProvideData() { const nowLevel = 100; return ( <div> <LevelContetext.Provider value={nowLevel}> <NeedData></NeedData> </LevelContetext.Provider> </div> ) }

    这段代码告诉React,如果ProvideData组件中的任意层次的子组件请求levelContext这个context数据,那么就将value = {nowLevel} 中的nowLevel返回给他们.

    组件会使用 UI 树中在它上层最近的那个 <LevelContext.Provider> 传递过来的值。

  • 在需要数据的组件内使用刚刚创建的context.

    needData.jsx:

    import { useContext } from "react"; import { LevelContetext } from "./levelContext.js" export default function NeedData() { const level = useContext(LevelContetext); } switch (level) { case 1:... case 2:... case 3:... }

# React中也有Ref

我们使用useState时,当state值更新了的时候,其当前组件和其子组件都会进行更新.但是ref允许更新数据的同时不进行新的渲染.

如何使用?

import { useRef } from "react"; const numberRef = useRef(0); //此时打印numberRef的值会是: console.log(numberRef) => { current:0 }

可以通过 numberRef.current 来访问当前值,同时也可以直接进行修改而无需通过 setState.

但是,由于你改变了该值后,并不会触发渲染,因此,如果你将ref值搬到html视图中,改变该值并不会体现到页面中.(因为他不会触发渲染.)

何时使用ref?

  • 存储timeoutID
  • 获取页面中的dom元素.
  • ...

以下代码是将ref放入HTML元素上时的情况.

import { useRef } from "react" export default const App(){ const domRef = useRef(null); //然后就可以使用任意dom的API domRef.current.scrollIntoView(); console.log(domRef.current); //返回null,因为此时dom元素还未挂载. return ( <> <div ref={domRef}></div> </> ) }

以下代码是将ref放入自己创建的组件上时的情况.

import { useRef } from "react"; export default const App() { const domRef = useRef(null); return ( <div> <Fake ref={domRef} /> </div> )

当你直接访问放到自定义组件上的ref时,控制台会报错:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

发生这种情况是因为默认情况下,React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!这是故意的。Refs 是一个应急方案,应该谨慎使用。手动操作 另一个组件的 DOM 节点会使你的代码更加脆弱。

但是,如果你真的需要这样做,请参考:

想要暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件。使用 forwardRef API:

const Fake = forwardRef((props, ref) => { //注意此处是(props,ref)而不是{props,ref}. return <input {...props} ref={ref} />; });

它是这样工作的:

  1. <Fake ref={domRef} /> 告诉 React 将对应的 DOM 节点放入 domRef.current 中。但是,这取决于 Fake 组件是否允许这种行为, 默认情况下是不允许的。
  2. Fake 组件是使用 forwardRef 声明的。 这让从上面接收的 domRef 作为第二个参数 ref 传入组件,第一个参数是 props
  3. Fake 组件将自己接收到的 ref 传递给它内部的 <input>
  4. 现在,你可以访问了.

当然,如果你想让子组件只暴露一部分内容而不是全部,那么可以使用 useImperativeHandle 做到这一点.

Fake 组件中:

const Fake = forwardRef(function Fake(props, ref) => { //注意此处是(props,ref)而不是{props,ref}. const realRef = useRef(null); useImperativeHandle(ref, () => ({ // 只暴露 focus,没有别的 focus() { realInputRef.current.focus(); }, })); return <input {...props} ref={realRef} />; });

# Effect

什么是Effect:

Effect 通常用于暂时“跳出” React 代码并与一些 外部 系统进行同步。这包括浏览器 API、第三方小部件,以及网络等等。

如何编写Effect:

  • 声明Effect

    import { useEffect } from "react"; function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { // 每次渲染后都会执行此处的代码 console.log(count); }); return <div />; }

    每当你的组件渲染时,React 将更新屏幕,然后运行 useEffect 中的代码。换句话说,useEffect 会把这段代码放到屏幕更新渲染之后执行.

    const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }); //此代码会造成死循环.
  • 指定Effect依赖

    • Effect的依赖必须是响应式的.

    • 一般来说,Effect 会在 每次 渲染时执行。但更多时候,并不需要每次渲染的时候都执行 Effect。将 依赖数组 传入 useEffect 的第二个参数,以告诉 React 跳过不必要地重新运行 Effect

    • 例如,在声明Effect的第一段程序内,可以在useEffect的第二个参数添加**[count]**,这表示只有是由于count引起的渲染时,才会触发useEffect.

    • 当然,如果你只想让Effect里的代码只执行一遍,那么可以第二个参数添加**[]空数组,那么该组件的Effect在组件挂载**后执行.

      挂载

      组件挂载主要是指React的自定义组件被使用并显示在页面上.当离开该页面后,组件自动被卸载,再次进入该页面将再次被挂载.

      < 在开发环境中,React 会在初始挂载组件后,立即再挂载一次,用于帮助开发者解决未及时清理

    useEffect(() => { // 这里的代码会在每次渲染后执行 }); useEffect(() => { // 这里的代码只会在组件挂载后执行 }, []); useEffect(() => { //这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行 }, [a, b]);
  • 按需添加清理函数

    useEffect(() => { const connection = createConnection(); connection.connect(); return () => { connection.disconnect(); }; }, []);

    useEffect return出去的函数就是清理函数. **每次重新执行 Effect 之前,**React 都会调用清理函数;组件被卸载时,也会调用清理函数。

Effect的生命周期

首先,我们先来看看 每个 React 组件的生命周期.

  • 当组件被添加到屏幕上时,它会进行组件的 挂载
  • 当组件接收到新的 props 或 state 时,通常是作为对交互的响应,它会进行组件的 更新
  • 当组件从屏幕上移除时,它会进行组件的 卸载

组件的生命周期并不适用于Effect.

Effect 能够在需要时始终具备启动和停止的弹性 =>

  • Effect 在当前组件挂载完毕后,组件更新时将执行清理函数并启动新一轮的effect.

  • Effect 的更新仅与依赖项有关,与组件自己的更新无关(除非组件卸载又挂载).

  • 如果没有设置依赖项,将会与组件的更新同步.

  • 如果依赖项为[],那么仅在组件挂载完毕之后执行一次,组件更新也不会触发effect的更新.

一个组件里 Effect 可以写多个,每个 Effect 都代表一个独立的同步过程.

# 如何编写自定义Hook

...

# React 实用Hook

has introduce:

  • useState() : 用于创建响应式的数据. [has]
  • useRef() : 用于创建不会随着组件的更新而重置的数据. [has]
  • useContext(): 用于Props穿透. [has]
  • useEffect() : 允许组件连接到外部系统并与之同步. [has]
  • useReducer() : ... [has]

other:

  • useCallback: 允许在多次渲染中缓存函数的Hook. [性能优化Hook]

    • 用法: const cachedFn = useCallback(fn, dependencies);
    • 参数:
      • fn:想要缓存的函数,React将会在初次渲染时返回(而非调用)该函数. 当下一次渲染时,如果 dependencies 相比于上一次渲染没有发生改变,那么将会返回相同的函数,否则将会返回最新一次渲染时返回的函数,并将其缓存以便之后使用.
      • dependencies: 和Effectdependencies一样.
    • 返回值: 初次渲染时返回fn函数,在之后的渲染中,如果依赖没有发生改变,则返回上一次缓存的函数,否则就返回这一次渲染返回的函数. 简而言之,useCallback 在多次渲染中缓存一个函数,直至这个函数的依赖发生改变。

    例如:

    function ProductPage({ productId, referrer, theme }) { // 每当 theme 改变时,都会生成一个不同的函数 function handleSubmit(orderDetails) { post('/product/' + productId + '/buy', { referrer, orderDetails, }); } return ( <div className={theme}> {/* 这将导致 ShippingForm props 永远都不会是相同的,并且每次它都会重新渲染 */} {/*ShippingForm 里面内置了memo,可以在dependencies保持不刷新*/} <ShippingForm onSubmit={handleSubmit} /> </div> ); }
  • useMemo: 它在每次重新渲染的时候能够缓存计算的结果。

    • 用法: const catch = useMemo(calculateValue, dependencies)

    • 参数:

      • calculateValue :要缓存计算值的函数,他应该是一个没有任何参数的纯函数,React将会在首次渲染的时候调用(与useCallBack的不同点)该函数,并将调用的结果进行缓存,如果后面dependencies没有发生变化,则将直接返回缓存结果;否则将重新调用并缓存结果.
      • dependencies: ...
    • 返回值: 在初次渲染时,useMemo 返回不带参数调用 calculateValue 的结果。

      在接下来的渲染中,如果依赖项没有发生改变,它将返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果。

    • 另外,memo还可以跳过组件的渲染.例如:

      export default function TodoList({ todos, tab, theme }) { // ... return ( <div className={theme}> <List items={visibleTodos} /> </div> ); } //List组件将会在theme变更后重新渲染,即使他与上一次渲染后的结果一致.

      你可以通过将它包装在 memo 中,这样当它的 props 跟上一次渲染相同的时候它就会跳过本次渲染:

      import { memo } from 'react'; const List = memo(function List({ items }) { // ... });

      useMemo 经常与 useCallback 一同出现。

  • useDeferredValue: 允许延迟更新UI的某些部分.

    • 用法:const deferredValue = useDeferredValue(value)

    • 参数: value: 要延迟的值.

    • 返回值: 在组件的初始渲染期间,返回的延迟值将与你提供的值相同。但是在组件更新时,React 将会先尝试使用旧值进行重新渲染(因此它将返回旧值),然后再在后台使用新值进行另一个重新渲染(这时它将返回更新后的值)。这个后台重新渲染是可以被中断的,如果 value 有新的更新,React 会从头开始重新启动后台渲染。

  • useId: 生成唯一ID .但是 不应该被用来生成列表中的 key.

  • useImperativeHandle: 自定义由 ref 暴露出来的句柄。

    • 用法: useImperativeHandle(ref, createHandle, dependencies?)

    • 参数:

      • ref:该 ref 是你从 forwardRef 渲染函数 中获得的第二个参数。
      • createHandle:该函数无需参数,它返回你想要暴露的 ref 的句柄。该句柄可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。
      • dependencies: 可选参数.
    • 事例:

      import { forwardRef, useImperativeHandle } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { useImperativeHandle(ref, () => { return { // ... 你的方法 ... }; }, []); // ...
  • useTransition : 在不阻塞 UI 的情况下更新状态的 React Hook。

    • 用法: const [isPending, startTransition] = useTransition()

    • 参数: 无参.

    • 返回值:

      • isPending: 告诉你是否存在待处理的 transition。

      • startTransition: 函数,使用此方法将状态更新标记为 transition。

        例如:

        function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } // …… }
    • transition 可以使用户界面的更新在慢速设备上仍保持响应性。

      通过 transition,UI 仍将在重新渲染过程中保持响应性。例如用户点击一个选项卡,但改变了主意并点击另一个选项卡,他们可以在不等待第一个重新渲染完成的情况下完成操作。

# 可用的其他组件

<Suspense> : 允许在子组件完全加载完成之前展示备选方案.

-用法:

<Suspense fallback={<Loading />}> <SomeComponent /> </Suspense>

-参数:

  • children: 真正的 UI 渲染内容。如果 children 在渲染中被挂起,Suspense 边界将会渲染 fallback
  • fallback: 真正的 UI 未渲染完成时代替其渲染的备用 UI,它可以是任何有效的 React 节点。

默认情况下,Suspense 内部的整棵组件树都被视为一个单独的单元。例如,即使 只有一个 组件因等待数据而被挂起,Suspense 内部的整棵组件树中的 所有 的组件都将被替换为加载中指示器. 然后,当它们都准备好展示时,它们将一起出现。

嵌套加载Suspense:

<Suspense fallback={<BigSpinner />}> <Biography /> <Suspense fallback={<AlbumsGlimmer />}> <Panel> <Albums /> </Panel> </Suspense> </Suspense>

加载序列将会是:

  1. 如果 Biography 没有加载完成,BigSpinner 会显示在整个内容区域的位置。
  2. 一旦 Biography 加载完成,BigSpinner 会被内容替换。
  3. 如果 Albums 没有加载完成,AlbumsGlimmer 会显示在 Albums 和它的父级 Panel 的位置。
  4. 最后,一旦 Albums 加载完成,它会替换 AlbumsGlimmer

注意!

延迟值和 transition 都可以让你避免显示 Suspense 后备方案,而是使用内联指示器。transition 将整个更新标记为非紧急的,因此它们通常由框架和路由库用于导航。另一方面,延迟值在你希望将 UI 的一部分标记为非紧急,并让它“落后于” UI 的其余部分时非常有用。

# 懒加载

lazy 能够让你在组件第一次被渲染之前延迟加载组件的代码。

import { lazy } from 'react'; const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
uid:O3hPim
VOIDIS.ME
  1. no-like
  2. message
  3. Bilibili
  4. Github
  5. RSS
  6. sun