听说你还不知道React18新特性?看我给你整明白!

前言

目前react的最新版本是18.2.0。React 团队在 2022 年 3 月 29 日正式发布了 React 的第 18 个版本 是 React 框架的最新版本,它主要着眼于解决 React 应用在性能、稳定性、开发体验等方面的问题。本文将介绍 React 18 的升级内容、新特性、新的 API、底层逻辑更新等方面的内容,并通过示例展示其使用效果。 我将在这篇文章里简单介绍 React 18 的新特性,React Concurrent Mode(并发模式)的实现,以及简要的升级指南。

升级

  • react18 已经不支持IE浏览器
  • 新项目: 直接用 npm 或者 yarn 安装最新版依赖即可(如果是js,可以不需要安装types类型声明文件)
  • 改变根节点的挂载方式使用新的 API createRoot,使用旧的 API 仍然兼容,只有在使用 createRoot 了之后才会有 React 18 的新特性。
tsx 复制代码
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App/>
    </Provider>
  </React.StrictMode>,
)

在这个示例中,我们使用了 ReactDOM.createRoot 方法创建了一个根节点,并使用 render 方法将组件渲染到根节点中。这样可以让 React 应用更快地响应用户操作,提高用户体验。

react18 setState异步同步

在 React 18 中,setState 的行为有一些改变,它将更倾向于以异步方式进行更新,但也提供了一些选项来控制同步更新。下面是关于 React 18 中 setState 的异步和同步行为的解释:

1. 异步更新(默认行为):

在 React 18 中,默认情况下,setState 方法会以异步方式进行更新。这意味着它会将多个状态更新批量处理,并在适当的时机进行合并和应用,以优化性能。这样做可以减少不必要的重渲染,并提高应用程序的响应性。

tsx 复制代码
// 异步更新
this.setState({ count: this.state.count + 1 });

2. 同步更新(使用 flushSync):

尽管 setState 默认以异步方式进行更新,但在某些情况下,您可能需要立即获取更新后的状态。为了实现此目的,React 18 提供了 flushSync 方法,可以强制执行同步更新。

tsx 复制代码
import { flushSync } from 'react-dom';

// 同步更新

flushSync(() => {

  this.setState({ count: this.state.count + 1 });

});

通过使用 flushSync 包裹 setState 的调用,您可以确保在执行下一个任务之前立即获取到更新后的状态。请注意,使用 flushSync 可能会对性能产生影响,并且应谨慎使用,以避免阻塞主线程。

需要注意的是,React 18 引入了一种新的异步渲染优先级机制,称为 useTransition。通过使用 useTransition,您可以控制异步更新的优先级。这对于在高优先级工作(例如用户交互)和低优先级工作(例如懒加载数据)之间进行平衡非常有用。然而,它不直接影响 setState 的异步/同步行为,而是影响更新的优先级。

总结一下,在 React 18 中,setState 通常以异步方式进行更新,并且使用 flushSync 可以实现同步更新。此外,您还可以使用 useTransition 提供的优先级控制来平衡不同任务之间的更新。

React18 新增API

React 18 是 React 的一个重要版本,它包含了一些新的特性和改进,其中一些会对应用程序的开发流程、性能和用户体验产生重要影响。以下是 React 18 中新增的一些 API:

1. startTransition

startTransition 是一个新的 React API,旨在帮助开发者优化应用程序的性能和用户体验。这个函数可以告诉 React 在下次重新渲染组件时,应该延迟更新状态。这样,一些较慢的操作(例如异步请求等)就可以在后台执行,不会影响应用程序的交互性能。

tsx 复制代码
import { startTransition } from 'react';

function App() {

  const [searchTerm, setSearchTerm] = useState('');

  const [results, setResults] = useState([]);

  function handleSearch(event) {

    setSearchTerm(event.target.value);

    startTransition(() => {

      fetch(`/api/search?query=${searchTerm}`)

        .then(response => response.json())

        .then(data => setResults(data));

    });

  }

  return (

    <div>

      <input type="text" value={searchTerm} onChange={handleSearch} />

      <ul>

        {results.map(result => (

          <li key={result.id}>{result.title}</li>

        ))}

      </ul>

    </div>

  );

}

