likes
comments
collection
share

React 18 Transition&startTransiton&useTransiton&us

作者站长头像
站长
· 阅读数 29

React 18中,引入了一个新概念——transition,由此带来了一个新的API——startTransition和两个新的hooks——useTransitionusedeferredValue

1)transition产生初衷

transtion 直接翻译为 过渡。tansition本质上是为了解决渲染并发问题所提出。在React中一旦组件状态改变并触发了重新渲染,则无法停止渲染。直到组件重新渲染完毕,页面才能继续响应用户的交互。

为此react 18中更新都可以划分为以下两类:

  • 紧急更新(urgent update):用户期望马上响应的更新操作,例如鼠标单击或键盘输入。
  • 过渡更新(transition update):一些延迟可以接受的更新操作,如查询时,搜索推荐、搜索结果的展示等。
// 被startTransiton标记后为过渡更新
startTransition(()=> {
    // 非紧急更新,会被降低优先级,延迟执行
    setQueryValue(inputValue)
})

// 未被标记则马上执行
setInputValue(inputValue)

在react 18中被startTrionstion标记的更新,即为过渡更新(执行的优先级被降低),此时react会根据内部的调度机制延迟执行内部的state更新。

开发中开发者可以通过transition hook决定哪些更新被标记为transition事件。一旦被标记则代表为低优先级执行,即react知道该state可以延迟更新,通过区分更新优先级,让高优先级的事件保持响应,提高用户交互体验,保持页面响应

2)startTransiton

startTransiton使用介绍

const handleClick = () => {
    // startTransition包裹标记为低优先级更新
    startTransition(()=> {
        setQueryValue(inputValue)
    })
    
    // 未被标记则马上执行
    setInputValue(inputValue)
}

首先我们来介绍下最简单的startTransition

  • startTransiton 是一个接受回调的函数,用于告知React需要延迟更新的state;
  • 如果某个state的更新会导致组件挂起,则应该包裹在startTransition中。

这是一个对输入字符后展示搜索结果的场景模拟,通过伪造大量搜索结果,模拟容易卡顿的情况。我们试着连续输入123,监听搜索框值value变化(urgent update)和搜索值searchVal变化(transition update)并输出到控制栏。

import React, { useEffect, useState, startTransition } from 'react';
import './index.css'

const SearchResult = (props) => {
  const resultList = props.query
    ? Array.from({ length: 100000 }, (_, index) => ({
      id: index,
      keyword: `${props.query} -- 搜索结果${index}`,
    })) : [];
  return resultList.map(({ id, keyword }) => (
    <li key={id}>{keyword}</li>
  ))
}

export default () => {
  const [type, setType] = useState(1)
  const [value, setValue] = useState('');
  const [searchVal, setSearchVal] = useState('');

  useEffect(() => {
    // 监听搜索值改变
    console.log('对搜索值更新的响应++++++' + searchVal + '+++++++++++')
  }, [searchVal])

  useEffect(() => {
    // 监听输入框值改变
    console.log('对输入框值更新的响应-----' + value + '-------------')
  }, [value])

  useEffect(() => {
    if (type === 1) {
      setSearchVal(value)
    }
    if (type === 2) {
      startTransition(() => {
        setSearchVal(value)
      })
    }
  }, [value]);

  return (
    <div className='App'>
      <h3>StartTransition</h3>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <div className={`type_button ${type === 1 ? 'type_button_checked' : ''}`} onClick={() => setType(1)}>normal</div>
      <div className={`type_button ${type === 2 ? 'type_button_checked' : ''}`} onClick={() => setType(2)}>transiton</div>
      <ul>
        <SearchResult query={searchVal}></SearchResult>
      </ul>
    </div>
  );
}

普通模式

React 18 Transition&startTransiton&useTransiton&us

startTransiton模式

React 18 Transition&startTransiton&useTransiton&us

和setTimeout的区别

就上面setSearchQuery的例子,使用setTimeout也能达到相似的目的,那这个startTransition和setTimeout有啥区别?

  1. 一个重要区别是setTimeout是「延迟」执行startTransition是立即执行的,传递给startTransition的函数是同步运行,但是其内部的所有更新都会标记为非紧急,React将在稍后处理更新时决定如何render这些updates,这意味着将会比setTimeout中的更新更早地被render。
  2. 另一个重要区别是用setTimeout包裹的如果是内大面积的更新操作会导致页面阻塞不可交互,直到超时。这时候用户的输入、键盘按下等紧急更新操作将被阻止。而startTransition则不同,由于它所标记的更新都是可中断的,所以不会阻塞UI交互。即使用户输入发生变化,React也不必继续渲染用户不再感兴趣的内容。
  3. 最后,因为setTimeout是异步执行,哪怕只是展示一个小小的loading也要编写异步代码。而通过transitions,React可以使用hook来追踪transition的执行状态,根据transition的当前状态来更新loading。

