React 18 的 alpha 尝鲜

546 阅读6分钟

前言

主动上卷,各大公众号都 介绍了18 ,这几天利用下班时间特意把玩了下,并做个记录。 React18 git位置

构建工具

构建工具尝试下vite,耐不住好奇心,多尝试,多学习

  • vite 是vue 的作者开发的web开发构建工具
  • 基于浏览器原生es模块导入的服务器
  • 在开发环境下,利用浏览器去解析import,在服务器按需编译返回,完全跳过了打包这个概念
  • 服务器随启随用
  • 不仅对vue文件提供支持react 也ok,还支持热更新,热更新也不会随着模块增多而变慢 vite官网解释

项目初始化

  • npm init -y
  • npm install react@alpha react-dom@alpha @types/react @types/react-dom -S
  • npm install vite typescript @vitejs/plugin-react-refresh -D
  • npm install react-router-dom @types/react-router-dom

文件结构

image.png

文件配置

tsconfig.json

{
  "compilerOptions": {
   就是很普通的配置,省略,减少篇幅
  },
  "include": ["./src", "src/routes/.tsx"]
}

vite.config.ts

写的时候有被惊到,这也太简约了

  • 主要就是配置了一个热更新用的插件
import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
export default defineConfig({
  plugins: [reactRefresh()],
});

package.json

  • 命令配置
 "scripts": {
    "dev": "vite",
    "build": "tsc && vite build"
  },
  

入口文件

  • src/main.tsx
  • index.html
    1. type="module" 可以导入es6模块,启用esm模块机制

image.png

项目启动

  • 测试结果 image.png

新特性

1.Automatic batching批量更新

不同模式下的setState 的输出

state = { number: 0 };
  handleClick = () => {
    const { number } = this.state;
    this.setState({
      number: this.state.number + 1,
    });
    console.log(this.state); //同步模式:0;批量模式:0
    this.setState({
      number: this.state.number + 1,
    });
    console.log(this.state); //同步模式:0;批量模式:0
    setTimeout(() => {
      this.setState({
        number: this.state.number + 1,
      });
      console.log(this.state); //同步模式:2;批量模式:1
      this.setState({
        number: this.state.number + 1,
      });
      console.log(this.state); //同步模式:3;批量模式:1
    });

legacy 模式(旧模式)

image.png

  • React内部,状态更新由isBatchingUpdate 的状态决定是推入队列还是立即更新
  • isBatchingUpdate 默认false 普通seState状态更新被触发则为true,同时将state push至更新队列,等待批量更新(异步更新)
  • 中途如若遇到原生js里调用了 setState ,则isBatchingUpdate为false,提前执行等待被更新的状态,并在此基础上,立即执行新状态的更新(同步更新)

concurrent 模式

18下的可选模式 :concurrent 模式 image.png

  • 不管什么情况下 setState都是异步的批量更新
  • 每个状态更新都有自己的任务优先级priority,优先级相同的更新会被合并;值越小优先级越高(题外话
  • concurrent 模式,setTimeout里的setState和普通的setState,优先级是同级的,于是会被合并。

小结

concurrent 模式 下通过priority判断优先级相同更新会被合并,把 原生调用的setState 也做了合并操作(priority的优先级等级列表,可以自行查阅

2. Suspense 容器组件

这个其实17就已经有了,但React 18 的更新后全面支持 Suspense ,对依赖异步数据的组件,实现懒加载。

调用方式

  • Suspense fallback属性用于render 等待时候的组件ui
  • Suspense 默认loading 是false 所以渲染children<User/>
    <Suspense fallback={<div>loading</div>}>
       <User />
   </Suspense>

补充:Suspense的外层可以再写一个ErrorBoundary 做数据请求的错误处理,利用getDerivedStateFromError

子组件Use

  • 依赖一个异步加载数据方法createResource.read()
let userResource = createResource(fetchUser(1));
function User() {
  let { success, data, error }: any = userResource.read();
  if (success) {
    let { id, name } = data;
    return (
      <div>
        {id}
        {name}
      </div>
    );
  } else {
    return <div>数据出错了</div>
  }
}

异步请求

  • createResource 默认 pending; 则走 抛出异常逻辑 入参 promise 并执行,这个异常 可以 被 Suspense组件内部的componentsDidCatch 捕获
  • componentsDidCatch 捕获异常,判断这个异常是不是一个promise 是的 setState loading 为true 渲染fallback 的loading状态 (用户界面上,数据未返回时 渲染的是<div>loading</div>
  • 无论promise执行结果如何,执行完毕,Suspense组件内部loading状态都更新未false,触发Suspense的render,并渲染 <User />
function fetchUser(id: number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve({ success: true, data: { id: 1, name: "zoe" + id } });
      // 请求失败的处理
      reject({ success: false, error: "数据加载失败" });
    });
  });
}
function createResource(promise: Promise<any>) {
  let status = "pending";
  let result: any;
  return {
    read() {
      if (status === "success" || status === "error") {
        return result;
      } else {
        throw promise.then(
          (data: any) => {
            status = "success";
            result = data;
          },
          (error: any) => {
            status = "error";
            result = error;
          }
        );
      }
    },
  };
}

