react 18新特性

一、简介

升级

  • 新项目: 直接用 npm 或者 yarn 安装最新版依赖即可(如果是js,可以不需要安装types类型声明文件)
npm i react react-dom --save
    
npm i @types/react @types/react-dom -D

  • 旧项目: 先把依赖中的版本号改成最新,然后删掉 node_modules 文件夹,重新安装:
npm i

Note

react 18已经放弃了对 ie11 的支持,将于 2022年6月15日 停止支持 ie,如需兼容,需要回退到 React 17 版本。

React 18 中引入的新特性是使用现代浏览器的特性构建的,在IE中无法充分polyfill,比如micro-tasks

二、新特性

1.createRoot

1.1、为了更好的管理root节点,支持 new concurrent renderer(并发模式的渲染),它允许你进入concurrent mode(并发模式)。

// React 17
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.render(<App />, root);

// React 18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.createRoot(root).render(<App />);

1.2 React 18 还从 render 方法中删除了回调函数

如果需要在 render 方法中使用回调函数,我们可以在组件中通过 useEffect 实现:

// React 17
const root = document.getElementById('root')!;
ReactDOM.render(<App />, root, () => {
  console.log('渲染完成');
});

// React 18
const AppWithCallback: React.FC = () => {
  useEffect(() => {
    console.log('渲染完成');
  }, []);
  return <App />;
};
const root = document.getElementById('root')!;
ReactDOM.createRoot(root).render(<AppWithCallback />);

1.3更新 TypeScript 类型定义
在定义props类型时,如果需要获取子组件children,那么你需要显式的定义它,例如这样:

// React 17
interface MyButtonProps {
  color: string;
}

const MyButton: React.FC<MyButtonProps> = ({ children }) => {
  // 在 React 17 的 FC 中,默认携带了 children 属性
  return <div>{children}</div>;
};

export default MyButton;

// React 18
interface MyButtonProps {
  color: string;
  children?: React.ReactNode;
}

const MyButton: React.FC<MyButtonProps> = ({ children }) => {
  // 在 React 18 的 FC 中,不存在 children 属性,需要手动申明
  return <div>{children}</div>;
};

export default MyButton;

2.setState 自动批处理

批处理是指为了获得更好的性能,在数据层,将多个状态更新批量处理,合并成一次更新(在视图层,将多个渲染合并成一次渲染

React 18 之前,我们只在 React 事件处理函数 中进行批处理更新。promisesetTimeout原生事件处理函数中、或任何其它事件内的更新都不会进行批处理
2.1、React 事件处理函数

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <button
      onClick={() => {
        setCount1(count => count + 1);
        setCount2(count => count + 1);
        // 在React事件中被批处理
      }}
    >
      {`count1 is ${count1}, count2 is ${count2}`}
    </button>
  );
};

export default App;

