├── .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 |
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 |
30 | {count === 2
31 | ? [2, 1, 3, 4].map((item) => {
32 | return - {item}
;
33 | })
34 | : [0, 1, 2, 3, 4].map((item) => {
35 | return - {item}
;
36 | })}
37 |
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 |
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 |
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 | //
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 |
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 |
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 |
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