在上述代码中,我们使用 startTransition 函数将异步请求和状态更新操作包裹起来,以告诉 React 在下一次重新渲染之前应该延迟更新状态。

2. useTransition

useTransitionstartTransition 的 hook 版本。它可以在函数组件中使用,从而让开发者更方便地控制异步操作的状态。

tsx 复制代码
import { useState, useTransition } from 'react';

function App() {

  const [searchTerm, setSearchTerm] = useState('');

  const [results, setResults] = useState([]);

  const [isPending, setIsPending] = useState(false);

  const [startTransition, isPendingTransition] = useTransition({ timeoutMs: 3000 });

  function handleSearch(event) {

    setSearchTerm(event.target.value);

    startTransition(() => {

      setIsPending(true);

      fetch(`/api/search?query=${searchTerm}`)

        .then(response => response.json())

        .then(data => setResults(data))

        .finally(() => setIsPending(false));

    });

  }

  return (

    <div>

      <input type="text" value={searchTerm} onChange={handleSearch} />

      {isPendingTransition ? <p>Loading...</p> : null}

      <ul>

        {results.map(result => (

          <li key={result.id}>{result.title}</li>

        ))}

      </ul>

    </div>

  );

}

在上述代码中,我们使用 useTransition hook 来控制异步请求的状态,并在加载数据时显示一个 Loading... 的提示信息。

3. createRoot

createRoot 是一个新的入口函数,用于创建根 React 组件。它可以替代原先的 ReactDOM.render 方法,使得开发者可以将多个根节点渲染到一个页面上。

javascript 复制代码
import { createRoot } from 'react-dom';

function App() {

  return (

    <div>Hello, world!</div>

  );

}

// 原先的使用方式

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

// 新的使用方式

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

createRoot(rootElement).render(<App />);

在上述代码中,我们使用 createRoot 函数来创建根 React 组件,并将其渲染到页面上。这样,我们就可以使用多个根节点来构建各种复杂的应用程序界面。

4. useDeferredValue

useDeferredValue 是一个新的 hook,可以将某个状态值的更新延迟一段时间后再执行,从而提高应用程序的性能和用户体验。

javascript 复制代码
import { useState, useDeferredValue } from 'react';

function App() {

  const [searchTerm, setSearchTerm] = useState('');

  function handleSearch(event) {

    setSearchTerm(event.target.value);

  }

  const deferredSearchTerm = useDeferredValue(searchTerm, {

    timeoutMs: 1000

  });

  return (

    <div>

      <input type="text" value={searchTerm} onChange={handleSearch} />

      <p>Search term: {deferredSearchTerm}</p>

    </div>

  );

}

在上述代码中,我们使用 useDeferredValue hook 将搜索词的更新延迟了一秒钟。这样,用户在快速输入搜索词时,不会因为频繁的重新渲染而出现卡顿等问题。

5. useTransition

上面已经提到过了,在 React 18 中新增了 useTransition hook,用于帮助开发者控制异步操作的状态。

6. useMutableSource

useMutableSource 是一个新的 hook,用于获取可变数据源,并可以在多个组件之间共享状态。它可以帮助开发者拆分组件逻辑,并使其更加灵活和可复用。

tsx 复制代码
import { useMutableSource } from 'react';

const myDataSource = {

  get: () => ({ count: 0 }),

  subscribe: (handleUpdate) => {

    const intervalId = setInterval(() => {

      handleUpdate({ count: Math.floor(Math.random() * 100) });

    }, 1000);

    return () => clearInterval(intervalId);

  }

};

function Counter() {

  const [dataSource, setDataSource] = useState(() => myDataSource);

  const [count, setCount] = useState(0);

  function handleUpdate(data) {

    setCount(count => count + data.count);

  }

  useEffect(() => {

    const unsubscribe = dataSource.subscribe(handleUpdate);

    return unsubscribe;

  }, [dataSource]);

  return (

    <div>

      <p>Count: {count}</p>

      <button onClick={() => setDataSource(myDataSource)}>Restart</button>

    </div>

  );

}

function App() {

  const [dataSource, setDataSource] = useState(() => myDataSource);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleRestart() {

    setDataSource(myDataSource);

    forceUpdate();

  }

  const count = useMutableSource(dataSource, ({ count }) => count);

  return (

    <div>

      <p>Count: {count}</p>

      <button onClick={handleRestart}>Restart</button>

    </div>

  );

}

