1. React18介绍 #

2. 并发模式(concurrent mode) #

2.1 更新优先级 #

jia_sai_1625651545473

bing_fa_geng_xin

2.2 双缓冲 #

shuang_huan_cun_hui_tu

3.搭建开发环境 #

3.1 vite #

3.2 安装 #

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

3.3 vite.config.ts #

import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
export default defineConfig({
  plugins: [reactRefresh()]
})

3.4 tsconfig.json #

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"]
}

3.5 package.json #

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"
  }
}

3.6 index.html #

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>

3.7 src\main.tsx #

src\main.tsx

import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.createRoot(
    document.getElementById('root')!
).render(<h1>hello</h1>);

3.8 启动 #

npm run dev

4.批量更新 #

4.1 安装路由 #

4.2 src\main.tsx #

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>
);

4.3 BatchState.tsx #

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>
        );
    }
}

5.Suspense #

5.1 src\main.tsx #

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>
);

5.2 ErrorBoundary.tsx #

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;
  }
}

5.3 Suspense.tsx #

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>
        );
    }
}

5.4 Suspense.tsx #

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;
    }
}

6.SuspenseList #

6.1 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';
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>
);

6.2 SuspenseList.tsx #

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>
        );
    }
}

7.startTransition #

7.1 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';
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>
);

7.2 StartTransition.tsx #

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>
  );
}

8.useDeferredValue #

8.1 src\main.tsx #

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>
);

8.2 UseDeferredValue.tsx #

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>
  );
}

9.useTransition #

9.1 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';
+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>
);

9.2 UseTransition.tsx #

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>
        </>
    )
}