前言
主动上卷,各大公众号都 介绍了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
文件结构
文件配置
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
- type="module" 可以导入es6模块,启用esm模块机制
项目启动
- 测试结果
新特性
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 模式(旧模式)
- React内部,状态更新由isBatchingUpdate 的状态决定是推入队列还是立即更新
- isBatchingUpdate 默认false 普通seState状态更新被触发则为true,同时将state push至更新队列,等待批量更新(异步更新)
- 中途如若遇到原生js里调用了 setState ,则isBatchingUpdate为false,提前执行等待被更新的状态,并在此基础上,立即执行新状态的更新(同步更新)
concurrent 模式
18下的可选模式 :concurrent 模式
- 不管什么情况下 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的显示数据
- forwards :从上往下(根据书写位置上的从上往下,如果前面的返回的慢最后还是会和后面的一起显示)
- together:默认是together ,一起显示
- backwards: 从下往上依次显示
- tail
- collapsed : 加载谁显示谁;
- 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());
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 返回两个值的数组
- startTransition 是一个接受回调的函数,我们用它告诉React需要推迟的state
- 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 实用的样子
最后 码字不易 如果觉得本文有帮助 记得点赞三连哦 十分感谢