react18新特性

react18新特性,第1张

前言:最近在忙于项目,又接了一个谷歌浏览器插件开发的任务,一直没有时间看新的技术更新,突然发现react已经更新到了18,随着react18正式版本发布,我们可以使用新版带来的新特性,快来一起看看都提供了哪些新的特性供我们使用吧。

注意:学习用例使用vite + react18搭建项目

一、初始化项目 1、初始化一个项目
npm init -y
2、安装react和react-dom
npm install react react-dom --save
3、安装vite和可以react热更新的@vitejs/plugin-react-refresh插件
npm install vite @vitejs/plugin-react-refresh --save-dev
4、配置@vitejs/plugin-react-refresh插件,在项目中新建vite.config.js文件
import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
export default defineConfig({
  plugins: [reactRefresh()],
});
5、package.json设置启动命令
"scripts": {
  "start": "vite"
},

默认会找当前目录下index.html文件,因此,需要创建index.html文件

DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vite-react18title>
head>
<body>
  <div id="root">div>
  <script src="./main.jsx" type="module">script>
body>
html>
6、根据路径新建main.jsx入口文件。
  • react18之前写法
import React from 'react';
import {render} from 'react-dom';

const root = document.querySelector('#root');
const element = 测试;
render(element, root);

此时启动项目npm run vite

会看到控制台报错信息,react18不让继续使用此方法。

  • react18写法:
import React from 'react';
import {createRoot} from 'react-dom/client';

const root = document.querySelector('#root');
const element = 测试;
createRoot(root).render(element);
二、批量更新
  • 在react中多次的setState合并到一次进行渲染。
  • 在react18中更新是以优先级为依据进行合并。
1、旧版本react以前合并更新:
  • 合并更新演示代码:
import React, { Component } from "react";

export default class OldBatchUpdate extends Component {
  state = { age: 0 };

  handleClick = () => {
    this.setState({ age: this.state.age + 1 });
    console.log(this.state.age); // 0
    this.setState({ age: this.state.age + 1 });
    console.log(this.state.age); // 0
    this.setState({ age: this.state.age + 1 });
    console.log(this.state.age); // 0
    setTimeout(() => {
      this.setState({ age: this.state.age + 1 });
      console.log(this.state.age); // 2
      this.setState({ age: this.state.age + 1 });
      console.log(this.state.age); // 3
    });
  };
  render() {
    return (
      <div>
        <span>{this.state.age}</span>
        <button onClick={this.handleClick}>+</button>
      </div>
    );
  }
}

  • react18以前版本合并更新原理代码:
let state = { age: 0 };
let isBatchUpdate = false; // 批量更新标识
const updaeQueue =[]; // 批量更新队列
function setState(newState){
  if(isBatchUpdate){
    updaeQueue.push(newState);
  }else{
    state = newState;
  }
}
function handleClick() {
  setState({ age: state.age + 1 });
  console.log(state.age);
  setState({ age: state.age + 1 });
  console.log(state.age);
  setState({ age: state.age + 1 });
  console.log(state.age);
  setTimeout(() => {
    setState({ age: state.age + 1 });
    console.log(state.age);
    setState({ age: state.age + 1 });
    console.log(state.age);
  });
}
// 更新函数
function batchUpdate(fn){
  isBatchUpdate= true; // 启用批量更新
  fn();
  isBatchUpdate = false; // 关闭批量更新,此时同步任务执行结束,异步任务还没开始,所以,进入异步任务后,批量更新标识为false,也就是关闭了批量更新。
  console.log(updaeQueue, 'updaeQueue')
  updaeQueue.forEach(item=>{
    state = item;
  });
  updaeQueue.length = 0;
}

batchUpdate(handleClick);
2、react18中新的更新机制
  • 演示代码:
import React, { Component } from "react";

export default class BatchUpdate extends Component {
  state = { age: 0 };