我不会录屏,这个效果 ... 嗯..读者小伙伴自己想象吧

小结

createResource 首先走到异常逻辑里,执行promise, promise 修改success 状态 会回到if里 return 出promise的执行结果,最终 render <User>

3.SusPenseList

  • 就是在Suspense的基础上包一层,可以支持多个Suspense 懒加载
  • revealOrder属性决定Suspense的显示数据
    1. forwards :从上往下(根据书写位置上的从上往下,如果前面的返回的慢最后还是会和后面的一起显示)
    2. together:默认是together ,一起显示
    3. backwards: 从下往上依次显示
  • tail
    1. collapsed : 加载谁显示谁;
    2. hidden 未加载的不限时 ,加载了才显示
     <SuspenseList revealOrder="forwards" tail="hidden">
        <Suspense fallback={<div>1.loading</div>}>
          <User id={1} />
        </Suspense>
        <Suspense fallback={<div>2.loading</div>}>
          <User id={2} />
        </Suspense>
        <Suspense fallback={<div>3.loading</div>}>
          <User id={3} />
        </Suspense>
      </SuspenseList>

效果自己脑补,我还不会录屏

4.StartTransition

用于非紧急状态更新

例子:输入框输入,底部根据输入框内容render1000条数据,页面在用户输入的时候会有短暂无反应。StartTransition可以做到顺滑的ui 上的render

  • startTransition里的回调是需要被开启渐变更新,降低操作的优先级的任务;
  • 此时如果有其他的用户操作,则会优先执行用户当前的操作。
  • 然后再来执行startTransition里的回调,最后Automatic batching来做状态合并更新,从而实现伴随巨量数据的高频操作的优化
  • 用法如下,降低setSomething的任务优先级,
 startTransition(() => setSomething());

推荐StartTransition原理解释的文章

5.UseDeferredValue

沿用 StartTransition 里的例子

  • UseDeferredValue是简单粗暴让状态延迟更新 有说useDeferredValue 是startTransition的语法糖
const [keyword, setKeyword] = useState<string>("");
  const deferredText = useDeferredValue(keyword);
  const handleChange = () => {
    setKeyword(event.target.value);
  };

6.UseTransition 双缓冲

  • UseTransition,允许组件在切换到下一个界面之前等待内容加载,从而避免不必要的加载状态
  • UseTransition,允许组件将速度较慢的数据获取更新推迟到随后渲染,以便能够立即渲染更重要的更新
  • UseTransition用了两个独立的对象 用于存储需要渲染的的数据,一个直接展示,一个用于接收数据更改后的存储,两个交替展示在用户界面
  • UseTransition hook 返回两个值的数组
    1. startTransition 是一个接受回调的函数,我们用它告诉React需要推迟的state
    2. isPending 是一个布尔值,用于告诉我们是否正在等待数据更新的过渡中,可以用它做过度ui的展示 判断
export default function () {
  const [resource, setResource] = useState(initialResource);
  const [isPending, startTransition] = useTransition();
  return (
    <div>
        <User resource={resource} />
      {/* 按钮点击 切换用户 */}
      <button
        onClick={() => {
          startTransition(() => setResource(createResource(fetchUser(2))));
        }}
      >
        next user
      </button>
      {isPending?<div>加载中。。。</div>:null}
    </div>
  );
}

小结

当我把fetchUser的请求 时间设置 3秒的话 用户切换的效果也是要等3秒以后才切换,然后用isPending 做加载过渡的判断,这....是我打开方式不对吗,并没有觉得这个api 实用的样子

react18 alpha demo 地址

最后 码字不易 如果觉得本文有帮助 记得点赞三连哦 十分感谢