├── .yarnrc.yml ├── .DS_Store ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── .DS_Store ├── pages │ ├── .DS_Store │ ├── ServerComponentWithoutServer.jsx │ ├── ErrorBoundaryPage.js │ ├── CloneElementPage.js │ ├── PureComponentPage.jsx │ ├── ActionPage.jsx │ ├── AboutThisPage.js │ ├── DiffPage.jsx │ ├── UseActionStatePage.jsx │ ├── FunctionComponentForceUpdate.js │ ├── UseMemoPage.jsx │ ├── SuspensePage.js │ ├── TransitionPage2.js │ ├── ReactMemoPage.jsx │ ├── RefPage19.jsx │ ├── TransitionPage.js │ ├── NewHookApi.js │ ├── UseOptimisticPage2.jsx │ ├── CommentListPage.jsx │ ├── UseCallbackPage.jsx │ ├── UseDeferredValuePage.js │ ├── UseFormStatusPage.jsx │ ├── OptimizingPage.js │ ├── ExamplePage.jsx │ ├── UsePage.jsx │ ├── ContextPage2.jsx │ ├── UseOptimisticPage copy.jsx │ ├── SetStatePage.jsx │ ├── FunctionComponent.jsx │ ├── UseOptimisticPage.jsx │ ├── ClassFunctionComponent.jsx │ ├── ContextPage.jsx │ ├── LifeCyclePage.jsx │ └── RefPage.jsx ├── components │ ├── .DS_Store │ ├── Num.js │ ├── User.js │ ├── Button.js │ └── MySlowList.js ├── store │ ├── index.js │ └── createStore.js ├── RootRouter.jsx ├── index.css ├── index.js ├── whichReact.js └── utils.js ├── config ├── .DS_Store ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── pnpTs.js ├── getHttpsConfig.js ├── paths.js ├── modules.js ├── env.js ├── webpackDevServer.config.js └── webpack.config.js ├── .gitignore ├── scripts ├── test.js ├── start.js └── build.js ├── DebugReact.code-workspace ├── package.json ├── 那些我回答了800遍的问题.md └── README.md /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/.DS_Store -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /config/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/config/.DS_Store -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/pages/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/src/pages/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | react-/ 3 | # react/ 4 | 5 | .yarn/* 6 | 7 | src/-react* 8 | 9 | *.zip -------------------------------------------------------------------------------- /src/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bubucuo/DebugReact/HEAD/src/components/.DS_Store -------------------------------------------------------------------------------- /src/components/Num.js: -------------------------------------------------------------------------------- 1 | export default function Num({ resource }) { 2 | const num = resource.num.read(); 3 | return ( 4 |
5 |

Num

6 |

