CPU-bound
的更新 (例如创建新的 DOM 节点和运行组件中的代码),并发意味着一个更急迫的更新可以“中断”已经开始的渲染IO-bound
的更新 (例如从网络加载代码或数据),并发意味着 React 甚至可以在全部数据到达之前就在内存中开始渲染,然后跳过令人不愉快的空白加载状态npm install react@alpha react-dom@alpha @types/react @types/react-dom -S
npm install vite typescript @vitejs/plugin-react-refresh -D
node ./node_modules/esbuild/install.js
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
export default defineConfig({
plugins: [reactRefresh()]
})
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"types": ["react/next", "react-dom/next"]
},
"include": ["./src"]
}
package.json
{
"name": "zhufeng-react18",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^18.0.0-alpha-ed6c091fe-20210701",
"react-dom": "^18.0.0-alpha-ed6c091fe-20210701"
},
"devDependencies": {
"@types/react": "^17.0.13",
"@types/react-dom": "^17.0.8",
"@vitejs/plugin-react-refresh": "^1.3.5",
"typescript": "^4.3.5",
"vite": "^2.4.1"
}
}
type='module'
可以导入ES6模块,可以启用ESM模块机制index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
legacy
模式ReactDOM.renders
会同步渲染createRoot
会启用concurrent
并发模式src\main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.createRoot(
document.getElementById('root')!
).render(<h1>hello</h1>);
npm run dev
Concurrent
模式中更新是以优先级为依据进行合并的-f
或 --force
参数npm install react-router-dom @types/react-router-dom --force -S
src\main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
+import {HashRouter as Router,Route,Link} from 'react-router-dom';
+import BatchState from './routes/BatchState';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
+ <Router>
+ <ul>
+ <li><Link to="/BatchState">BatchState</Link></li>
+ </ul>
+ <Route path="/BatchState" component={BatchState}/>
+ </Router>
);
src\routes\BatchState.tsx
import React, { Component } from 'react'
interface Props { }
interface State {
count: number
}
export default class extends Component<Props, State> {
state = { count: 0 }
handleCLick = () => {
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log("count", this.state.count);
this.setState({ count: this.state.count + 1 });
console.log("count", this.state.count);
}, 0);
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleCLick}>+</button>
</div>
);
}
}
等待
,并在等待时显示fallback
的内容callback
之类的东西componentDidCatch
,如果 render()
函数抛出错误,则会触发该函数src\main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import BatchState from './routes/BatchState';
+import Suspense from './routes/Suspense';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
<Router>
<ul>
<li><Link to="/BatchState">BatchState</Link></li>
+ <li><Link to="/Suspense">Suspense</Link></li>
</ul>
<Route path="/BatchState" component={BatchState}/>
+ <Route path="/Suspense" component={Suspense}/>
</Router>
);
src\components\ErrorBoundary.tsx
import React from "react";
interface Props{
fallback:React.ReactNode
}
export default class ErrorBoundary extends React.Component<Props> {
state = {hasError: false, error: null};
static getDerivedStateFromError(error:any) {
return {
hasError: true,
error,
};
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
src\routes\Suspense.tsx
import React, { Component, Suspense } from 'react'
import ErrorBoundary from "../components/ErrorBoundary";
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;
});
}
}
}
}
function fetchData(id: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true, data: { id, name: '张三' } });
//reject({success:false,message:'获取数据发生了错误'});
}, 1000);
});
}
const initialResource = createResource(fetchData(1));
function User() {
const result = initialResource.read();
if (result.success) {
let user = result.data;
return <p>{user.id}:{user.name}</p>;
} else {
return <p>{result.message}</p>;
}
}
export default class extends Component {
render() {
return (
<ErrorBoundary fallback={<h1>出错了</h1>}>
<Suspense fallback={<h1>加载中</h1>}>
<User />
</Suspense>
</ErrorBoundary>
);
}
}
src\components\Suspense.tsx
import React, { Component } from 'react'
interface SuspenseProps {
fallback: React.ReactNode
}
interface SuspenseState {
loading: boolean
}
export default class Suspense extends React.Component<SuspenseProps, SuspenseState> {
mounted: any = null
state = { loading: false };
componentDidCatch(error: any) {
if (typeof error.then === 'function') {
this.setState({ loading: true });
error.then(() => { this.setState({ loading: false }) });
}
}
render() {
const { fallback, children } = this.props;
const { loading } = this.state;
return loading ? fallback : children;
}
}
import React from 'react'
import ReactDOM from 'react-dom'
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import BatchState from './routes/BatchState';
import Suspense from './routes/Suspense';
+import SuspenseList from './routes/SuspenseList';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
<Router>
<ul>
<li><Link to="/BatchState">BatchState</Link></li>
<li><Link to="/Suspense">Suspense</Link></li>
+ <li><Link to="/SuspenseList">SuspenseList</Link></li>
</ul>
<Route path="/BatchState" component={BatchState}/>
<Route path="/Suspense" component={Suspense}/>
+ <Route path="/SuspenseList" component={SuspenseList}/>
</Router>
);
src\routes\SuspenseList.tsx
import React, { Component, Suspense, SuspenseList } from 'react'
import ErrorBoundary from "../components/ErrorBoundary";
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;
});
}
}
}
}
function fetchData(id: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true, data: { id, name: '姓名' + id } });
//reject({success:false,message:'获取数据发生了错误'});
}, 1000 * id);
});
}
let userResourceMap: any = {
1: createResource(fetchData(1)),
2: createResource(fetchData(2)),
3: createResource(fetchData(3))
}
interface UserProps {
id: number
}
function User(props: UserProps) {
const result = userResourceMap[props.id].read();
if (result.success) {
let user = result.data;
return <p>{user.id}:{user.name}</p>;
} else {
return <p>{result.message}</p>;
}
}
export default class extends Component {
render() {
return (
<ErrorBoundary fallback={<h1>出错了</h1>}>
<SuspenseList revealOrder="backwards" tail="collapsed" >
<Suspense fallback={<h1>加载用户3......</h1>}>
<User id={3} />
</Suspense>
<Suspense fallback={<h1>加载用户2......</h1>}>
<User id={2} />
</Suspense>
<Suspense fallback={<h1>加载用户1......</h1>}>
<User id={1} />
</Suspense>
</SuspenseList>
</ErrorBoundary>
);
}
}
import React from 'react'
import ReactDOM from 'react-dom'
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import BatchState from './routes/BatchState';
import Suspense from './routes/Suspense';
import SuspenseList from './routes/SuspenseList';
+import StartTransition from './routes/StartTransition';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
<Router>
<ul>
<li><Link to="/BatchState">BatchState</Link></li>
<li><Link to="/Suspense">Suspense</Link></li>
<li><Link to="/SuspenseList">SuspenseList</Link></li>
+ <li><Link to="/StartTransition">StartTransition</Link></li>
</ul>
<Route path="/BatchState" component={BatchState}/>
<Route path="/Suspense" component={Suspense}/>
<Route path="/SuspenseList" component={SuspenseList}/>
+ <Route path="/StartTransition" component={StartTransition}/>
</Router>
);
src\routes\StartTransition.tsx
import React, { startTransition, useEffect, useState } from 'react';
function getSuggestions(keyword: string):Promise<Array<string>> {
let items = new Array(10000).fill(0).map((item: number, index: number) => keyword + index);
return Promise.resolve(items);
}
interface SuggestionProps {
keyword: string;
}
function Suggestion(props: SuggestionProps) {
const [suggestions, setSuggestions] = useState<Array<string>>([]);
useEffect(() => {
getSuggestions(props.keyword).then(suggestions => {
startTransition(() => {
setSuggestions(suggestions);
})
})
}, [props.keyword]);
return (
<ul>
{
suggestions.map((item: string) => (<li key={item}>{item}</li>))
}
</ul>
)
}
export default function () {
const [keyword, setKeyword] = useState<string>("");
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value);
};
return (
<div>
请输入商品关键字<input value={keyword} onChange={handleChange} />
<Suggestion keyword={keyword} />
</div>
);
}
useDeferredValue
内部会调用useState并触发一次更新,但此更新的优先级很低src\main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import BatchState from './routes/BatchState';
import Suspense from './routes/Suspense';
import SuspenseList from './routes/SuspenseList';
import StartTransition from './routes/StartTransition';
+import UseDeferredValue from './routes/UseDeferredValue';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
<Router>
<ul>
<li><Link to="/BatchState">BatchState</Link></li>
<li><Link to="/Suspense">Suspense</Link></li>
<li><Link to="/SuspenseList">SuspenseList</Link></li>
<li><Link to="/StartTransition">StartTransition</Link></li>
+ <li><Link to="/UseDeferredValue">UseDeferredValue</Link></li>
</ul>
<Route path="/BatchState" component={BatchState}/>
<Route path="/Suspense" component={Suspense}/>
<Route path="/SuspenseList" component={SuspenseList}/>
<Route path="/StartTransition" component={StartTransition}/>
+ <Route path="/UseDeferredValue" component={UseDeferredValue}/>
</Router>
);
src\routes\UseDeferredValue.tsx
import React, { startTransition, useEffect, useState,useDeferredValue } from 'react';
function getSuggestions(keyword: string):Promise<Array<string>> {
let items = new Array(10000).fill(0).map((item: number, index: number) => keyword + index);
return Promise.resolve(items);
}
interface SuggestionProps {
keyword: string;
}
function Suggestion(props: SuggestionProps) {
const [suggestions, setSuggestions] = useState<Array<string>>([]);
useEffect(() => {
getSuggestions(props.keyword).then(suggestions => {
setSuggestions(suggestions);
})
}, [props.keyword]);
return (
<ul>
{
suggestions.map((item: string) => (<li key={item}>{item}</li>))
}
</ul>
)
}
export default function () {
const [keyword, setKeyword] = useState("");
const deferredText = useDeferredValue(keyword);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value);
};
return (
<div>
请输入商品关键字<input value={keyword} onChange={handleChange} />
<Suggestion keyword={deferredText} />
</div>
);
}
useTransition
允许组件在切换到下一个界面之前等待内容加载,从而避免不必要的加载状态import React from 'react'
import ReactDOM from 'react-dom'
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import BatchState from './routes/BatchState';
import Suspense from './routes/Suspense';
import SuspenseList from './routes/SuspenseList';
import StartTransition from './routes/StartTransition';
import UseDeferredValue from './routes/UseDeferredValue';
+import UseTransition from './routes/UseTransition';
ReactDOM.createRoot(
document.getElementById('root')!
).render(
<Router>
<ul>
<li><Link to="/BatchState">BatchState</Link></li>
<li><Link to="/Suspense">Suspense</Link></li>
<li><Link to="/SuspenseList">SuspenseList</Link></li>
<li><Link to="/StartTransition">StartTransition</Link></li>
<li><Link to="/UseDeferredValue">UseDeferredValue</Link></li>
+ <li><Link to="/UseTransition">UseTransition</Link></li>
</ul>
<Route path="/BatchState" component={BatchState}/>
<Route path="/Suspense" component={Suspense}/>
<Route path="/SuspenseList" component={SuspenseList}/>
<Route path="/StartTransition" component={StartTransition}/>
<Route path="/UseDeferredValue" component={UseDeferredValue}/>
+ <Route path="/UseTransition" component={UseTransition}/>
</Router>
);
src\routes\UseTransition.tsx
import React, { Component, Suspense, useTransition, useState } from 'react'
import ErrorBoundary from "../components/ErrorBoundary";
function fetchData(id: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true, data: { id, name: '张三' + id } });
//reject({success:false,message:'获取数据发生了错误'});
}, 3000);
});
}
interface UserProps {
resource: any
}
function User(props: UserProps) {
const result = props.resource.read();
if (result.success) {
let user = result.data;
return <p>{user.id}:{user.name}</p>;
} else {
return <p>{result.message}</p>;
}
}
function createResource(promise: Promise<string>) {
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;
});
}
}
}
}
const initialResource = createResource(fetchData(1));
export default function () {
const [resource, setResource] = useState(initialResource);
const [isPending, startTransition] = useTransition();
return (
<>
<ErrorBoundary fallback={<h1>出错了</h1>}>
<Suspense fallback={<h1>加载中...</h1>}>
<User resource={resource} />
</Suspense>
</ErrorBoundary>
{isPending ? " 加载中..." : null}
<button
disabled={isPending}
onClick={() => {
//startTransition(() => {
setResource(createResource(fetchData(2)));
//});
}}
>下一个用户</button>
</>
)
}