在上述代码中,我们使用 myDataSource 作为可变数据源,并将其共享到多个组件中。在 Counter 组件中,我们订阅了数据源的更新,并实时反映出计数器的变化。在 App 组件中,我们使用了 useMutableSource hook 来获取数据源的值,从而实现了多组件之间的状态共享。

总而言之,React 18 中引入了许多有用的新特性和 API,包括 startTransitionuseTransitioncreateRootuseDeferredValueuseMutableSource 等。这些新特性和 API 可以让开发者更方便地构建高性能、灵活和可复用的 React 应用程序。

新增Hooks

React 18 引入了一些新的 hooks,以帮助开发者更好地管理状态和副作用。以下是 React 18 中新增的一些 hooks:

1. useTransition

useTransition 允许开发者在处理潜在的延迟操作时控制异步更新的优先级。它接受一个配置对象,可以设置超时时间和中断标志等选项。

tsx 复制代码
import { useTransition } from 'react';

function MyComponent() {

  const [isPending, startTransition] = useTransition({ timeoutMs: 2000 });

  function handleClick() {

    startTransition(() => {

      // 执行某个需要较长时间的操作

    });

  }

  return (

    <div>

      <button onClick={handleClick}>开始操作</button>

      {isPending && <p>操作进行中...</p>}

    </div>

  );

}

在上述代码中,我们使用了 useTransition hook 来控制长时间操作的优先级,并在操作进行中显示一个提示信息。

2. useDeferredValue

useDeferredValue 允许开发者将某个状态的更新推迟到未来的帧中。这对于处理与用户输入相关的操作非常有用,可以避免在频繁输入时产生连续的重渲染。

tsx 复制代码
import { useState, useDeferredValue } from 'react';

function MyComponent() {

  const [searchTerm, setSearchTerm] = useState('');

  function handleChange(event) {

    setSearchTerm(event.target.value);

  }

  const deferredSearchTerm = useDeferredValue(searchTerm, {

    timeoutMs: 500

  });

  return (

    <div>

      <input type="text" value={searchTerm} onChange={handleChange} />

      <p>搜索词: {deferredSearchTerm}</p>

    </div>

  );

}

在上述代码中,我们使用了 useDeferredValue hook 来将搜索词的更新推迟了 500ms。这样,在频繁输入时,只有用户停止输入一段时间后,才会执行搜索操作。

3. useMutableSource

useMutableSource 允许开发者访问可变的数据源,并在多个组件之间共享状态。这对于高性能的数据订阅和共享非常有用。

tsx 复制代码
import { useMutableSource } from 'react';

const myDataSource = {

  get: () => ({ count: 0 }),

  subscribe: (callback) => {

    const interval = setInterval(() => {

      callback({ count: Math.floor(Math.random() * 100) });

    }, 1000);

    return () => clearInterval(interval);

  }

};

function MyComponent() {

  const [, forceUpdate] = useState({});

  const count = useMutableSource(myDataSource, source => source.get());

  function handleRestart() {

    forceUpdate({});

  }

  return (

    <div>

      <p>Count: {count}</p>

      <button onClick={handleRestart}>重启</button>

    </div>

  );

}

在上述代码中,我们使用了 useMutableSource hook 来获取可变数据源中的值,并在计数器组件中共享该状态。

这些是 React 18 中新增的一些重要 hooks。通过使用这些 hooks,开发者可以更好地管理状态、处理潜在的延迟操作,并实现高性能的数据共享。除了这些新增的 hooks,React 18 也支持其他常用的 hooks,如 useStateuseEffectuseCallback 等。

严格模式

React 严格模式(Strict Mode)是一个开发模式,可以帮助开发者发现一些潜在的问题,以提高应用程序的质量。启用严格模式后,React 会执行额外的检查和警告,以帮助开发者发现一些常见问题,并尽早地解决它们。

启用 React 严格模式可以通过在代码中添加如下代码实现:

javascript 复制代码
import React from 'react';

import ReactDOM from 'react-dom';

ReactDOM.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>,

  document.getElementById('root')

);