3)useTransition

useTransition使用介绍

import { useTransition } from 'react'

const [isPending, startTransition] = useTransition({timeoutMs: 2000})
// 例如, 在pending状态下,您可以展示一个Spinner
{ isPending ? < Spinner /> : null }
  • startTransition 是一个接受回调的函数,用于告知React需要延迟更新的state。
  • isPending 是一个布尔值,这是react告知我们是否等待过渡完成的方式。
  • useTransition 接受带有 timeoutMs 的延迟响应的值,如果给定的timeoutMs内未完成,它将会强制执行startTransition回调函数内state的更新。
import React, { useEffect, useState, useTransition } from 'react';
import './index.css'

const SearchResult = (props) => {
  const resultList = props.query
    ? Array.from({ length: 100000 }, (_, index) => ({
      id: index,
      keyword: `${props.query} -- 搜索结果${index}`,
    })) : [];
  return resultList.map(({ id, keyword }) => (
    <li key={id}>{keyword}</li>
  ))
}

export default () => {
  const [type, setType] = useState(1)
  const [value, setValue] = useState('');
  const [searchVal, setSearchVal] = useState('');
  const [loading, startTransition] = useTransition({ timeoutMs: 2000 });

  useEffect(() => {
    // 监听搜索值改变
    console.log('对搜索值更新的响应++++++' + searchVal + '+++++++++++')
  }, [searchVal])

  useEffect(() => {
    // 监听输入框值改变
    console.log('对输入框值更新的响应-----' + value + '-------------')
  }, [value])

  useEffect(() => {
    if (type === 1) {
      setSearchVal(value)
    }
    if (type === 2) {
      startTransition(() => {
        setSearchVal(value)
      })
    }
  }, [value]);

  return (
    <div className='App'>
      <h4>UseTransition</h4>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <div className={`type_button ${type === 1 ? 'type_button_checked' : ''}`} onClick={() => setType(1)}>normal</div>
      <div className={`type_button ${type === 2 ? 'type_button_checked' : ''}`} onClick={() => setType(2)}>transiton</div>
      {loading && <p>数据加载中,请稍候...</p>}
      <ul>
        <SearchResult query={searchVal}></SearchResult>
      </ul>
    </div>
  );
}

注意useTransition使用场景:

  • 在明确知道耗时操作速度极快的情况下,可以直接使用返回值中的 startTransition;
  • 如果不能保证响应速度,还是需要使用 isPending 进行过渡状态的判断和展示;
  • 如果对于更新的优先级有较高的要求,可以不使用 useTransition。

4)useDeferredValue

useDeferredValue使用介绍

const [value, setValue] = useState('')
// defferedValue值延后于state更新
const deferredValue = useDeferredValue(value)
  • useTransition 返回一个延迟响应的状态,可以设置最长延迟时间timeoutMs。如果给定的timeoutMs内未完成,它将会强制更新
  • useDeferredValue的不同: useDeferredValue是处理一段逻辑,而useTransition是产生一个新状态
  • useDeferredValue通过useEffect监听传入值的变化,然后通过过渡任务执行值的改变。这样保证defrredValue的更新滞后于setState,同时符合过渡更新的原则,因为是通过transition 调度机制执行的。
import React, { useEffect, useState, useTransition, useDeferredValue } from 'react';
import './index.css'

const SearchResult = (props) => {
  const resultList = props.query
    ? Array.from({ length: 100000 }, (_, index) => ({
      id: index,
      keyword: `${props.query} -- 搜索结果${index}`,
    })) : [];
  return resultList.map(({ id, keyword }) => (
    <li key={id}>{keyword}</li>
  ))
}

export default () => {
  const [value, setValue] = useState('');
  const searchValue = useDeferredValue(value);

  useEffect(() => {
    console.log('对输入框值的响应--------' + value + '---------------')
  }, [value])

  useEffect(() => {
    // 监听搜索值改变
    console.log('对搜索值的更新响应++++++' + searchValue + '+++++++++++')
  }, [searchValue])

  return (
    <div className='App'>
      <h3>useDeferredValue</h3>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <div className='useDeferredValue'>useDeferredValue</div>
      <ul>
        <SearchResult query={searchValue}></SearchResult>
      </ul>
    </div>
  );
};