zoukankan      html  css  js  c++  java
  • react18 来了,我 get 到...

    大家好!

    本文主要是关于即将发布的 react 18 的新特性。那么 react18 带来了什么呢?

    详情可以关注 github React 18 工作组仓库

    1. automatic batching:自动批处理。

    batching 批处理,说的是,可以将回调函数中多个 setState 事件合并为一次渲染,因此是异步的。

    解决的问题是多次同值、不同值 setState, 期望最后显示的是最后一次 setState 的结果,减少渲染。

      const Index = () => {
        const [name, setName] = useState('')
        const [age, setAge] = useState(0)
        
        const change = () => {
          setName('a')
          setAge(1) 
          // 仅触发一次渲染,批处理,2次setState合并为一次渲染
    
          // 需需要立即重渲染,需要手动调用
          // ReactDOM.flushSync(() => {
          //   setName('a') // 立即执行渲染
          //   setAge(1) // 立即执行渲染
          //   // 不会合并处理,即没有批处理,触发2次
          // });
        }
    
        console.log(1) // 只打印一次
    
        return (
          <div>
            <p>name: {name}</p>
            <p>age: {age}</p>
            <button onClick={change}>更改</button>
          </div>
        )
      }
    

    但是 react 18 之前,在 promise、timeout 或者 event 回调中调用多次 setState,由于丢失了上下文,无法做合并处理,所以每次 setState 调用都会立即触发一次重渲染:

     const Index = () => {
       const [name, setName] = useState('')
       const [age, setAge] = useState(0)
       
       const change = () => {
         setTimeout(() => {
           setName('a') // 立即执行渲染
           setAge(1) // 立即执行渲染
           // 不会合并处理,即没有批处理,触发2次
    
           // 若需要批处理,需要手动调用
           // ReactDom.unstable_batchedUpdates(() => {
           //   setName('a')
           //   setAge(1) 
           //   // 合并处理
           // })
           // 并且将 ReactDOM.render 替换为 ReactDOM.createRoot 调用方式
           // 旧 ReactDOM.render(<App tab="home" />, container);
           // 新 ReactDOM.createRoot(container).render(<App tab="home" />)
         }, 0);
       }
    
       console.log(1) // 打印2次
    
       return (
         <div>
           <p>name: {name}</p>
           <p>age: {age}</p>
           <button onClick={change}>更改</button>
         </div>
       )
     }
    

    react18,在 promise、timeout 或者 event 回调中调用多次 setState,会合并为一次渲染。提升渲染性能。

    v18实现「自动批处理」的关键在于两点:

    • 增加调度的流程
    • 不以全局变量 executionContext 为批处理依据,而是以更新的「优先级」为依据

    参考:

    2. concurrent apis:全新的并发 api。比如:startTransition

    Concurrent:并发,采用可中断的遍历方式更新 Fiber Reconciler。是渐进升级策略的产物。

    不同更新触发的视图变化是有轻重缓急的,让高优更新对应的视图变化先渲染,那么就能在设备性能不变的情况下,让用户更快看到他们想看到的UI。

    案例:用户操作滑块,然后响应树的变化。滑块响应是高优先级的,而树的变化可以认为是低优先级的。

    demo

    未开启:可以看到滑块的拖动有卡顿

    开启:可以看到滑块的拖动,非常的丝滑顺畅

    代码实现,将设置更新树的 setState,放到 startTransition 中。而更新滑块的不变,认为是高优先级,优先响应。

    2部分:

    • 紧急响应:滑块。
    • 过渡更新:根据滑块,呈现结果内容。
      import { useTransition } from 'react';
      const [isPending, startTransition] = useTransition();
    
      // 更改滑块触发
      function changeTreeLean(event) {
          const value = Number(event.target.value);
          setTreeLeanInput(value); // 更新滑块
    
          // 是否开启startTransition
          if (enableStartTransition) {
            startTransition(() => {
              setTreeLean(value); // 这个变慢,根据滑块,呈现结果内容。
            });
    
            // react18之前,想要有类似功能。变体,setTimeout,防抖节流
            // setTimeout(() => {
            //   setTreeLean(value)
            // }, 0)
    
          } else {
            setTreeLean(value);
          }
      }
    
      // 过渡期间可以这么处理
      {isPending ? <Spinner /> : <Con>}
    

    setTimeout 更好,能有状态 isPending,且更早更快的呈现更新到界面上(微任务里处理)。而且 setTimeout 是不可中断的,而 startTransition 是可中断的,不会影响页面交互响应。

    依赖于React底层实现的优先级调度模型,被 startTransition 包含的 setState 的优先级会被设置为低优先级的过渡更新。

    参考:

    3. suspense:更好的 suspense。更好的支持在 ssr 和 异步数据 场景下使用 suspense。

    1. ssr 下支持,可参考:React18 中的新 Suspense SSR 架构

    2.透明的异步数据处理(未来18.x支持)

    和写同步逻辑代码一样,写异步代码逻辑。大大的简化了代码逻辑的书写。把代数效应应用到极致了,把异步的副作用剥离了。

    代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。

    场景案例:demo,显示畅销书排行榜。

    其中,名称和日期是一个接口获取,而下面的列表是另一个接口获取。

    从图中,可以明显感到 with suspense 的效果更丝滑,用户体验更好。而代码也非常简洁。部分代码如下:

    ```js
    // 接口部分
    import { fetch } from "react-fetch"
    
    export function fetchBookLists() {
      const res = fetch(`
      https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
    
      const json = res.json()
    
      if (json.status === "OK") {
        return json.results
      } else {
        console.log(json)
        throw new Error("Loading failed, likely rate limit")
      }
    }
    
    // 组件部分
    // 没有处理 loading 状态等的异步处理,和同步已经完全一致的代码书写
    const Content = () => {
      const list = fetchBookLists()[0]
    
      return (
        <>
          <h4>From {list.display_name}</h4>
          <Paragraph sx={{ mt: -3 }}>
            Published on {list.newest_published_date}
          </Paragraph>
          <BookList list={list} />
        </>
      )
    }
    
    export const BestSellers = () => {
      return (
        <Suspense fallback={<Spinner />}>
          {/* loading must happen inside a <Suspense> */}
          <Content />
        </Suspense>
      )
    }
    ```
    

    而在 react18 之前,你得这么写:

    ```js
    // 接口部分
    import { fetch } from "react-fetch"
    export async function fetchBookLists() {
      const res = await fetch(`
      https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
    
      const json = await res.json()
    
      if (json.status === "OK") {
        return json.results
      } else {
        console.log(json)
        throw new Error("Loading failed, likely rate limit")
      }
    }
    
    // 组件部分,按照异步的逻辑写,写loading,对异步结果的处理等
    function useNYTBestSellerLists() {
      // poor man's useQuery implementation
      const [isLoading, setIsLoading] = useState(false)
      const [lists, setLists] = useState(null)
    
      useEffect(() => {
        setIsLoading(true)
    
        fetchBookLists()
          .then((lists) => {
            setLists(lists)
            setIsLoading(false)
          })
          .catch(() => setIsLoading(false))
      }, [])
    
      return { isLoading, lists }
    }
    
    export const BestSellers = () => {
      const { isLoading, lists } = useNYTBestSellerLists();
    
      if (isLoading) {
        return <Spinner />;
      }
    
      if (!lists) {
        return "not loading or error";
      }
    
      const list = lists[0];
    
      return (
        <>
          <h4>From {list.display_name}</h4>
          <Paragraph sx={{ mt: -3 }}>
            Published on {list.newest_published_date}
          </Paragraph>
          <BookList list={list} />
        </>
      );
    }
    ```
    

    参考:

    3.优化 suspense 的行为表现。

    场景举例:

        <Suspense fallback={<h3>loading...</h3>}>
          <LazyCpn /> // 为 React.lazy 包裹的异步加载组件
          <Sibling /> // 普通组件
        </Suspense>
    

    由于 Suspense 会等待子孙组件中的异步请求完毕后再渲染,所以当代码运行时页面首先会渲染 fallback:loading。而在loading这个过程中,页面表现是一致的,但是背后的行为是不一致的:

    • react18 之前:即在 Legacy Suspense 中,Sibling 组件会立即安装到 DOM 并触发其效果/生命周期。页面上隐藏。
    • react18:即在 Concurrent Suspense 中,Sibling 组件没有挂载到 DOM。它的效果/生命周期也不会在 ComponentThatSuspends 解决之前触发。

    react18,Sibling 不会执行,会等 suspense 包裹的组件都加载完才执行渲染

    优化的是提交渲染的流程:

    打断兄弟组件并阻止他们提交。等待提交 Suspense 边界内的所有内容- 挂起的组件及其所有兄弟组件 - 直到挂起的数据解决。然后在一个单一的、一致的批次中同时提交整个树渲染。

    参考:

    4. 其他

    比如:新 Hook —— useId

    解决问题:ssr 场景下,客户端、服务端生成的id不匹配!官方推出 Hook——useId解决,每个 id 代表该组件在组件树中的层级结构。

    function Checkbox() {
      // 生成唯一、稳定id
      const id = useId();
      return (
        <>
          <label htmlFor={id}>Do you like React?</label>
          <input type="checkbox" name="react" id={id} />
        </>
      );
    );
    

    参考:为了生成唯一id,React18专门引入了新Hook:useId

    最后

    这几个重大的更新,目的都是较少渲染、根据优先级响应、提升性能、拥有更好的体验。非常值得期待。

    想尝鲜的可安装 react18 beta 版(2021-11-16发布的)

    # npm
    npm install react@beta react-dom@beta
    # yarn
    yarn add react@beta react-dom@beta
    
  • 相关阅读:
    Base64正反编码
    json数据测试接口
    ajax上传进度条
    ajax利用php上传图片
    ajax缓存 header头文件
    ajax同步与异步
    ajax的post请求与编码
    ajax的get请求与编码
    ajax获取服务器响应信息
    创建ajax对象
  • 原文地址:https://www.cnblogs.com/EnSnail/p/15679835.html
Copyright © 2011-2022 走看看