  handleClick = () => {
    this.setState({ age: this.state.age + 1 });
    console.log(this.state.age);  // 0
    this.setState({ age: this.state.age + 1 });
    console.log(this.state.age);// 0
    setTimeout(() => {
      this.setState({ age: this.state.age + 1 });
      console.log(this.state.age); // 1
      this.setState({ age: this.state.age + 1 });
      console.log(this.state.age);  // 1
    });
  };
  render() {
    return (
      
        {this.state.age}
        
      
    );
  }
}

注意:可见,不论是在合成事件中,还是在宏任务中,都是会合并更新

const updaeQueue = [];// 更新队列
let onePriority = 1; // 更新优先级1,数字越小更新优先级越高
let towPriority = 2; // 更新优先级2
let larstPriority; // 上一次更新优先级
let state = { age: 0 }; // 初始化状态

function setState(newState, priority) {
  updaeQueue.push(newState);
  if (priority === larstPriority) {
    return;
  }
  larstPriority = priority;
  setTimeout(() => { // 模拟热更新
    updaeQueue.forEach((item) => {
      state = item;
    });
    updaeQueue.length =0;
  });
}
// 新版更新不再依靠是合成事件或者宏任务微任务作为区分,而是,根据更新优先级来处理。
function handleClick() {
  setState({ age: state.age + 1 }, onePriority);
  console.log(state.age);// 0
  setState({ age: state.age + 1 }, onePriority);
  console.log(state.age); // 0
  setTimeout(() => {
    setState({ age: state.age + 1 }, towPriority);
    console.log(state.age); // 1
    setState({ age: state.age + 1 }, towPriority);
    console.log(state.age); // 1
  });
}
handleClick()
三、Suspense
  • Suspense让你的组件在渲染完成之前进行等待,等待期间显示fallback中的内容。
  • Suspense内的组件子树比其他组件数优先级更低。
  • 完全同步写法,没有任何异步callback之类东西。
1、Suspense执行流程
  • 在render函数中我们可以使用异步请求数据,而不使用await或者promise。
  • react会从缓存中读取这个请求数据的promise。
  • 如果没有请求完成就抛出一个promise异常。
  • 当这个promise完成后(数据请求完成),react会重新回到原来的render中,将请求回来数据加载出来
2、Suspense子组件中直接调用promise举例:
import React, { Suspense, lazy } from "react";
const Home = lazy(() => import("../Home"));
import ErrorBoundary from "../ErrorBoundary";
export default function SuspensePage() {
  return (
    
      
        
      
    
  );
}

其中Home组件如下:

import React from 'react';
import {login} from '/src/services'
import { wrapPromise } from '../../utils';
const myLogin = login();
const loginRes = wrapPromise(myLogin);
export default function Home(){
  const logins = loginRes.read(); // 此处直接调用promise,没有使用await或者then
  return 返回结果:{logins.success?"成功":'失败'}, 请求返回信息: {logins.message}
}
3、wrapPromise代码要遵顼Suspense流程
export function wrapPromise(promise) {
  let status = "pending";
  let result;
  const subspender = promise
    .then((resolve) => {
      result = resolve;
      status = "success";
      console.log(resolve, "success");
    })
    .catch((error) => {
      console.log(error, "err");
      status = "error";
      result = error;
    });
  return {
    read() {
      if (status === "pending") {
        throw subspender;
      } else if (status === "error") {
        throw result;
      } else if (status === "success") {
        return result;
      }
    },
  };
}
4、ErrorBoundary组件
mport React, {Component} from 'react';

class ErrorBoundary extends Component{
  state = {hasError: false, error: null};

  static getDerivedStateFromError(err){ // 用于报错时候ui切换
    return {hasError: true, error: err}
  }