{num}

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/User.js: -------------------------------------------------------------------------------- 1 | export default function User({ resource }) { 2 | const user = resource.user.read(); 3 | return ( 4 |
5 |

User

6 |

网络请求:{user.name.first}

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import createStore from "./createStore"; 2 | 3 | const store = createStore(countReducer); 4 | 5 | export default store; 6 | 7 | function countReducer(action, state = 0) { 8 | switch (action.type) { 9 | case "ADD": 10 | return state + 1; 11 | case "MINUS": 12 | return state - 1; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/pages/ServerComponentWithoutServer.jsx: -------------------------------------------------------------------------------- 1 | export default function ServerComponentWithoutServer({ page }) { 2 | // const content = await fetch("http://localhost:3000/api/content"); 3 | // console.log( 4 | // "%c [ content ]-6", 5 | // "font-size:13px; background:pink; color:#bf2c9f;", 6 | // content 7 | // ); 8 | 9 | return ( 10 |
11 |

ServerComponentWithoutServer

12 | {/*

{sanitizeHtml(marked(content))}

*/} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/ErrorBoundaryPage.js: -------------------------------------------------------------------------------- 1 | import { Component } from "../whichReact"; 2 | 3 | export default class ErrorBoundaryPage extends Component { 4 | state = { hasError: false, error: null }; 5 | // 此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state 6 | static getDerivedStateFromError(error) { 7 | return { 8 | hasError: true, 9 | error, 10 | }; 11 | } 12 | render() { 13 | if (this.state.hasError) { 14 | return this.props.fallback; 15 | } 16 | return this.props.children; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Button.js: -------------------------------------------------------------------------------- 1 | import { 2 | // startTransition 3 | useTransition, 4 | } from "../whichReact"; 5 | 6 | export default function Button({ refresh }) { 7 | const [isPending, startTransition] = useTransition(); 8 | return ( 9 |
10 |

Button

11 | 19 |

{isPending ? "loading" : ""}

20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/CloneElementPage.js: -------------------------------------------------------------------------------- 1 | import {React, useState, Component} from "../CONST"; 2 | 3 | const ProcessInput = ({value, onChange}) => 4 | React.cloneElement(, { 5 | placeholder: "omg", 6 | value, 7 | onChange: e => { 8 | onChange(e.target.value); 9 | } 10 | }); 11 | 12 | export default function CloneElementPage(props) { 13 | const [value, onChange] = useState(""); 14 | return ( 15 |
16 |

CloneElementPage

17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | // reducer是状态修改规则 2 | export default function createStore(reducer) { 3 | let currentState; 4 | let listeners = []; 5 | 6 | function getState() { 7 | return currentState; 8 | } 9 | 10 | function dispatch(action) { 11 | currentState = reducer(action, currentState); 12 | 13 | listeners.forEach((listener) => listener()); 14 | } 15 | 16 | function subscribe(listener) { 17 | listeners.push(listener); 18 | } 19 | 20 | dispatch({ type: "ADDDDDDDDDDDDDDDDD" }); 21 | 22 | return { 23 | getState, 24 | dispatch, 25 | subscribe, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/PureComponentPage.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import {Component, PureComponent} from "react"; 3 | 4 | export default class PureComponentPage extends PureComponent { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | counter: 0 9 | }; 10 | } 11 | 12 | setCounter = () => { 13 | this.setState({ 14 | counter: 100 15 | // obj: {num: 0} 16 | }); 17 | }; 18 | 19 | render() { 20 | const {counter, obj} = this.state; 21 | console.log("render"); 22 | return ( 23 |
24 |

PuerComponentPage

25 | 26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/MySlowList.js: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | 3 | function ListItem({ children }) { 4 | let now = performance.now(); 5 | while (performance.now() - now < 3) {} 6 | return
{children}
; 7 | } 8 | 9 | export default memo(function MySlowList({ text }) { 10 | let items = []; 11 | for (let i = 0; i < 80; i++) { 12 | items.push( 13 | 14 |       Result #{i} for "{text}"       15 | 16 | ); 17 | } 18 | return ( 19 |
20 |       21 |

22 |        Results for "{text}":      23 |

24 |          25 |
26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/pages/ActionPage.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useTransition } from "react"; 2 | import { fetchUser } from "../utils"; 3 | // import { startTransition } from "react"; 4 | 5 | export default function ActionPage() { 6 | const [user, setUser] = useState(""); 7 | const [isPending, startTransition] = useTransition(); 8 | 9 | const handleSubmit = () => { 10 | startTransition(async () => { 11 | const user = await fetchUser(); 12 | setUser(user.name.first); 13 | }); 14 | }; 15 | 16 | return ( 17 |
18 |

ActionPage

19 |

userName: {user}

20 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/pages/AboutThisPage.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | 3 | export default class AboutThisPage extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { count: 0 }; 7 | // this.handle = this.handle.bind(this); 8 | } 9 | 10 | handleOfArrow = (arg) => { 11 | console.log("箭头函数 this", this); //sy-log 12 | this.setState({ count: this.state.count + 1 }); 13 | }; 14 | 15 | handle() { 16 | console.log("标准函数 this", this); //sy-log 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 |

AboutThisPage

23 |

{this.state.count}

24 | 25 | 26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/RootRouter.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Link, Routes, Route } from "react-router"; 2 | import jsx from "./pages/ExamplePage"; 3 | import ActionPage from "./pages/ActionPage"; 4 | import UseOptimisticPage from "./pages/UseOptimisticPage"; 5 | 6 | export default function RootRouter(props) { 7 | return ( 8 | 9 | 10 | 11 | } /> 12 | } /> 13 | 14 | 15 | ); 16 | } 17 | 18 | function Nav(props) { 19 | return ( 20 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/DiffPage.jsx: -------------------------------------------------------------------------------- 1 | import { React, ReactDOM, useState } from "../whichReact"; 2 | 3 | // old 1 3 2 5 4 | // new 0 1 2 3 4 5 | 6 | // 新增 0 4 7 | // 复用 1 2 3 8 | // 移动 3 9 | 10 | function A() { 11 | return
a
; 12 | } 13 | 14 | function b() { 15 | return "b"; 16 | } 17 | export default function DiffPage() { 18 | const [count, setCount] = useState(0); 19 | 20 | return ( 21 |
22 | 29 | 38 | 39 | 40 | {b()} 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/UseActionStatePage.jsx: -------------------------------------------------------------------------------- 1 | import { useActionState } from "react"; 2 | import { fetchUser2 } from "../utils"; 3 | 4 | export default function UseActionStatePage() { 5 | const [user, submitAction, isPending] = useActionState( 6 | async (previousState, formData) => { 7 | console.log(formData?.get("name")); //sy-log 8 | const user = await fetchUser2(formData?.get("name")); 9 | return user; 10 | }, 11 | "initial value" // state 12 | ); 13 | 14 | // ? 在点击update 1次之后,猜猜这里log几次 15 | console.log("user", user); //sy-log 16 | 17 | return ( 18 |
19 |

UseActionStatePage

20 |
21 | 22 | 25 |
26 | 27 |

userName: {user}

28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | #root { 11 | margin: 8px; 12 | } 13 | 14 | .app { 15 | margin: 8px; 16 | /* color: red; */ 17 | } 18 | 19 | .red { 20 | color: red; 21 | } 22 | 23 | .pink { 24 | color: pink; 25 | } 26 | 27 | .orange { 28 | color: orange; 29 | } 30 | 31 | .border { 32 | margin: 10px; 33 | padding: 10px; 34 | border: solid 1px red; 35 | } 36 | 37 | .dialog { 38 | position: absolute; 39 | top: 0; 40 | right: 0; 41 | bottom: 0; 42 | left: 0; 43 | margin: auto; 44 | border: solid 1px black; 45 | width: 400px; 46 | height: 300px; 47 | text-align: center; 48 | line-height: 100px; 49 | } 50 | 51 | .box { 52 | margin: 20px; 53 | } 54 | -------------------------------------------------------------------------------- /src/pages/FunctionComponentForceUpdate.js: -------------------------------------------------------------------------------- 1 | import {useCallback, useReducer, useState} from "react"; 2 | 3 | export default function FunctionComponentForceUpdate(props) { 4 | console.log("omg"); //sy-log 5 | 6 | // const [count, forceUpdate] = useState(0); 7 | // ! 方法1 8 | // const [, forceUpdate] = useReducer((x) => x + 1, 0); 9 | 10 | // ! 方法2 11 | const forceUpdate = useForceUpdate(); 12 | 13 | const handleClick = () => { 14 | // forceUpdate(count + 1); 15 | // forceUpdate((prev) => prev + 1); 16 | forceUpdate(); 17 | }; 18 | 19 | return ( 20 |
21 |

FunctionComponentForceUpdate

22 | 23 |
24 | ); 25 | } 26 | 27 | function useForceUpdate() { 28 | const [state, setState] = useState(0); 29 | // const [, setState] = useReducer((x) => x + 1, 0); 30 | 31 | const update = useCallback(() => { 32 | setState((prev) => prev + 1); 33 | // setState(); 34 | }, []); 35 | 36 | return update; 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/UseMemoPage.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useState, useMemo } from "react"; 3 | 4 | export default function UseMemoPage(props) { 5 | const [count, setCount] = useState(0); 6 | const [value, setValue] = useState(""); 7 | 8 | const expensive = useMemo(() => { 9 | console.log("compute"); 10 | let sum = 0; 11 | for (let i = 0; i < count; i++) { 12 | sum += i; 13 | } 14 | return sum; 15 | //只有count变化,这里才重新执行 16 | }, [count]); 17 | 18 | // const expensive = () => { 19 | // console.log("compute"); 20 | // let sum = 0; 21 | // for (let i = 0; i < count; i++) { 22 | // sum += i; 23 | // } 24 | // return sum; 25 | // //只有count变化,这里才重新执行 26 | // }; 27 | 28 | return ( 29 |
30 |

UseMemoPage

31 |

expensive:{expensive}

32 |

{count}

33 | 34 | setValue(event.target.value)} /> 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/SuspensePage.js: -------------------------------------------------------------------------------- 1 | import Num from "../components/Num"; 2 | import User from "../components/User"; 3 | import {fetchData} from "../utils"; 4 | import {Suspense, SuspenseList, useState} from "../whichReact"; 5 | import ErrorBoundaryPage from "./ErrorBoundaryPage"; 6 | 7 | const initialResource = fetchData(); 8 | 9 | export default function SuspensePage(props) { 10 | const [resource, setresource] = useState(initialResource); 11 | return ( 12 |
13 |

SuspensePage

14 | 15 | 出错了}> 16 | 17 | Loading user..}> 18 | 19 | 20 | 21 | Loading num..}> 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/TransitionPage2.js: -------------------------------------------------------------------------------- 1 | import User from "../components/User"; 2 | import Num from "../components/Num"; 3 | import { fetchData } from "../utils"; 4 | import { 5 | Suspense, 6 | useState, 7 | useTransition, 8 | // startTransition 9 | } from "react"; 10 | 11 | const initialResource = fetchData(); 12 | 13 | export default function TransitionPage(props) { 14 | const [resource, setResource] = useState(initialResource); 15 | const [isPending, startTransition] = useTransition(); 16 | 17 | const refresh = () => { 18 | startTransition(() => { 19 | setResource(fetchData()); 20 | }); 21 | }; 22 | return ( 23 |
24 |

TransitionPage

25 | Loading user..}> 26 | 27 | 28 | Loading num..}> 29 | 30 | 31 | 32 | 33 | {/* */} 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import "./index.css"; 4 | 5 | // basic 6 | import jsx from "./pages/ExamplePage"; 7 | 8 | // action | form 9 | import ActionPage from "./pages/ActionPage"; 10 | import UseActionStatePage from "./pages/UseActionStatePage"; 11 | import UseFormStatusPage from "./pages/UseFormStatusPage"; 12 | 13 | // 乐观 14 | import UseOptimisticPage from "./pages/UseOptimisticPage"; 15 | import UseOptimisticPage2 from "./pages/UseOptimisticPage2"; 16 | 17 | // use 18 | import UsePage from "./pages/UsePage"; 19 | 20 | // improvements of 19 21 | import RefPage19 from "./pages/RefPage19"; 22 | // other 23 | import UseDeferredValuePage from "./pages/UseDeferredValuePage"; 24 | 25 | const root = createRoot(document.getElementById("root")); 26 | 27 | // let MyComponent = () => Promise.resolve(); 28 | 29 | // MyComponent = () => false; 30 | // root.render(); 31 | root.render(jsx); 32 | 33 | console.log( 34 | "%c React Version " + React.version, 35 | "font-size:24px; background:green; color:yellow;" 36 | ); 37 | -------------------------------------------------------------------------------- /src/pages/ReactMemoPage.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import {Component, memo} from "react"; 3 | 4 | export default class ReactMemoPage extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | date: new Date(), 9 | count: 0 10 | }; 11 | } 12 | render() { 13 | const {count, date} = this.state; 14 | console.log("render", count); 15 | return ( 16 |
17 |

ReactMemoPage

18 | 21 | 24 | 25 |
26 | ); 27 | } 28 | } 29 | 30 | const MemoCounter = memo( 31 | props => { 32 | console.log("MemoCounter"); 33 | return
MemoCounter-{props.count}
; 34 | } 35 | // , 36 | // (prevProps, nextProps) => { 37 | // return prevProps.count.count === nextProps.count.count; 38 | // } 39 | ); 40 | -------------------------------------------------------------------------------- /src/whichReact.js: -------------------------------------------------------------------------------- 1 | // ! React 18 2 | import * as React from "react"; 3 | import { 4 | useState, 5 | useReducer, 6 | useEffect, 7 | useLayoutEffect, 8 | useContext, 9 | useImperativeHandle, 10 | useTransition, 11 | useId, 12 | useSyncExternalStore, 13 | useInsertionEffect, 14 | useRef, 15 | Component, 16 | PureComponent, 17 | Suspense, 18 | // SuspenseList, 19 | startTransition, 20 | } from "react"; 21 | 22 | import * as ReactDOM from "react-dom"; 23 | import { flushSync } from "react-dom"; 24 | import { createRoot } from "react-dom/client"; 25 | 26 | export { 27 | React, 28 | ReactDOM, 29 | useState, 30 | useReducer, 31 | useEffect, 32 | useLayoutEffect, 33 | useContext, 34 | useImperativeHandle, 35 | Component, 36 | PureComponent, 37 | // ReactDOM, 38 | createRoot, 39 | flushSync, 40 | Suspense, 41 | // SuspenseList, 42 | useId, 43 | useSyncExternalStore, 44 | useInsertionEffect, 45 | useRef, 46 | startTransition, 47 | useTransition, 48 | }; 49 | 50 | //**********************************分割线********************************** 51 | // mini react 52 | //**********************************分割线********************************** 53 | -------------------------------------------------------------------------------- /src/pages/RefPage19.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export default function RefPage19(props) { 4 | const [count, setCount] = React.useState(0); 5 | const inputRef = React.useRef(null); 6 | return ( 7 |
8 |

RefPage19

9 | 10 | {!!(count % 2) && ( 11 | <> 12 | { 14 | inputRef.current = ref; // ref created 15 | // ref created 16 | // NEW: return a cleanup function to reset 17 | // the ref when element is removed from DOM. 18 | return () => { 19 | console.log("ref cleanup"); //sy-log 20 | }; 21 | }} 22 | /> 23 | 24 | 25 | )} 26 |
27 | ); 28 | } 29 | 30 | function Child({ ref, count }) { 31 | return ( 32 |
33 |

count: {count}

34 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/TransitionPage.js: -------------------------------------------------------------------------------- 1 | import Button from "../components/Button"; 2 | import Num from "../components/Num"; 3 | import User from "../components/User"; 4 | import { fetchData } from "../utils"; 5 | import { 6 | Suspense, 7 | // SuspenseList, 8 | useState, 9 | } from "../whichReact"; 10 | import ErrorBoundaryPage from "./ErrorBoundaryPage"; 11 | 12 | const initialResource = fetchData(); 13 | 14 | export default function TransitionPage(props) { 15 | const [resource, setresource] = useState(initialResource); 16 | return ( 17 |
18 |

TransitionPage

19 | 20 | {/* 出错了}> */} 21 | {/* */} 22 | Loading user..}> 23 | 24 | 25 | 26 | Loading num..}> 27 | 28 | 29 | {/* */} 30 | {/* */} 31 | 32 | {/* */} 33 | 34 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/NewHookApi.js: -------------------------------------------------------------------------------- 1 | import { 2 | useId, 3 | useSyncExternalStore, 4 | useInsertionEffect, 5 | useState, 6 | useEffect, 7 | useLayoutEffect, 8 | useReducer, 9 | } from "../whichReact"; 10 | 11 | import store from "../store/"; 12 | 13 | export default function NewHookApi({ storeProps }) { 14 | const id = useId(); 15 | 16 | // const state = store.getState(); 17 | 18 | // const [, forceUpdate] = useReducer((x) => x + 1, 0); 19 | 20 | // useEffect(() => { 21 | // store.subscribe(() => { 22 | // forceUpdate(); 23 | // }); 24 | // }, []); 25 | 26 | const state = useSyncExternalStore(store.subscribe, store.getState); 27 | 28 | useInsertionEffect(() => { 29 | // debugger; 30 | }, []); 31 | useLayoutEffect(() => { 32 | // debugger; 33 | }, []); 34 | useEffect(() => { 35 | // debugger; 36 | }, []); 37 | 38 | return ( 39 |
40 |

NewHookApi

41 | 42 | 49 | {/* */} 50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/UseOptimisticPage2.jsx: -------------------------------------------------------------------------------- 1 | import { useActionState, useOptimistic } from "react"; 2 | import { fetchUser2 } from "../utils"; 3 | 4 | export default function UseOptimisticPage2() { 5 | const [user, submitAction, isPending] = useActionState( 6 | async (previousState, formData) => { 7 | const name = formData?.get("name"); 8 | const user = await fetchUser2(name); 9 | return user; 10 | }, 11 | "initial value" 12 | ); 13 | 14 | const [optimisticUser, setOptimisticUser] = useOptimistic( 15 | user, 16 | (currentState, optimisticValue) => { 17 | return optimisticValue; 18 | } 19 | ); 20 | 21 | const handleSubmit = (formaData) => { 22 | const name = formaData.get("name"); 23 | setOptimisticUser(name); 24 | submitAction(formaData); 25 | }; 26 | 27 | return ( 28 |
29 |

UseOptimisticPage2

30 |
31 | 32 | 35 |
36 | 37 |

user (final): {user}

38 |

optimisticUser: {optimisticUser}

39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/pages/CommentListPage.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import {Component} from "react"; 3 | 4 | export default class CommentListPage extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | commentList: [] 9 | }; 10 | } 11 | componentDidMount() { 12 | this.timer = setInterval(() => { 13 | this.setState({ 14 | commentList: [ 15 | { 16 | id: 0, 17 | author: "小明", 18 | body: "这是小明写的文章" 19 | }, 20 | { 21 | id: 1, 22 | author: "小红", 23 | body: "这是小红写的文章" 24 | } 25 | ] 26 | }); 27 | }, 1000); 28 | } 29 | render() { 30 | const {commentList} = this.state; 31 | return ( 32 |
33 |

CommentListPage

34 | {commentList.map(item => { 35 | return ; 36 | })} 37 |
38 | ); 39 | } 40 | } 41 | 42 | class Comment extends Component { 43 | shouldComponentUpdate(nextProps, nextState) { 44 | // true false 45 | const {author, body} = this.props.data; 46 | const {author: newAuthor, body: newBody} = nextProps.data; 47 | if (newAuthor === author && body === newBody) { 48 | return false; 49 | } 50 | return true; 51 | } 52 | render() { 53 | const {author, body} = this.props.data; 54 | console.log("render"); //sy-log 55 | return ( 56 |
57 |

{author}

58 |

{body}

59 |
60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/UseCallbackPage.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, memo, useEffect } from "react"; 2 | 3 | export default function UseCallbackPage(props) { 4 | const [count, setCount] = useState(0); 5 | const addClick = useCallback(() => { 6 | let sum = 0; 7 | for (let i = 0; i < count; i++) { 8 | sum += i; 9 | } 10 | return sum; 11 | }, [count]); 12 | 13 | // const addClick = () => { 14 | // let sum = 0; 15 | // for (let i = 0; i < count; i++) { 16 | // sum += i; 17 | // } 18 | // return sum; 19 | // }; 20 | const [value, setValue] = useState(""); 21 | return ( 22 |
23 |

UseCallbackPage

24 |

{count}

25 | 26 | setValue(event.target.value)} /> 27 | 28 |
29 | ); 30 | } 31 | 32 | const ChildMemo = memo(function Child({ addClick }) { 33 | useEffect(() => { 34 | return () => { 35 | console.log("destroy"); //sy-log 36 | }; 37 | }, []); 38 | console.log("Child"); //sy-log 39 | return ( 40 |
41 | 42 |
43 | ); 44 | }); 45 | 46 | // class Child extends PureComponent { 47 | // render() { 48 | // console.log("child render"); 49 | // const { addClick } = this.props; 50 | // return ( 51 | //
52 | //

Child

53 | // 54 | //
55 | // ); 56 | // } 57 | // } 58 | -------------------------------------------------------------------------------- /src/pages/UseDeferredValuePage.js: -------------------------------------------------------------------------------- 1 | import { useDeferredValue, useState, memo } from "react"; 2 | import MySlowList from "../components/MySlowList"; 3 | 4 | export default function UseDeferredValuePage(props) { 5 | const [count, setCount] = useState(0); 6 | const [text, setText] = useState("hello"); 7 | const deferredText = useDeferredValue(text, "loading..."); // 第二个initialValue为可选,如果不设置,初次渲染就不会延迟 8 | 9 | const handleChange = (e) => { 10 | setText(e.target.value); 11 | }; 12 | return ( 13 |
14 | 15 |

UseDeferredValuePage

16 | {/* 保持将当前文本传递给 input */} 17 | {/* 紧急更新 */} 18 | 19 |

{text}

20 | {/* 但在必要时可以将列表“延后” */} 21 |

deferredText: {deferredText}

22 | 23 | {/* 非紧急更新 */} 24 | 25 |
26 | ); 27 | } 28 | 29 | // function ListItem({ children }) { 30 | // let now = performance.now(); 31 | // while (performance.now() - now < 3) {} 32 | // return
{children}
; 33 | // } 34 | 35 | // const MySlowList = memo(function ({ text }) { 36 | // let items = []; 37 | // for (let i = 0; i < 80; i++) { 38 | // items.push( 39 | // 40 | // Result #{i} for "{text}" 41 | // 42 | // ); 43 | // } 44 | // return ( 45 | //
46 | //

47 | // Results for "{text}": 48 | //

49 | //
    {items}
50 | //
51 | // ); 52 | // }); 53 | -------------------------------------------------------------------------------- /src/pages/UseFormStatusPage.jsx: -------------------------------------------------------------------------------- 1 | import { useActionState } from "react"; 2 | import { useFormStatus } from "react-dom"; 3 | import { updateSomething } from "../utils"; 4 | 5 | export default function UseFormStatusPage() { 6 | const [user, submitAction, isPending] = useActionState( 7 | async (previousState, formData) => { 8 | const res = await updateSomething({ 9 | user: formData.get("name"), 10 | userId: 1, 11 | }); 12 | return res.user; 13 | }, 14 | null 15 | ); 16 | 17 | return ( 18 |
19 |

UseFormStatusPage

20 |
21 | 22 | 25 | 26 | 27 | 28 |

userName: {user}

29 | {/* 注意下面这个 DesignButton 不能通过 useFormStatus 获取pending,因为它不在form内 */} 30 | {/* {isPending ? "2Updating.." : "2Update"} */} 31 |
32 | ); 33 | } 34 | 35 | function DesignButton() { 36 | const status = useFormStatus(); 37 | console.log( 38 | "%c [ pending ]-41", 39 | "font-size:13px; background:pink; color:#bf2c9f;", 40 | status, 41 | status.data?.get("name") 42 | ); 43 | return ( 44 | <> 45 |

分割线

46 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /DebugReact.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".", 5 | }, 6 | ], 7 | "settings": { 8 | "better-comments.tags": [ 9 | { 10 | "tag": "sy-", 11 | "color": "yellow", 12 | "strikethrough": false, 13 | "underline": false, 14 | "backgroundColor": "#000", 15 | "bold": false, 16 | "italic": false, 17 | }, 18 | { 19 | "tag": "!", 20 | "color": "#cf40c2", 21 | "strikethrough": false, 22 | "underline": false, 23 | "backgroundColor": "transparent", 24 | "bold": false, 25 | "italic": false, 26 | }, 27 | { 28 | "tag": "?", 29 | "color": "#cf6640", 30 | "strikethrough": false, 31 | "underline": false, 32 | "backgroundColor": "transparent", 33 | "bold": false, 34 | "italic": false, 35 | }, 36 | { 37 | "tag": "//", 38 | "color": "#474747", 39 | "strikethrough": true, 40 | "underline": false, 41 | "backgroundColor": "transparent", 42 | "bold": false, 43 | "italic": false, 44 | }, 45 | { 46 | "tag": "todo", 47 | "color": "#FF8C00", 48 | "strikethrough": false, 49 | "underline": false, 50 | "backgroundColor": "transparent", 51 | "bold": false, 52 | "italic": false, 53 | }, 54 | { 55 | "tag": "*", 56 | "color": "green", //"#9c27b0", 57 | "strikethrough": false, 58 | "underline": false, 59 | "backgroundColor": "transparent", 60 | "bold": false, 61 | "italic": false, 62 | }, 63 | ], 64 | "editor.cursorWidth": 2, 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /src/pages/OptimizingPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | PureComponent, 4 | useEffect, 5 | useState, 6 | memo, 7 | useMemo, 8 | } from "react"; 9 | 10 | export default function OptimizingPage(props) { 11 | const [arr, setArr] = useState([0, 1, 2, 3]); 12 | 13 | return ( 14 |
15 |

OptimizingPage

16 | 23 | 24 | {arr.map((item, index) => { 25 | return ; 26 | })} 27 |
28 | ); 29 | } 30 | 31 | function Child({ item }) { 32 | useEffect(() => { 33 | return () => { 34 | console.log("destroy"); //sy-log 35 | }; 36 | }, []); 37 | console.log("Child", item); //sy-log 38 | return
{item}
; 39 | } 40 | 41 | const ChildMemo = memo(Child, (prev, next) => { 42 | return prev.item === next.item; 43 | }); 44 | 45 | class ChildShouldComponentUpdate extends Component { 46 | shouldComponentUpdate(nextProps) { 47 | return this.props.item !== nextProps.item; 48 | } 49 | render() { 50 | console.log("ChildComponent", this.props.item); //sy-log 51 | return ( 52 |
53 |

{this.props.item}

54 |
55 | ); 56 | } 57 | } 58 | 59 | class ChildPureComponent extends PureComponent { 60 | render() { 61 | console.log("ChildPureComponent"); //sy-log 62 | return ( 63 |
64 |

{this.props.item}

65 |
66 | ); 67 | } 68 | } 69 | 70 | function ChildUseMemo({ item }) { 71 | return useMemo(() => , []); 72 | } 73 | -------------------------------------------------------------------------------- /src/pages/ExamplePage.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | Component, 4 | // useState, 5 | useReducer, 6 | useEffect, 7 | // useLayoutEffect, 8 | } from "react"; 9 | 10 | class ClassComponent extends Component { 11 | state = { count: 0 }; 12 | render() { 13 | return ( 14 |
15 | {this.props.name} 16 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | function FunctionComponent(props) { 30 | const [count1, setCount1] = useReducer((x) => x + 1, 0); 31 | 32 | // passive effect 异步执行 33 | useEffect(() => { 34 | // setCount1(); 35 | // return () => { 36 | // console.log("销毁"); 37 | // }; 38 | }, [count1]); 39 | 40 | return ( 41 |
42 |

{props.name}

43 | 50 |
51 | ); 52 | } 53 | 54 | const A = () => [1, 2, 3]; 55 | 56 | const jsx = ( 57 |
58 | 59 | {/*

omg

*/} 60 | {/* 123 */} 61 | 62 | <> 63 |

1

64 |

2

65 | 66 |
67 | ); 68 | 69 | export default jsx; 70 | 71 | // document.createDocumentFragment 72 | 73 | // ! 原生节点 有对应的dom节点 74 | // 1. 原生标签节点 div\span\a等 HostComponent 75 | // 2. 文本节点 76 | 77 | // ! 非原生节点 没有对应的dom节点 78 | // 函数组件、类组件、Provider、Consumer、Fragment等 79 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Debug React19 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/pages/UsePage.jsx: -------------------------------------------------------------------------------- 1 | import { useState, use, Suspense, createContext, useContext } from "react"; 2 | import * as React from "react"; 3 | import { ErrorBoundary } from "react-error-boundary"; 4 | 5 | const Context = createContext(); 6 | 7 | function fetchMessage() { 8 | // return new Promise((resolve, reject) => setTimeout(resolve("Hello"), 1000)); 9 | // return new Promise((resolve, reject) => setTimeout(reject, 1000)); 10 | return fetch("https://randomuser.me/api").then((x) => x.json()); 11 | } 12 | 13 | export default function UsePage() { 14 | const [num, setNum] = useState(0); 15 | const [messagePromise, setMessagePromise] = useState(null); 16 | const [show, setShow] = useState(false); 17 | function download() { 18 | setMessagePromise(fetchMessage()); 19 | setShow(true); 20 | } 21 | 22 | if (show) { 23 | return ( 24 | 25 | 26 | 27 | 28 | ); 29 | } else { 30 | return ; 31 | } 32 | } 33 | 34 | function MessageContainer({ messagePromise }) { 35 | return ( 36 | ⚠️Something went wrong

}> 37 | ⌛Downloading message...

}> 38 | 39 |
40 |
41 | ); 42 | } 43 | 44 | // use的父组件不要写太复杂 45 | function Message({ messagePromise }) { 46 | const content = use(messagePromise); 47 | const ctx = 1 < 2 && use(Context); 48 | const ctx2 = useContext(Context); 49 | console.log( 50 | "%c [ ctx ]-48", 51 | "font-size:13px; background:pink; color:#bf2c9f;", 52 | ctx, 53 | ctx2 54 | ); 55 | 56 | const user = content.results[0].name.first; 57 | return

Here is the message: {user}

; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | export async function updateName(prevState, formData) { 4 | const name = formData.get("name")?.toString() || ""; 5 | return { success: true, name }; 6 | } 7 | 8 | // fakeAPI 9 | export function fetchData() { 10 | return { 11 | user: wrapPromise(fetchUser()), 12 | num: wrapPromise(fetchNum()), 13 | }; 14 | } 15 | 16 | function wrapPromise(promise) { 17 | let status = "pending"; 18 | let result; 19 | let suspender = promise.then( 20 | (r) => { 21 | status = "success"; 22 | result = r; 23 | }, 24 | (e) => { 25 | status = "error"; 26 | result = e; 27 | } 28 | ); 29 | return { 30 | read() { 31 | if (status === "pending") { 32 | throw suspender; 33 | } else if (status === "error") { 34 | throw result; 35 | } else if (status === "success") { 36 | return result; 37 | } 38 | }, 39 | }; 40 | } 41 | 42 | export function fetchUser() { 43 | return fetch("https://randomuser.me/api") 44 | .then((x) => x.json()) 45 | .then((x) => x.results[0]); 46 | } 47 | 48 | export const fetchUser2 = async (txt = Math.random().toString()) => { 49 | return new Promise((resolve) => { 50 | setTimeout(() => { 51 | resolve(txt); 52 | }, 1000); 53 | }); 54 | }; 55 | 56 | function fetchNum() { 57 | return new Promise((resolve) => { 58 | setTimeout(() => { 59 | resolve(Math.random()); 60 | }, 3000); 61 | }); 62 | } 63 | 64 | export function updateSomething(option) { 65 | return fetch("https://jsonplaceholder.typicode.com/posts", { 66 | method: "POST", 67 | body: JSON.stringify(option), 68 | headers: { 69 | "Content-type": "application/json; charset=UTF-8", 70 | }, 71 | }) 72 | .then((response) => response.json()) 73 | .catch((error) => { 74 | return { error: { ...error, msg: "error" || error?.msg } }; 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/ContextPage2.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | useReducer, 4 | useState, 5 | createContext, 6 | useContext, 7 | } from "react"; 8 | 9 | const CountContext = createContext(-999); 10 | const MyBGContext = createContext("red"); 11 | 12 | export default function ContextPage2() { 13 | const [count, setCount] = useReducer((x) => x + 1, 0); 14 | const [bgColor, setBgColor] = useState("red"); 15 | 16 | return ( 17 |
18 | 19 | 22 | 23 | {/* 没有匹配的Provider,取值默认值 */} 24 | 25 | 26 | {/* 匹配到多个Provider,取最近的那个 */} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | ); 38 | } 39 | 40 | function Child({ desc }) { 41 | const background = useContext(MyBGContext); 42 | const count = useContext(CountContext); 43 | return ( 44 |
45 |

{desc}

46 |
{count}
47 | {(ctx) =>
{ctx}
}
48 | 49 |
50 | ); 51 | } 52 | 53 | class ClassComponent extends Component { 54 | static contextType = CountContext; 55 | render() { 56 | return ( 57 |
58 |

类组件:{this.context}

59 |
60 | ); 61 | } 62 | } 63 | 64 | function getRandomColor(props) { 65 | return "#" + Math.floor(Math.random() * Math.pow(10, 6)); 66 | } 67 | -------------------------------------------------------------------------------- /src/pages/UseOptimisticPage copy.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useOptimistic } from "react"; 2 | 3 | function getData(num) { 4 | return new Promise((resolve) => { 5 | setTimeout(() => { 6 | if (num === "error" || isNaN(num)) { 7 | resolve("请输入数字"); 8 | } else { 9 | resolve(null); 10 | } 11 | }, 500); 12 | }); 13 | } 14 | 15 | export default function UseOptimisticPage() { 16 | const formRef = useRef(); 17 | const [state, setState] = useState({ count: 0, sending: false, error: "" }); 18 | 19 | async function sendMessage(num) { 20 | const error = await getData(num); 21 | if (error) { 22 | setState((messages) => ({ 23 | error, 24 | count: messages.count, 25 | })); 26 | } else 27 | setState((messages) => ({ 28 | error: "", 29 | count: messages.count + num, 30 | })); 31 | } 32 | 33 | const [optimisticState, addOptimistic] = useOptimistic( 34 | state, 35 | (previousState, newCount) => { 36 | return { 37 | sending: true, 38 | error: "", 39 | count: previousState.count + newCount, 40 | }; 41 | } 42 | ); 43 | 44 | async function submitAction(formData) { 45 | const num = parseInt(formData.get("incrementAmount"), 10); 46 | addOptimistic(num); 47 | // formRef.current.reset(); 48 | await sendMessage(num); 49 | } 50 | 51 | console.log( 52 | "%c [ ]-60", 53 | "font-size:13px; background:pink; color:#bf2c9f;", 54 | optimisticState 55 | ); 56 | 57 | return ( 58 |
59 |

UseOptimisticPage

60 |
61 | Count: {optimisticState.count} 62 | 63 |

{state.error}

64 | 67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /src/pages/SetStatePage.jsx: -------------------------------------------------------------------------------- 1 | import {flushSync, Component, useState, useEffect} from "../whichReact"; 2 | 3 | // React 18以前 4 | // setState在合成事件中,是异步执行(批量执行) 5 | // 但是在setTimeout、或者原生事件中就是同步的 6 | export default class SetStatePage extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | count: 0, 11 | }; 12 | } 13 | componentDidMount() { 14 | document.getElementById("host").addEventListener( 15 | "click", 16 | //// 即react-reconciler/src/ReactFiberWorkLoop.js batchedUpdates 17 | // //() => ReactDOM.unstable_batchedUpdates(this.changeCount), 18 | this.changeCount, 19 | false 20 | ); 21 | } 22 | 23 | changeCount = () => { 24 | const {count} = this.state; 25 | flushSync(() => { 26 | this.setState({ 27 | count: count + 1, 28 | }); 29 | }); 30 | 31 | console.log("改变count", this.state.count); //sy-log 32 | }; 33 | 34 | changeCountWithSetTimeout = () => { 35 | setTimeout(() => { 36 | this.changeCount(); // setState 37 | }, 0); 38 | }; 39 | 40 | render() { 41 | console.log("render"); //sy-log 42 | const {count} = this.state; 43 | return ( 44 |
45 |

类组件SetStatePage

46 |

count: {count}

47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 |
57 | ); 58 | } 59 | } 60 | 61 | function FCSetStatePage(props) { 62 | const [count, setCount] = useState(0); 63 | const handle = () => { 64 | flushSync(() => { 65 | setCount(count + 1); 66 | }); 67 | // console.log("count", count); //sy-log 68 | }; 69 | 70 | useEffect(() => { 71 | console.log( 72 | "%c [ ]-73", 73 | "font-size:13px; background:pink; color:#bf2c9f;", 74 | count 75 | ); 76 | }, [count]); 77 | 78 | return ( 79 |
80 |

函数组件SetStatePage

81 | 82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/pages/FunctionComponent.jsx: -------------------------------------------------------------------------------- 1 | import { useReducer, useState, useEffect, useLayoutEffect } from "react"; 2 | import * as React from "react"; 3 | 4 | export default function FunctionComponent() { 5 | const [count1, setCount1] = useReducer((x) => x + 1, 1); 6 | const [count2, setCount2] = useState(1); 7 | const [txt, setTxt] = useState(""); 8 | 9 | // useLayoutEffect(() => { 10 | // console.log("useLayoutEffect"); //sy-log 11 | // return () => { 12 | // console.log("useLayoutEffect: before update or before unmount"); //sy-log 13 | // }; 14 | // }, [count1]); 15 | 16 | // useEffect(() => { 17 | // console.log("useEffect"); //sy-log 18 | // return () => { 19 | // console.log("useEffect: before update or before unmount"); //sy-log 20 | // }; 21 | // }, [count2]); 22 | return ( 23 |
24 |

函数组件

25 | 32 | 39 | 40 | {count1 % 3 !== 0 ? : null} 41 | 42 | { 46 | console.log( 47 | "%c [ ]-31", 48 | "font-size:13px; background:pink; color:#bf2c9f;", 49 | e.target.value 50 | ); 51 | setTxt(e.target.value); 52 | }} 53 | /> 54 |
55 | ); 56 | } 57 | 58 | function Child({ count1, count2 }: { count1: number, count2: number }) { 59 | useLayoutEffect(() => { 60 | console.log("useLayoutEffect Child"); //sy-log 61 | return () => { 62 | console.log("useLayoutEffect: before update or before unmount"); //sy-log 63 | }; 64 | }, [count1]); 65 | 66 | useEffect(() => { 67 | console.log("useEffect Child"); //sy-log 68 | return () => { 69 | console.log("useEffect: before update or before unmount"); //sy-log 70 | }; 71 | }, [count2]); 72 | 73 | return
Child
; 74 | } 75 | -------------------------------------------------------------------------------- /src/pages/UseOptimisticPage.jsx: -------------------------------------------------------------------------------- 1 | import { useOptimistic, useState } from "react"; 2 | import { updateSomething } from "../utils"; 3 | 4 | // useOptimistic hook, 升级版的useReducer,可以处理乐观更新 5 | // hooks: 单链表 hook1(next) -> hook2 -> hook3 -> hook4 6 | function Thread({ messages, sendMessage }) { 7 | async function formAction(formData) { 8 | addOptimisticMessage(formData.get("message")); 9 | await sendMessage(formData); 10 | } 11 | const [optimisticMessages, addOptimisticMessage] = useOptimistic( 12 | messages, 13 | (state, newMessage) => { 14 | return [ 15 | ...state, 16 | { 17 | text: newMessage, 18 | sending: true, 19 | }, 20 | ]; 21 | } 22 | ); 23 | 24 | console.log( 25 | "%c [ ]-24", 26 | "font-size:13px; background:pink; color:#bf2c9f;", 27 | optimisticMessages 28 | ); 29 | return ( 30 | <> 31 | {/* 正常更新(不考虑乐观) */} 32 | {/* {messages.map((message, index) => ( 33 |
34 | {message.text} 35 | {!!message.sending && (Sending...)} 36 |
37 | ))} */} 38 | {/* 乐观更新 */} 39 | {optimisticMessages.map((message, index) => ( 40 |
41 | {message.text} 42 | {!!message.sending && (Sending...)} 43 |
44 | ))} 45 |
46 | 52 | 53 |
54 | 55 | ); 56 | } 57 | 58 | export default function UseOptimisticPage() { 59 | const [errorMsg, setErrorMsg] = useState(""); 60 | const [messages, setMessages] = useState([ 61 | { text: "Hello there!", sending: false, key: 0 }, 62 | ]); 63 | async function sendMessage(formData) { 64 | const msg = formData.get("message"); 65 | const res = await updateSomething({ msg }); 66 | if (res.error) { 67 | setErrorMsg(msg + "-" + res.error.msg); 68 | } else { 69 | setErrorMsg(""); 70 | setMessages((messages) => [ 71 | ...messages, 72 | { text: res.msg, key: messages.length }, 73 | ]); 74 | } 75 | } 76 | 77 | return ( 78 |
79 |

UseOptimisticPage

80 | 81 |

{errorMsg}

82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right