React 18: Hooks & Components & APIs

React 的生命周期
  • 横轴代表时间的流逝,纵轴代表React需要执行的步骤(render,commit)

React Hooks

Hooks related to performance:

  • useCallback, useMemo, useTransition, useDeferredValue, useOptimistic
  • useLayoutEffect, useInsertionEffect

useId

  • Notable: 不应该用来生成 List key
  • 使用- 无障碍属性生成 unique ID
  • 使用- 为多个元素生成ID
  • 使用- 为Batch ID指定共享前缀
  • 使用- 服务端和客户端使用相同的前缀

useInsertionEffect

  • vs. renderuseLayoutEffect中注入样式
  • 直接在JS代码中编写样式,而不是编写CSS文件.
    1. 使用编译器静态提取到CSS文件
    2. 内联样式
    3. 运行时注入 style
      计算 - 浏览器频繁地重新计算样式
      慢 - useInsertionEffect可以解决

useCallback

  • useCallbackdependency 是使用 Object.is() 进行浅比较.
  • useCallback 目的是提高React 的性能,防止re-render. 一般使用存在2个场景
    1. 函数作为一个 prop 传递给子组件,子组件是一个slowRenderComponent; 该函数作为 prop传递给 React.memo 包裹的子组件
    2. 函数作为 useEffect dependency. 也可以使用 useCallback 包裹 function, 防止 re-render.
export interface IFilterState {
  [key: string]: string[] | string;
}

useDeferredValue

主要用于优化渲染性能, 避免多次更新状态和重新渲染组件
debounce的区别是: useDeferredValue 适用于渲染, debounce 使用于接口的调用.
useTransition的区别是: useDeferredValue 作用于 state, startTransition 可以包裹一段同步程序, 作用于set函数,

useLayoutEffect

useLayoutEffectbody中代码运行完,再进行repaint
scene1: 计算元素的位置.比如说 tooltip
scene2: re-direct 到别的页面(换句话说,如果没有权限, 当前页面休想看见一点)

 if (campaign_id  && ad_id) {
      location.href = `${XXX}? aadvid=${aadvid}`;
 }

useMemo

useMemo + React.memo 支持跳过子组件的 re-render/expensive calculation

useRef

  • 不要在渲染期间写入或者读取 ref.current,也就是不要将ref.current写在函数之中, 放在Event handleruseEffect
  • React 创建 dom节点并将其渲染到屏幕时,React 将会把 DOM 节点设置为 ref 对象的 current 属性
  • 避免重复创建 DOM
  • 使用 ref 引用一个值, 间隔器
export default  VideoComponent() {
  const playerRef = useRef(null);

 const  getPlayer = () => {
    if (playerRef.current !== null) {
      return playerRef.current;
    }
    const player = new VideoPlayer();
    playerRef.current = player;
    return player;
  }

useState

React 的心智模型

useSyncExternalStore

Tearing React 18之前渲染是不可以中断的,因此会出现卡顿现象,在18中增加了lane和时间分片(5ms)那渲染是可以中断的,那在用户执行输入的过程中,页面展示的状态是状态不一致.Redux 8使用useSyncExternalStore强制一致,但是当你停止操作,状态渲染出来是一致的.

import { useSyncExternalStore } from 'react';

export default function ChatIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

function getSnapshot() {
  return navigator.onLine;
}

function subscribe(callback) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

useTransition

useTransition 是一个帮助你在不阻塞 UI 的情况下,更新状态的 React Hook

官推

const [isPending, startTransition] = useTransition()
const [tab, setTab] = useState('about');

  function handleSelectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
  • startTransition 中需要包裹一个异步函数, setXXState(). [如果异步更新一个value,请使用 useDeferredValue]
  • 将当前选项卡更新状态放在一个过渡中,startTransition 中包裹的 state 更新允许被打断. (e.g.: 将选项卡的切换标记为transition state, 切换A后立即切换成 B, A 的渲染就会被打断)
  1. 转换效果可以被中断,在渲染期间可以和页面其他地方进行交互;也就是渲染期间点击页面,页面会给到反应
  2. 转换效果可以防止不必要的加载指示符
    Troubleshooting
  • 确保startTransition中的代码是同步运行的. setTimeout 不能放在 startTransition 中, await 也不能放在 startTransition
  • 传递给startTransition的函数会立即运行,在React运行的时候会使用transition标记

APIs

  • 一般API
    createContext, forwardRef, use
  • 性能优化相关
    React.lazy, React.memo, React.startTransition

lazy

  • lazy 能够让你在组件第一次被渲染之前,延迟加载组件的代码
  • lazy(load) load function
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

function Editor() {
  // ...
}

startTransition

startTransition(scope)
startTransition 可以让你在不阻塞 UI 的情况下更新 state. 将state标记为Transition渲染
vs useTransition 不提供 isPending 状态来跟踪一个 transition 是否正在进行

import { startTransition } from 'react';
function TabContainer() {
  const [tab, setTab] = useState('about');

// 在回调函数中标记 transition
  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
  // ...
}

memo

  • React 通过浅比较来比较旧的和新的props.
  • 可以指定自定义比较函数arePropsEqual, 我们必须比较每一个props, 包括函数. deepClone可能变得非常缓慢.

use

use API 可以读类Promise或者 Context 中的值. use 可以替代useContext

React Dom

  • API: ReactDOM.createRoot, ReactDOM.hydrateRoot, ReactDOM.createPortal
  • related to performance: ReactDOM.flushSync
  • <div/> 通用组件

createRoot

  • onRecoverableError 适用于组件中可能出现可恢复性错误的场景
  • an app fully built with React 使用一次 createRoot, modal 使用 createPortal
  • a page partially built with React 使用 多次
const navDomNode = document.getElementById('navigation');
const navRoot = createRoot(navDomNode); 
navRoot.render(<Navigation />);

const commentDomNode = document.getElementById('comments');
const commentRoot = createRoot(commentDomNode); 
commentRoot.render(<Comments />);
  • onRecoverableError 重试机制尝试重新加载, 注意使用ErrorBoundary
    可以恢复,就重试一次尝试恢复,不可以就展示ErrorBoundary 的后备UI
    我们可以尝试使用 try/catch 块捕获异常,并在 onRecoverableError 函数中进行自定义的错误处理逻辑
  1. 网络通信错误
  2. 图片加载错误

createPortal

利用ReactDOM.createPortal 提供一个独立于主体的 Dom
createPortal 允许你将 JSX 作为 children 渲染至 DOM 的不同部分。

  1. portal 只改变 DOM 节点的所处位置
  2. 使用createPortal 渲染model
  3. Portal 中的事件传播, 遵循 React 树而不是 DOM

  1. 利于TabList布局, inputBar 和Tab放在一起布局,将ref传递到下面的子组件
<div ref={searchRef} />
   <VideoList
        currentTab={currentTab}
        getSearchContainer={() => searchRef.current}
   />

React synthetic Event:

有些事件,如 onAbort 和 onLoad,在浏览器中不冒泡,但是在 React 中仍然会冒泡
提供了与原生事件一致的: nativeEvent
与原生 DOM 事件标准一致,也提供了而为的属性和方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容