  componentDidCatch(error, info){ // 用于上报错误信息
    console.log(error, info);
  }
  render(){
    if(this.state.hasError){
      return 报错{this.state.error.toString()}
    }
    return this.props.children;
  }
}
export default ErrorBoundary;
5、Suspense原理
import React, { Component } from "react";
class Suspense extends Component {
  state = { loading: false };
  componentDidCatch(error) {
    if (typeof error.then === "function") {
      this.setState({ loading: true });
      error.then(() => {
        this.setState({ loading: false });
      });
    }
  }
  render() {
    const { loading = false } = this.state;
    const { fallback, children } = this.props;
    if (loading) {
      return <div>{fallback}</div>;
    }
    return children;
  }
}
export default Suspense;
四、 startTransition 1、并发更新
  • 并发更新就是可以中断渲染的架构。
  • 什么时候中断渲染呢?当有更高级别渲染到来时,先放弃当前正在渲染的东西,而是,去立即执行更高级别的渲染,换来视觉上更快的相应速度。
  • 在react18中以是否使用并发特性,作为是否开启并发更新的依据。
2、更新优先级
  • react18以前没有更新优先级的概念,所有更新都要排队,不管优先级高不高,都要等待上一个更新执行结束才能执行。
  • react18为什么又要有更新优先级呢?用户对于不同的 *** 作对交互的执行速度有着不同的预期。所以,我们可以根据用户的预期赋予不同的优先级。
    • 高优先级:用户输入,窗口缩放,窗口拖拽。
    • 低优先级:数据请求和文件下载。
  • 高优先级更新会中断低优先级更新,等高优先级执行完以后,低优先级更新会根据高优先级执行结果重新更新。
  • 对于cpu-bound的更新(例如:创建新的DOM节点),并发意味着一个更急迫的渲染可以中断已经开始的更新。
3、开启过渡更新(startTransition)

在输入框搜索东西的使用场景下,输入框优先级要比联想词高,所以,可以对联想词设置开启过渡更新。使用方法就是set值外包裹一层。

const [word, setWord]= useState([]);

startTransition(()=>{
    setWord(new Array(10000).fill(1));
})

如果不开启过渡更新就会在输入内容时候卡死。开启后,会很流畅。

import React, { startTransition, useState, useEffect } from "react";
function AssociativeWord({ word }) {
  const [wordList, setWordList] = useState([]);
  useEffect(() => {
    if (word.length > 0) {
      startTransition(()=>{
        setWordList(new Array(20000).fill(word));
      })
    }
  }, [word]);
  return (
    
    {wordList.map((item, index) => { return
  • {item}
  • ; })}
); } export default function StartTransitionPage() { const [word, setWord] = useState(""); function handleInput(event) { const { target: { value = "" }, } = event; setWord(value); } return ( ); }

注意:低优先级不会被丢弃,只是会在高优先级执行后执行

4、更新优先级问题
import React, {startTransition, useState, useEffect} from 'react';

export default function UpdatePriority(){
  const [result, setResult] = useState('');

  useEffect(()=>{
    console.log(result, 'result');
  }, [result]);
  function handleChangeResult(){
    setResult(item=>item + 'A');
    startTransition(()=>{setResult(item=>item + 'B')});
    setResult(item=>item + 'C');
    startTransition(()=>{setResult(item=>item + 'D')});
  }

  return 
    结果:{result}
    
  
}

结果在控制台输出如下内容:

AC result
ABCD result
5、结论:
  • 每次渲染时候会有一个渲染优先级,找到优先级最高的作为渲染优先级。

  • 虽然优先级不同,但是,最终的结果顺序和调用顺序是一致的。

  • 因为,优先级高的已经渲染过,会有diff比对更新,所以,相当于缓存。但最终还是都要渲染的。当执行到最低优先级时候,按照代码顺序全部执行一次。官方解释是类似于git,在master分支拉取A分支和B分支,但是,正在开发时候遇到master有bug,拉取一个hotfix分支C进行修改,C改完发布了,此时,发布A时候,C代码也会在里边。

五、 useDeferredValue 1、解决的问题

如果某些渲染比消耗性能,比如:实时计算和反馈,我们可以使用useDeferredValue来降低计算优先级,从而提升整体的性能。和startTransition作用类似,只是用法不同。