在上述代码中,我们使用 React.StrictMode 组件来包裹应用程序的顶层组件 <App>。这样,React 将会在严格模式下执行应用程序,并对常见问题进行检查和提示。

React 严格模式主要包含以下几个方面的检查和提示:

  • 识别不安全的生命周期方法,提示开发者修改,这些方法可能会导致意外的副作用或错误。
  • 检测意外的副作用,例如:多余的重新渲染、不符合预期的函数调用等。
  • 检测某些过时的 API 使用,提供更好的替代方案。
  • 检测警告信息,使其更加明显和易于发现。

需要注意的是,React 严格模式只在开发环境下工作,不会影响生产环境下的应用程序。因此,在开发过程中启用严格模式可以帮助开发者及早发现问题,并尽可能将这些问题解决,以提高应用程序的稳定性和质量。

总而言之,React 严格模式是一种非常有用的开发模式,可以帮助开发者发现常见问题并提高应用程序的质量。通过在顶层组件中添加 <React.StrictMode> 包裹,我们可以启用严格模式,并享受其带来的好处。

如何禁用严格模式

在 React 应用中禁用严格模式可以通过以下两种方式实现:

1. 直接移除 <React.StrictMode> 组件

最简单的方法是将应用程序顶层组件中的 <React.StrictMode> 组件直接移除。这样,React 将不会启用严格模式,也不会执行额外的检查和警告。

tsx 复制代码
import React from 'react';

import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(

  <App />,

  document.getElementById('root')

);

2. 在应用程序启动时禁用严格模式

在一些情况下,移除 <React.StrictMode> 组件可能不太方便,例如:在大型项目中或已经存在大量的 console.log 调用等代码片段。此时,可以在应用程序启动时禁用严格模式。

在应用程序启动文件中,我们可以使用 React 的 unstable_disableDevMode() 函数来禁用严格模式:

tsx 复制代码
import React from 'react';

import ReactDOM from 'react-dom';

import App from './App';

React.unstable_disableDevMode();

ReactDOM.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>,

  document.getElementById('root')

);

在上述代码中,我们在调用 ReactDOM.render 之前调用了 React.unstable_disableDevMode() 函数,以禁用严格模式。该函数并不在文档中明确提供支持,因此请谨慎使用。

需要注意的是,禁用严格模式可能会导致一些潜在问题无法被及早发现,因此建议仅在必要时使用。同时,需要确保 React 版本兼容性,并遵循最佳实践和安全规则。

并发模式

React 并发模式(React Concurrent Mode)是 React 的一项新功能,旨在改善在复杂应用程序中的用户体验和性能。在传统的 React 中,更新组件树时会阻塞用户界面的响应,可能导致卡顿和延迟。而并发模式通过将任务分解为多个小步骤,让 React 在执行渲染和布局时可以中断和恢复任务,从而提供更平滑和响应式的用户体验。

在 React 并发模式中,引入了两个主要概念:任务调度和优先级。任务调度器负责决定哪些任务执行、何时执行以及中断和恢复任务。优先级允许 React 根据任务的紧迫性来安排任务的执行顺序,确保响应度更高的任务能够优先执行。

利用并发模式,React 可以将渲染过程分解为多个小任务,并根据优先级来动态调整任务执行的顺序。这样,在浏览器空闲时间或网络请求等异步操作期间,React 可以暂停当前任务,执行其他具有更高优先级的任务,以实现更爽快的用户交互体验。

总而言之,React 并发模式通过任务调度和优先级机制,提供了更好的用户体验和性能,使得 React 应用程序能够更加平滑地响应用户操作。

以下是一个简单的示例代码,展示了 React Concurrent Mode 的基本用法:

tsx 复制代码
import React, { useState, useEffect, unstable_ConcurrentMode as ConcurrentMode } from 'react';


function App() {

  const [count, setCount] = useState(0);

  useEffect(() => {

    const timer = setInterval(() => {

      setCount((prevCount) => prevCount + 1);

    }, 1000);

    return () => {

      clearInterval(timer);

    };

  }, []);

  return (

    <ConcurrentMode>

      <div>

        <h1>计数器</h1>

        <p>{count}</p>

      </div>

    </ConcurrentMode>

  );

}

export default App;

