体验一把 React transition

引言

React 系列继续,今天来聊一聊 transition。话不多说,我们先用一个例子(React 18)来引入今天的主题:

import {useState, memo} from 'react'

const HeavyItem = memo(({query}) => {
  for (let i = 0; i < 99999; i++) {}
  return <div>{query}</div>
})

export default function App({
  const [inputValue, setInputValue] = useState('')
  const handleChange = (e) => {
    setInputValue(e.target.value)
  }
  return (
    <div style={{paddingLeft: 100paddingTop: 10}}>
      <input value={inputValue} onChange={handleChange} />
      <div>
        {[...new Array(5000)].map((_, i) => (
          <HeavyItem key={i} query={inputValue} />
        ))}
      </div>
    </div>

  )
}

上面例子模拟了一个关键词搜索的应用,注意到其中的每一项搜索结果 HeavyItem 中,我们都空循环了 10 万次,用于模拟耗时的渲染过程。所以,我们在搜索的时候会感觉到有明显的卡顿现象:

体验一把 React transition

根本原因在于搜索列表的渲染是一个非常耗时的操作,整个 React 应用的更新都被其所阻塞。但其实列表的更新可以稍后一些,而搜索关键字在 input 中的更新必须足够及时才能使得用户使用起来感觉比较流畅,也就是两个更新的优先级是有先后的。而 transition 的出现,就是为了解决这一类的问题。

useTransition

使用

我们通过 React 提供的 useTransiton优化上面的例子:

import {useState, useTransition, memo} from 'react'

const HeavyItem = memo(({query}) => {
  for (let i = 0; i < 99999; i++) {}
  return <div>{query}</div>
})

export default function App({
  const [inputValue, setInputValue] = useState('')
  const [query, setQuery] = useState('')
  const [isPending, startTransition] = useTransition()
  const handleChange = (e) => {
    setInputValue(e.target.value)
    startTransition(() => {
      setQuery(e.target.value)
    })
  }
  return (
    <div style={{paddingLeft: 100paddingTop: 10}}>
      <input value={inputValue} onChange={handleChange} />
      <div>
        {isPending
          ? 'Loading'
          : [...new Array(5000)].map((_, i) => (
              <HeavyItem key={i} query={query} />
            ))}
      </div>
    </div>

  )
}

可以看到,现在搜索体验非常丝滑了:

体验一把 React transition

实现原理

当我们在输入框中输入 a 时,会触发 handleChange

  1. 调用 setInputValue 产生一个更新任务(假设为 inputUpdate1)。

  2. 调用 startTransition,首先会以当前优先级 setPending(true) (更新任务假设为 pendingTrueUpdate1),然后将优先级降低并 setPending(false) (更新任务假设为 pendingFalseUpdate1)以及调用回调函数执行 setQuery (更新任务假设为 queryUpdate1)。

  3. React 会处理优先级较高的 inputUpdate1pendingTrueUpdate1,此时页面 input 框中的内容得到更新,并显示 loading。

体验一把 React transition

  1. 更新渲染完成后,会开始处理 pendingFalseUpdate1queryUpdate1,由于此时需要渲染非常昂贵的列表,React 的 Render 过程可能会需要若干个时间切片才能处理完。

体验一把 React transition

  1. 当用户继续输入 b,由于步骤 4 中 React 是使用时间切片的方式来处理,所以当某个时间切片结束后,React 会把控制权交出,用户输入能够得到响应,此时又会触发如下更新任务:
// 高优先级
inputUpdate2
pendingTrueUpdate2

// 低优先级
pendingFalseUpdate2
queryUpdate2

React 发现有高优先级的更新插入,会取消掉步骤 4 中正在进行的更新任务,开始处理 inputUpdate2pendingTrueUpdate2

体验一把 React transition


 6. 用户没有继续输入,则会将所有低优先级更新任务继续处理完。

通过 performance 面板,我们可以更加直观看到整个过程:
体验一把 React transition

Why not debounce?

transition 出现之前,我们很容易会想到用 debounce (防抖)来解决这样的问题:

import {useRef, useState, memo} from 'react'

function debounce(fn, wait = 300{
  let timer = null
  return function (...args{
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      console.log('trigger')
      fn.apply(this, args)
    }, wait)
  }
}

const HeavyItem = memo(({query}) => {
  for (let i = 0; i < 99999; i++) {}
  return <div>{query}</div>
})

export default function App({
  const [inputValue, setInputValue] = useState('')
  const [query, setQuery] = useState('')
  const debouncedSetQuery = useRef(debounce(setQuery))
  const handleChange = (e) => {
    setInputValue(e.target.value)
    debouncedSetQuery.current(e.target.value)
  }
  return (
    <div style={{paddingLeft: 100paddingTop: 10}}>
      <input value={inputValue} onChange={handleChange} />
      <div>
        {[...new Array(5000)].map((_, i) => (
          <HeavyItem key={i} query={query} />
        ))}
      </div>
    </div>

  )
}

但实际上 debounce 并不能解决这个问题。如下所示,我们先输入 a,等到 trigger 打印后,继续输入 bcdef,很明显后面输入的内容并没有立刻渲染出来:

体验一把 React transition

原因在于 debounce 只是减少了 setQuery 的调用,但是治标不治本,一旦 setQuery 调用触发了更新,那 React 的渲染过程还是会阻塞用户交互。

performance 监控面板发现有两个耗时超过 1s 的任务(React 默认不会开启 Concurrent 模式,所以这里没有时间切片),分别对应着输入 abcdef 触发的更新。

体验一把 React transition

俗话说得好,“解铃还须系铃人”。React 通过虚拟 DOM、协调算法等手段给广大前端程序员的开发带来巨大便利的同时也引入了一些成本,通过外部手段很难“根治”病因,还是得官方出马才能解决问题。


原文始发于微信公众号(前端游):体验一把 React transition

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/282262.html

(0)
前端游的头像前端游bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!