2、和startTransition的区别
  • startTransition在目的组件中使用,包裹一层setValue来改变计算优先级,从而提升性能。
  • useDeferredValue在源头改变,通过延时改变传入子组件值,降低优先级,提高性能。
  • 一个在源头解决问题,一个在目的地解决问题。
3、使用方法:
import React, { useDeferredValue, useState, useEffect } from "react";
function AssociativeWord({ word }) {
  const [wordList, setWordList] = useState([]);
  useEffect(() => {
    if (word.length > 0) {
      setWordList(new Array(20000).fill(word));
    }
  }, [word]);
  return (
    
    {wordList.map((item, index) => { return
  • {item}
  • ; })}
); } export default function UseDeferredValuePage() { const [word, setWord] = useState(""); const defferedText = useDeferredValue(word); function handleInput(event) { const { target: { value = "" }, } = event; setWord(value); } return ( ); }
六、useTransition
  • 允许组件在切换到下一个组件之前等待加载内容,从而避免不必要的加载状态。
  • useTransition返回两个值的数组。一个是isPending,另外一个是startTransition。
  • 适用于加载很快的地方,将会使得Suspense中fallback不再执行,pending结束后直接渲染出来。
1、双缓冲
  • 当数据量很大时候,绘图需要几秒或者更长的时间,而且,有时候会出现闪烁现象。为了解决这些问题,可采用双缓冲技术来绘图。
  • 双缓冲即在内存中创建一个和屏幕绘图区域一致的对象,先将图像绘制到内存中这个对象上,再一次性将图形拷贝到界面上。这时候会加快绘图的速度。
2、useTransition使用
import React, { lazy, Suspense, useState, useTransition } from "react";
import ErrorBoundary from "../ErrorBoundary";
const Home = lazy(() => import("../Home"));
const DetailPage = lazy(() => import("../DetailPage"));
export default function UseTransitionPage() {
  const [currentHome, setCurrentHome] = useState(true);
  const [isPending, startTransition] = useTransition();
  function handleChangePage() {
    startTransition(() => { // 使用useTransition结构出来startTransition进行包裹
      setCurrentHome(!currentHome);
    });
  }
  return (
    
      {currentHome ?  : }
      
      {isPending?'切换中。。。':'已经切换'}
    
  );
}

以下 Hooks 是为库作者提供的,用于将库深度集成到 React 模型中,通常不用于应用程序代码。

七、 useSyncExternalStore
  • 用来存储数据
import React, { useSyncExternalStore } from "react";

class Store {
value = 0;
listeners = [];
subscribe=(listener)=> {
 this.listeners.push(listener);
}

getValue=()=> {
 return this.value;
}

setValue=(newValue)=> {
 this.value = newValue;
 this.listeners.forEach((l) => l());
}
}

const store = new Store();
export default function UseSyncExternalStorePage() {
const state = useSyncExternalStore(store.subscribe, store.getValue);

function handleAdd() {
 store.setValue(state + 1);
}
return (
 
   {state}
   
 
);
}

  • 类似的store也可以结合换成redux。
import React, { useSyncExternalStore } from "react";
import { createStore } from "redux";
function reducer(state = { number: 0 }, action) {
switch (action.type) {
 case "ADD":
   return { ...state, number: state.number + 1 };
 case 'SUB':
   return {...state, number: state.number - 1};
 default:
   return state;
}
}

const store = createStore(reducer);
export default function UseSyncExternalStorePage() {
const state = useSyncExternalStore(store.subscribe, store.getState);

function handleAdd() {
 store.dispatch({type: 'ADD'});
}
return (
 
   {state.number}
   
 
);
}

最新版本的react-redux使用useSyncExternalStore实现了redux和组件关联。

八、 useInsertionEffect
  • 它在所有 DOM 突变*之前同步触发。*在读取useLayoutEffect之前触发, 由于此挂钩的范围有限,因此此挂钩无法访问 refs 并且无法安排更新。
  • useInsertionEffect应该仅限于 css-in-js 库作者
九、学习参考

git项目地址

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/730343.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存