在上面的示例中,我们使用了 unstable_ConcurrentMode 组件来包裹根元素。这表示该组件下的子组件可以享受到并发模式的好处。

App 组件中,我们使用了 useState 来声明一个状态变量 count,并通过 setCount 来更新它的值。在 useEffect 中,我们使用定时器每秒钟增加 count 的值。注意,我们传递了空数组作为第二个参数,表示只在组件挂载时执行一次。

最后,在组件的返回值中,我们使用 <ConcurrentMode> 组件包裹了整个应用程序的 UI。这样,React 将会利用并发模式来处理渲染任务,以提供更平滑和响应式的用户体验。

服务端渲染

React 18 并没有专门针对服务端渲染(SSR)进行大规模的改进,但它仍然提供了一些与 SSR 相关的 API 和改进。以下是一些我们在 React 18 中可以使用的 SSR 相关功能:

1. useOpaqueIdentifier

useOpaqueIdentifier 允许开发者生成与数据不相关的、不透明的标识符,并在 SSR 上使用这些标识符来生成唯一的 DOM ID。

tsx 复制代码
import { useOpaqueIdentifier } from 'react';

function MyComponent() {
  const id = useOpaqueIdentifier();

  return <div id={`my-component-${id}`}>My component</div>;
}

在上述代码中,我们使用了 useOpaqueIdentifier hook 来生成一个不透明的标识符,并将其用于组件的 DOM ID 中。由于这个标识符与数据无关,因此在 SSR 上也可以正确地生成唯一的 ID。

2. ReactDOMServer.renderToStringAsync

ReactDOMServer.renderToStringAsync 允许开发者异步地渲染组件并输出 HTML。这样可以避免在 SSR 期间阻塞主线程,在数据加载和计算时保持响应性。

tsx 复制代码
import ReactDOMServer from 'react-dom/server';

async function renderApp(req, res) {
  const app = <MyApp />;
  const html = await ReactDOMServer.renderToStringAsync(app);
  res.send(html);
}

在上述代码中,我们使用了 ReactDOMServer.renderToStringAsync 方法异步地将 <MyApp /> 组件渲染为 HTML,并在 Express 中将其发送到客户端。

3. Concurrent Mode

Concurrent Mode 是 React 18 中引入的一个新特性,它通过异步渲染和交互优先级控制等方式提升了应用程序的响应性。在 SSR 中,Concurrent Mode 可以帮助开发者更好地处理异步数据加载和渲染等任务。

tsx 复制代码
import { unstable_createRoot } from 'react-dom';

async function renderApp(req, res) {
  const app = <MyApp />;
  const root = unstable_createRoot(document.createDocumentFragment());
  await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟数据加载延迟
  root.render(app);
  const html = ReactDOMServer.renderToString(root);
  res.send(html);
}

在上述代码中,我们使用了 unstable_createRoot 方法来创建一个 Concurrent Mode 的根节点。在数据加载完成后,我们渲染了应用程序,并将其输出为 HTML。

这些是 React 18 中与 SSR 相关的一些功能和改进。通过使用这些功能,开发者可以更好地处理异步数据加载和渲染,并提升应用程序的响应性。

相关推荐
anyup_前端梦工厂3 分钟前
uni-app 认识条件编译,了解多端部署
前端·vue.js·uni-app
Fetters0436 分钟前
一篇快速上手 Axios,一个基于 Promise 的网络请求库(涉及原理实现)
前端·ajax·axios·promise
蒟蒻的贤39 分钟前
vue11.22
开发语言·前端·javascript
爱上语文41 分钟前
Axios案例练习
前端·javascript·css·html
河畔一角42 分钟前
升级react@18.3.1后,把我坑惨了
前端·react.js·低代码
天天进步201543 分钟前
Vue 3 + Vite:构建闪电般快速的开发环境
前端·javascript·vue.js
Dragon Wu1 小时前
前端框架 Redux tool RTK 总结
前端·javascript·前端框架
yqcoder1 小时前
reactflow 中 useStoreApi 模块作用
前端·javascript
williamdsy1 小时前
【vue】关于异步函数使用不当导致的template内容完全无法渲染的问题
前端·javascript·vue.js
2402_839708051 小时前
第十章:作业
开发语言·前端·javascript