React18新特性

324 阅读4分钟

Automatic batching

  • 将多个状态更新合并成一个重新渲染以取得更好的性能的一种优化方式

V18前

  • 默认不batching的scene

    • promise

    • setTimeout;

    • 原生事件处理(native event handlers)

V18:所有更新自动batching

function App() {
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  function handleClick() {
    fetchSomething().then(() => {
      // React 18 and later DOES batch these:
      setCount((c) => c + 1)
      setFlag((f) => !f)
      // React will only re-render once at the end (that's batching!)
    })
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
    </div>
  )
}

不想batching:flushSync

function App() {
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  function handleClick() {
    fetchSomething().then(() => {
      // React 18 and later DOES batch these:
      setCount((c) => c + 1)
      setFlag((f) => !f)
      // React will only re-render once at the end (that's batching!)
    })
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
    </div>
  )
}

batching 对hooks及class的影响

handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }))
    // V18前 { count: 1, flag: false }
    // V18中 { count: 0, flag: false },除非使用flushSync
    console.log(this.state)

    this.setState(({ flag }) => ({ flag: !flag }))
  })
}

// 在一些react库中,如react-dom, unstable_batchedUpdates 实现类似功能
import { unstable_batchedUpdates } from 'react-dom'

unstable_batchedUpdates(() => {
  setCount((c) => c + 1)
  setFlag((f) => !f)
})

startTransition

  • 让页面展示时时刻保持re-render

  • 更新input的value的同时,用这个value去更新了一个有30000个item的list

    • 多数据更新让页面无法及时响应、用户交互感觉很慢
// 紧急的更新:展示用户的输入 
setInputValue(e.target.value) 

// 非紧急的更新: 展示结果 
setContent(e.target.value)

// debounce 和 throttle 经常使用
// Show what you typed
setInputValue(input)

// Show the results
setTimeout(() => {
  setSearchQuery(input)
}, 0)

// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input)
})

// 等同于
// 先setInputValue(e.target.value) 后执行 setContent(e.target.value)

react中的upate

  • Urgent updates

    • reflect direct interaction, like typing, clicking, pressing, and so on;
  • Transition updates

    • transition the UI from one view to another

误区

  • 与setTimeout的区别

    -startTransition:同步立即执行的

      - 不会被放到下一次event loop
      
      - 比timeout update更早
      
      - 低端机体验明显
    

使用场景

  • slow rendering

    • re-render需要耗费大量的工作量
  • slow network

    • 需要较长时间等待response的情况

支持React.lazy的SSR架构

react的SSR场景(server side render)

  • server

    • 获取数据

    • 组装返回带有HTML的接口

  • client

    • 加载 JavaScript

    • hydration,将客户端的JS与服务端的HTML结合

V18:支持拆解应用为独立单元,不影响其他模块

  • V18前:按序执行

  • 正常加载界面

= 不使用SSR界面,带个loading

  • 使用SSR

  • hydration后

SSR问题

  1. server:获取数据; --> 按序执行,必须在服务端返回所有HTML;
  2. client:加载 JavaScript; --> 必须JS加载完成;
  3. client:hydration,将客户端的JS与服务端的HTML结合; --> hydrate后才能交互;

流式 HTML&选择性hydrate

  1. 流式HTML

  2. client进行选择性的 hydration:

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      {/* 假设HTML加载很慢,分批 */}
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

// HTML返回过来在加载
<div hidden id="comments">
{/* Comments  */}
  <p>First comment</p>
  <p>Second comment</p>
</div>
<script>
  // This implementation is slightly simplified
  document.getElementById('sections-spinner').replaceChildren(
    document.getElementById('comments')
  );
</script>

  • JS选择性加载
import { lazy } from 'react'

const Comments = lazy(() => import('./Comments.js'))

// ...

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

  • hydration 之前要求交互

记录操作行为,并优先执行Urgent comp的hydration

Concurrent Mode(并发模式,以下简称CM)

什么是 CM 和 suspense?

  • 理解为等待代码加载,且指定加载界面

CM

  • 帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整

  • 阻塞渲染:如UI update,需要先执行对应视图操作,如更新DOM

solution:

  • debounce:输入完成后响应,输入时不会更新

  • throttle:功率低场景卡顿

可中断渲染(CM)

  • CPU-bound update

    • 如创建新的 DOM 节点和运行组件中的代码:中断当前渲染,切换更高优先级

    • IO-bound update: (例如从网络加载代码或数据):response前先在内存进行渲染;

suspense

  • 以声明的方式来“等待”任何内容,包括数据
const resource = fetchProfileData()

function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline />
      </Suspense>
    </Suspense>
  )
}

function ProfileDetails() {
  // 尝试读取用户信息,尽管该数据可能尚未加载
  const user = resource.user.read()
  return <h1>{user.name}</h1>
}

function ProfileTimeline() {
  // 尝试读取博文信息,尽管该部分数据可能尚未加载
  const posts = resource.posts.read()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  )
}

误区

  • Suspense 不是一个数据请求的库,而是一个机制

    • 是用来给数据请求库向 React 通信说明某个组件正在读取的数据当前仍不可用
  • 什么不是suspense

    • 不是数据获取方式

    • 不是一个可以直接用于数据获取的客户端

    • 它不使数据获取与视图层代码耦合

  • Suspense 可以做什么

    • 让数据获取库与 React 紧密整合

    • 让你有针对性地安排加载状态的展示

    • 消除 race conditions

为什么没有在V18中加上 CM 和 suspense ?

  • CM和suspense更适合针对库作者,日常应用的开发者更多的可以作为借鉴

  • react当前核心会放在迁移和解决兼容性的问题

    • Fragments、Context、Hook开箱即用

    • concurrent得引入新的语义