2.2setTimeout

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={() => {
        setTimeout(() => {
          setCount1(count => count + 1);
          setCount2(count => count + 1);
        });
        // 在 setTimeout 中不会进行批处理
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App;

2.3原生js事件

import React, { useEffect, useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  useEffect(() => {
    document.body.addEventListener('click', () => {
      setCount1(count => count + 1);
      setCount2(count => count + 1);
    });
    // 在原生js事件中不会进行批处理
  }, []);
  return (
    <>
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </>
  );
};

export default App;

总结:

  • 在 18 之前,只有在react事件处理函数中,才会自动执行批处理,其它情况会多次更新
  • 在 18 之后,任何情况都会自动执行批处理,多次更新始终合并为一次

3.flushSync

如果你想退出批量更新,你可以使用 flushSync

import React, { useState } from 'react';
import { flushSync } from 'react-dom';

const App: React.FC = () => {
  const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={() => {
        flushSync(() => {
          setCount1(count => count + 1);
        });
        // 第一次更新
        flushSync(() => {
          setCount2(count => count + 1);
        });
        // 第二次更新
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App;

4.Suspense

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染
Suspense 不再需要 fallback 来捕获
在 React 18 的 Suspense 组件中,官方对空的fallback 属性的处理方式做了改变:不再跳过 缺失值 或 值为null 的 fallback 的 Suspense 边界。相反,会捕获边界并且向外层查找,如果查找不到,将会把 fallback 呈现为 null
以前,如果你的 Suspense 组件没有提供 fallback 属性,React 就会悄悄跳过它,继续向上搜索下一个边界

// React 17
const App = () => {
  return (
    <Suspense fallback={<Loading />}> // <--- 这个边界被使用,显示 Loading 组件
      <Suspense>                      // <--- 这个边界被跳过,没有 fallback 属性
        <Page />
      </Suspense>
    </Suspense>
  );
};

export default App;

现在,React将使用当前组件的 Suspense 作为边界,即使当前组件的 Suspense 的值为 nullundefined

// React 18
const App = () => {
  return (
    <Suspense fallback={<Loading />}> // <--- 不使用
      <Suspense>                      // <--- 这个边界被使用,将 fallback 渲染为 null
        <Page />
      </Suspense>
    </Suspense>
  );
};

export default App;

如果忘记提供 fallback 属性,也不会有什么问题

三、concurrent Mode(并发模式)

并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行
js是单线程语言,同一时间只能执行一件事情。这样就会导致一个问题,如果有一个耗时任务占据了线程,那么后续的执行内容都会被阻塞
并发模式通过使渲染可中断来修复阻塞渲染限制。
Concurrent模式是React 的新功能,可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整
Concurrent 模式中,React 可以同时更新多个状态。
总结一句话就是:
同步不可中断更新变成了异步可中断更新

我们只需要把 render 升级成 createRoot(root).render() 就可以开启并发模式了

开启并发模式就是开启了并发更新么?
![image.png](https://img-blog.csdnimg.cn/img_convert/bdfa5aa30a811d349b963cc703a1143a.png#clientId=u44aa3b7f-4719-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=560&id=u0cbccd7c&margin=[object Object]&name=image.png&originHeight=616&originWidth=798&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49106&status=done&style=none&taskId=u886bb2e4-0acd-4de0-89d7-cbe6d99a4ea&title=&width=725.454529730742

并发特性指开启并发模式后才能使用的特性,比如:

  • useDeferredValue
  • useTransition

transition用于区分 urgent 和 non-urgent updates

  • Urgent updates 紧急更新,指直接交互,通常指的用户交互。如点击、输入等。这种更新一旦不及时,用户就会觉得哪里不对。
  • Transition updates 过渡更新,如UI从一个视图向另一个视图的更新。通常这种更新用户并不着急看到

1.startTransition

startTransition可以用在任何你想更新的时候。但是从实际来说,以下是两种典型适用场景:

  • 渲染慢:如果你有很多没那么着急的内容要渲染更新。
  • 网络慢:如果你的更新需要花较多时间从服务端获取。这个时候也可以结合Suspense。

2.useTransition

在使用startTransition更新状态的时候,用户可能想要知道transition的实时情况,这个时候可以使用useTransition。

import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();

如果transition未完成,isPending值为true,否则为false。

3.suspense 与transition 结合

4.useDeferredValue

使得我们可以延迟更新某个不那么重要的部分
![image.png](https://img-blog.csdnimg.cn/img_convert/b3cb4cbef89191b38d66d3a3f3a1c0fe.png#clientId=u8bca942b-7130-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=236&id=u9397bdf8&margin=[object Object]&name=image.png&originHeight=260&originWidth=769&originalType=binary&ratio=1&rotation=0&showTitle=false&size=135973&status=done&style=none&taskId=u9452f2d4-1763-4938-ac54-7d6ce5b0659&title=&width=699.0908939385221

四、新的api

1.userId

服务端渲染时,会用到

2.useSyncExternalStore

一般是三方状态管理库使用,我们在日常业务中不需要关注

3.useInsertionEffect

只建议 css-in-js 库来使用,它的执行时机在 DOM 生成之后,useLayoutEffect 之前

const useCSS = rule => {
  useInsertionEffect(() => {
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
};

const App: React.FC<T> = () => {
  const className = useCSS(rule);
  return <div className={className} />;
};

export default App;

总结:
1.@types/react 18.0.40报错 Property ‘children’ does not exist on type ‘IntrinsicAttributes & CollapseProps’

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值