【React基础全篇】

文章目录

  • 一、关于 React
  • 二、脚手架
    • 2.1 create-react-app 脚手架的使用
    • 2.2 项目目录解析
    • 2.3 抽离配置文件
    • 2.4 webpack 二次封装
      • 2.4.1 集成 css 预处理器
      • 2.4.2 配置@解析别名
    • 2.5 setupProxy 代理
  • 三、JSX
    • 3.1 jsx 语法详解
    • 3.2 React.createElement
  • 四、组件定义
    • 4.1 类组件
    • 4.2 函数组件
    • 4.3 两组组件的区别
  • 五、Props
    • 5.1 Props 详解
    • 5.2 父子组件通信
      • 5.2.1 构建一个父子组件
      • 5.2.2 父组件给子组件传值
      • 5.2.3 父组件给子组件传值设置默认值
      • 5.2.4 使用 prop-types 属性验证
    • 5.3 props.children
    • 5.4 render props 特性
  • 六、State
    • 6.1 state 及其特点
    • 6.2 state 的定义和使用
      • 6.2.1 es6 的类 - 构造函数
      • 6.2.2 es7 的类 - 属性初始化器
    • 6.3 如何正确的修改 state
    • 6.4 this.setState()方法及其特点
      • 6.4.1 传递函数
      • 6.4.2 传递对象
  • 七、生命周期
    • 7.1 三个阶段
      • 7.1.1 装载阶段
      • 7.1.2 更新阶段
      • 7.1.3 卸载阶段
      • 7.1.4 Error boundaries
    • 7.2 两个时期
    • 7.3 入门理解 React Fiber 架构
  • 八、事件绑定
    • 8.1 ES5 语法绑定事件
      • 8.1.1 无参数的绑定
        • 8.1.1.1 方法一
        • 8.1.1.1 方法二
      • 8.1.2 有参数的绑定
    • 8.2 ES6 语法绑定事件
      • 8.2.1 无参数绑定
        • 8.2.1.1 方法一
        • 8.2.1.2 方法二
      • 8.2.2 有参数绑定
        • 8.2.2.1 方法一
        • 8.2.2.2 方法二
    • 8.3 合成事件的特点
      • 8.3.1 事件机制
      • 8.3.2 对合成事件的理解
      • 8.3.3 事件机制的流程
        • 1、事件注册
        • 2、事件存储
        • 3、事件执行
      • 8.3.4 合成事件、原生事件之间的冒泡执行关系
  • 九、条件渲染
    • 9.1 &&
    • 9.2 三元运算符
    • 9.3 动态 className
    • 9.4 动态 style
  • 十、列表渲染
  • 十一、表单绑定
    • 11.1 各种表单的绑定与取值
    • 11.2 受控表单以及受控组件
  • 十二、状态提升
    • 12.1 父子组件通信
    • 12.2 状态提升解读
  • 十三、组合 vs 继承
    • 13.1 理解组件化
    • 13.2 使用组合而非继承实现 React 组件化
    • 13.3 封装 Modal 弹窗
    • 13.4 ReactDOM.createPortal()
  • 十四、上下文 Context
    • 14.1 理解上下文、作用及其特点
    • 14.2 使用 React.createContext()
      • 14.2.1 逐层传递数据
      • 14.2.2 使用 Context 传值
      • 14.2.3 传递多个值
      • 14.2.4 displayName
    • 14.3 常见应用场景解读
  • 十五、高阶组件
    • 15.1 理解高阶组件、作用及其特点
    • 15.2 高阶组件语法详解
      • 15.2.1 组件嵌套
      • 15.2.2 高阶组件
    • 15.3 常见应用场景解读
  • 十六、ref
    • 16.1 ref 访问 DOM
    • 16.2 详解 ref 转发
    • 16.3 使用 ref 注意事项
  • 十七、hooks
    • 17.1 为什么使用 hooks
    • 17.2 常见的 hooks
      • 17.2.1 useState
      • 17.2.2 useEffect
      • 17.2.3 useRef
      • 17.2.4 useReducer
      • 17.2.5 useContext
      • 17.2.6 useMemo
      • 17.2.7 useCallback
      • 17.2.8 useImperativeHandle
      • 17.2.9 useLayoutEffect
      • 17.2.10 useDebugValue
      • 17.2.11 useId
      • 17.2.12 useDeferredValue
      • 17.2.13 useTransition
      • 17.2.14 useSyncExternalStore
      • 17.2.15 useInsertionEffect
    • 17.3 自定义 hooks
  • 十八、Redux
    • 18.1 理解 Flux 架构

一、关于 React

英文官网:https://reactjs.org/

中文官网:https://zh-hans.reactjs.org/

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。

react 在发展过程中,一直跟随原生 js 的脚步,特别是从 v16.0 版本开始(用到了 class 来创建组件)

2015 年推出了使用 react 来编写移动端的 app ---- react-native

重要版本发版时间

序号版本号发版时间重要更新
1162017 年 9 月 26引入 es6 的类组件
216.32018 年 4 月 3 日生命周期更新
316.42018 年 5 月 23 日生命周期更新
416.82019 年 2 月 6 日引入 react hooks
517.02020 年 10 月 20 日过渡版本
618.02022 年 3 月 29 日写法改变,严格模式发生改变

二、脚手架

英文官网:https://create-react-app.dev/

中文官网:https://create-react-app.bootcss.com/

补充:react 的脚手架并不是只有 create-react-app,还有dva-cli,umi

2.1 create-react-app 脚手架的使用

Create React App 让你仅通过一行命令,即可构建现代化的 Web 应用。

本文档之后称之为 cra

创建项目的方式:

需要保证电脑安装 node 版本在 14 以上,系统在 win7 以上

# 方式1:使用npx
$ npx create-react-app react-basic
# 方式2:使用npm
$ npm init react-app react-basic
# 方式3:使用yarn
$ yarn create react-app react-basic
yarn的使用
yarn的安装:npm i yarn tyarn -g
安装依赖:
  npm i xxx -g  ->  yarn add xxx -global
  npm i xxx -S  ->  yarn add xxx
  npm i xxx -D  ->  yarn add xxx -dev
  npm i         ->  yarn

如果需要使用 ts 开发项目,创建项目时可以通过--template typescript指定模版

$ npx create-react-app myapp --template typescript

如果出现如下内容,即代表项目创建成功

Success! Created react-basic at /Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd react-basic
  npm start

Happy hacking!

2.2 项目目录解析

项目创建完毕生成目录结构如下:

react-basic
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js // 做性能测试
    └── setupTests.js // 测试

src/reportWebVitals.js

const reportWebVitals = (onPerfEntry) => {
  if (onPerfEntry && onPerfEntry instanceof Function) {
    import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(onPerfEntry); // 衡量视觉稳定性。为了提供一个好的用户体验,CLS应该小于0.1
      getFID(onPerfEntry); // 衡量可交互性。为了提供一个好的用户体验,FID应该在100毫秒内。
      getFCP(onPerfEntry); // 首次内容绘制
      getLCP(onPerfEntry); // 衡量加载性能。为了提供一个好的用户体验,LCP应该在2.5秒内
      getTTFB(onPerfEntry); // 到第一个字节的时间
    });
  }
};
export default reportWebVitals;

react 官方文档已经给了我们性能提升的方案:https://zh-hans.reactjs.org/docs/optimizing-performance.html

打开package.json,发现可运行命令如下:

 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

start指令用于启动开发者服务器

build指令用于打包项目

test指令用于测试

eject指令用于抽离配置文件

cra脚手架基于webpack,默认webpack的配置在 node_modules 下的 react-scripts 内部,但是一般情况下,传输代码时,不会上传 node_modules,那么在必要情况下就必须得抽离配置文件。

2.3 抽离配置文件

通过npm run eject或者cnpm run eject 或者yarn eject指令抽离配置文件

抽离配置文件过程中注意事项

  • 1.确保项目的 git 仓库是最新的
  • 2.如果不需要对于 webpack 进行配置,那么不需要抽离配置文件
  • create-react-app v2 默认支持 ts 以及 sass 以及 css 的模块化,如果使用 sass 作为 css 预处理器,那么不需要抽离配置文件
Copying files into /Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic
  Adding /config/env.js to the project
  Adding /config/getHttpsConfig.js to the project
  Adding /config/modules.js to the project
  Adding /config/paths.js to the project
  Adding /config/webpack.config.js to the project
  Adding /config/webpackDevServer.config.js to the project
  Adding /config/jest/babelTransform.js to the project
  Adding /config/jest/cssTransform.js to the project
  Adding /config/jest/fileTransform.js to the project
  Adding /scripts/build.js to the project
  Adding /scripts/start.js to the project
  Adding /scripts/test.js to the project
  Adding /config/webpack/persistentCache/createEnvironmentHash.js to the project

Updating the dependencies
  Removing react-scripts from dependencies
  Adding @babel/core to dependencies
  Adding @pmmmwh/react-refresh-webpack-plugin to dependencies
  Adding @svgr/webpack to dependencies
  Adding babel-jest to dependencies
  Adding babel-loader to dependencies
  Adding babel-plugin-named-asset-import to dependencies
  Adding babel-preset-react-app to dependencies
  Adding bfj to dependencies
  Adding browserslist to dependencies
  Adding camelcase to dependencies
  Adding case-sensitive-paths-webpack-plugin to dependencies
  Adding css-loader to dependencies
  Adding css-minimizer-webpack-plugin to dependencies
  Adding dotenv to dependencies
  Adding dotenv-expand to dependencies
  Adding eslint to dependencies
  Adding eslint-config-react-app to dependencies
  Adding eslint-webpack-plugin to dependencies
  Adding file-loader to dependencies
  Adding fs-extra to dependencies
  Adding html-webpack-plugin to dependencies
  Adding identity-obj-proxy to dependencies
  Adding jest to dependencies
  Adding jest-resolve to dependencies
  Adding jest-watch-typeahead to dependencies
  Adding mini-css-extract-plugin to dependencies
  Adding postcss to dependencies
  Adding postcss-flexbugs-fixes to dependencies
  Adding postcss-loader to dependencies
  Adding postcss-normalize to dependencies
  Adding postcss-preset-env to dependencies
  Adding prompts to dependencies
  Adding react-app-polyfill to dependencies
  Adding react-dev-utils to dependencies
  Adding react-refresh to dependencies
  Adding resolve to dependencies
  Adding resolve-url-loader to dependencies
  Adding sass-loader to dependencies
  Adding semver to dependencies
  Adding source-map-loader to dependencies
  Adding style-loader to dependencies
  Adding tailwindcss to dependencies
  Adding terser-webpack-plugin to dependencies
  Adding webpack to dependencies
  Adding webpack-dev-server to dependencies
  Adding webpack-manifest-plugin to dependencies
  Adding workbox-webpack-plugin to dependencies

Updating the scripts
  Replacing "react-scripts start" with "node scripts/start.js"
  Replacing "react-scripts build" with "node scripts/build.js"
  Replacing "react-scripts test" with "node scripts/test.js"

Configuring package.json
  Adding Jest configuration
  Adding Babel preset

Running npm install...

up to date in 4s

203 packages are looking for funding
  run `npm fund` for details
Ejected successfully!
$ npm start
$ npm build

2.4 webpack 二次封装

2.4.1 集成 css 预处理器

  • 集成 less 预处理器
$ cnpm i less less-loader -D
  • 集成 sass 预处理器
$ cnpm i node-sass -D
  • 集成 stylus 预处理器
$ cnpm i stylus stylus-loader -D

具体配置如下:

React-basic/config/webpack.config.js

// style files regexes 可以搜索此关键字快速定位
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
const stylusRegex = /\.stylus/;
const stylusModuleRegex = /\.module\.stylus/;

// "postcss" loader applies autoprefixer to our CSS.可以搜索此关键字快速定位
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
  test: cssRegex,
    exclude: cssModuleRegex,
      use: getStyleLoaders({
        importLoaders: 1,
        sourceMap: isEnvProduction
        ? shouldUseSourceMap
        : isEnvDevelopment,
        modules: {
          mode: 'icss',
        },
      }),
        // Don't consider CSS imports dead code even if the
        // containing package claims to have no side effects.
        // Remove this when webpack adds a warning or an error for this.
        // See https://github.com/webpack/webpack/issues/6571
        sideEffects: true,
},
  // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
  // using the extension .module.css
  {
    test: cssModuleRegex,
      use: getStyleLoaders({
        importLoaders: 1,
        sourceMap: isEnvProduction
        ? shouldUseSourceMap
        : isEnvDevelopment,
        modules: {
          mode: 'local',
          getLocalIdent: getCSSModuleLocalIdent,
        },
      }),
  },
    // Opt-in support for SASS (using .scss or .sass extensions).
    // By default we support SASS Modules with the
    // extensions .module.scss or .module.sass
    {
      test: sassRegex,
        exclude: sassModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 3,
              sourceMap: isEnvProduction
              ? shouldUseSourceMap
              : isEnvDevelopment,
              modules: {
                mode: 'icss',
              },
            },
            'sass-loader'
          ),
            // Don't consider CSS imports dead code even if the
            // containing package claims to have no side effects.
            // Remove this when webpack adds a warning or an error for this.
            // See https://github.com/webpack/webpack/issues/6571
            sideEffects: true,
    },
      // Adds support for CSS Modules, but using SASS
      // using the extension .module.scss or .module.sass
      {
        test: sassModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 3,
              sourceMap: isEnvProduction
              ? shouldUseSourceMap
              : isEnvDevelopment,
              modules: {
                mode: 'local',
                getLocalIdent: getCSSModuleLocalIdent,
              },
            },
            'sass-loader'
          ),
      },
        {
          test: lessRegex,
            exclude: lessModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
                  modules: {
                    mode: 'icss',
                  },
                },
                'less-loader'
              ),
                // Don't consider CSS imports dead code even if the
                // containing package claims to have no side effects.
                // Remove this when webpack adds a warning or an error for this.
                // See https://github.com/webpack/webpack/issues/6571
                sideEffects: true,
        },
          // Adds support for CSS Modules, but using SASS
          // using the extension .module.scss or .module.sass
          {
            test: lessModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
                  modules: {
                    mode: 'local',
                    getLocalIdent: getCSSModuleLocalIdent,
                  },
                },
                'less-loader'
              ),
          },
     {
          test: stylusRegex,
            exclude: stylusModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
                  modules: {
                    mode: 'icss',
                  },
                },
                'stylus-loader'
              ),
                // Don't consider CSS imports dead code even if the
                // containing package claims to have no side effects.
                // Remove this when webpack adds a warning or an error for this.
                // See https://github.com/webpack/webpack/issues/6571
                sideEffects: true,
        },
          // Adds support for CSS Modules, but using SASS
          // using the extension .module.scss or .module.sass
          {
            test: stylusModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
                  modules: {
                    mode: 'local',
                    getLocalIdent: getCSSModuleLocalIdent,
                  },
                },
                'stylus-loader'
              ),
          },

2.4.2 配置@解析别名

vue 项目中可以使用@代替 src 目录,那么 react 中抽离配置文件之后也可以实现此功能

react-basic/config/webpack.config.js

alias: {
  '@': path.resolve('src'), // +++++++++++++
    // Support React Native Web 搜索此关键词快速定位
    // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
    'react-native': 'react-native-web',
      // Allows for better profiling with ReactDevTools
      ...(isEnvProductionProfile && {
      'react-dom$': 'react-dom/profiling',
      'scheduler/tracing': 'scheduler/tracing-profiling',
    }),
      ...(modules.webpackAliases || {}),
},

如果是 ts 项目,需要在tsconfig.json中加入如下配置

{
  "compilerOptions": {
    "target": "es6", // ts代码以es5为标准
    "lib": ["dom", "dom.iterable", "esnext"],
    "paths": {
      // ++++++++++
      "@/*": ["./src/*"]
    },
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src",
    "src/**/*" // ++++++++++
  ]
}

src/index.js 测试

import React from "react";
import ReactDOM from "react-dom/client";
import "@/index.css"; // ++++++
import App from "@/App"; // ++++++
import reportWebVitals from "@/reportWebVitals"; // ++++++

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

如果控制台报错如下,说明@别名没有配置成功

Failed to compile.

Module not found: Error: Can't resolve '@/index.css' in '/Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic/src'
ERROR in ./src/index.js 6:0-21
Module not found: Error: Can't resolve '@/index.css' in '/Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic/src'

ERROR in ./src/index.js 7:0-24
Module not found: Error: Can't resolve '@/App' in '/Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic/src'

ERROR in ./src/index.js 8:0-48
Module not found: Error: Can't resolve '@/reportWebVitals' in '/Users/wudaxun/Desktop/workspace/code/bk2207A/code/react-course/react-basic/src'

webpack compiled with 3 errors

如果没有错误说明配置是成功的。

如果不抽离配置文件,但是也需要配置别名@

$ cnpm i @craco/craco -D

项目根目录中创建 craco 的配置文件:craco.config.js

const path = require("path");
module.exports = {
  webpack: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
};

修改package.json中运行命令

"scripts": {
   "start": "craco start",
   "build": "craco build"
   "test": "craco test"
}

重启服务器即可生效

2.5 setupProxy 代理

即使不抽离配置文件,也是在此处配置代理

如果整个项目只有一个服务器且有跨域问题,可以直接在package.json中做如下配置:

"proxy": "http://121.89.205.189:3001/api/"

然后在项目中可以如下访问:

// src/index.js
fetch('/pro/list').then(res => res.json()).then(res => {
  console.log(res.data)
})

那么如果有多个服务器 并且也需要解决跨域问题

首先,http-proxy-middleware使用 npm 或 Yarn 安装:

$ cnpm install http-proxy-middleware -S
$ # or
$ yarn add http-proxy-middleware -S

接下来,创建src/setupProxy.js并在其中放置以下内容:

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  // ...
};

您现在可以根据需要注册代理了!这是使用上述内容的示例http-proxy-middleware

const { createProxyMiddleware } = require("http-proxy-middleware"); // 此处不使用import语法

module.exports = function (app) {
  // http://121.89.205.189:3001/api/pro/list ==> /myapi/pro/list
  app.use(
    "/myapi",
    createProxyMiddleware({
      target: "http://121.89.205.189:3001/api",
      changeOrigin: true,
      pathRewrite: {
        "^/myapi": "",
      },
    })
  );
};

**注意:**您不需要在任何地方导入此文件。当您启动开发服务器时,它会自动注册。

**注意:**此文件仅支持 Node 的 JavaScript 语法。确保只使用受支持的语言功能(即不支持 Flow、ES 模块等)。

**注意:**将路径传递给代理函数允许您在路径上使用通配符和/或模式匹配,这比快速路由匹配更灵活。

三、JSX

设想如下变量声明:

const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。

JSX 可以生成 React “元素”。

React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

src文件夹下只保留index.js

3.1 jsx 语法详解

在下面的例子中,我们声明了一个名为 name 的变量,然后在 JSX 中使用它,并将它包裹在大括号中

src/index.js

// src/index.js
// react 18版本写法
// import React from 'react'
// import ReactDOM from 'react-dom/client'

// const name = <h1>前端就是好啊!</h1>
// const app = <div>你好, { name }</div> // js 变量加{} 变 react

// const root = ReactDOM.createRoot(document.getElementById('root'))

// root.render(app)

// react 18版本以前
import React from "react";
import ReactDOM from "react-dom";

const name = <h1>前端就是好啊!!!!</h1>;
const app = <div>你好, {name}</div>; // js 变量加{} 变 react

// 警告信息:ReactDOM.render is no longer supported in React 18. Use createRoot instead.
ReactDOM.render(app, document.getElementById("root"));

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2+2user.firstNameformatName(user) 都是有效的 JavaScript 表达式。

在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到 <div> 元素中。

src/index.js

// src/index.js
// react 18版本写法
import React from "react";
import ReactDOM from "react-dom/client";

// const name = <h1>前端就是好啊!!</h1>
// const app = <div>你好, { name }</div> // js 变量加{} 变 react

function formatUser(user) {
  return user.firstName + user.lastName;
}

const user = {
  firstName: "吴",
  lastName: "大勋",
};
// 为了便于阅读,我们会将 JSX 拆分为多行。
// 同时,我们建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱。
const app = (
  <div>
    你好,
    {formatUser(user)}
  </div>
);
const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(app);

jsx 也是一个表达式

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX

src/index.js

// src/index.js
// react 18版本写法
import React from "react";
import ReactDOM from "react-dom/client";

// const name = <h1>前端就是好啊!!</h1>
// const app = <div>你好, { name }</div> // js 变量加{} 变 react

function formatUser(user) {
  return user.firstName + user.lastName;
}

const user = {
  firstName: "吴",
  lastName: "大勋",
};
// 为了便于阅读,我们会将 JSX 拆分为多行。
// 同时,我们建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱。
// const app = (
//   <div>
//     你好,
//     { formatUser(user) }
//   </div>
// )

function getGreeting(user) {
  if (user) {
    return (
      <div>
        你好,
        {formatUser(user)}
      </div>
    );
  } else {
    return <div>hello error</div>;
  }
}

// const app = getGreeting()
const app = getGreeting(user);

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(app);

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

上述代码都没见到使用过 React 模块,但是显示却用了,为什么?

3.2 React.createElement

先看一个代码

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// 因为 JSX 语法上更接近 JavaScript 而不是 HTML,
// 所以 React DOM 使用 `camelCase`(小驼峰命名)来定义属性的名称,
// 而不使用 HTML 属性名称的命名约定。

// 例如,JSX 里的 `class` 变成了 className
// class 在React中被看做了关键字
// const app = (
//   <div className='box'>
//     hello react
//   </div>
// )

// ?为什么react模块被显示使用了
const app = React.createElement(
  "div",
  { className: "box" },
  "hello react !!!"
);

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(app);

React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

// 注意:这是简化过的结构
const element = {
  type: "div",
  props: {
    className: "box",
    children: "hello react",
  },
};

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

四、组件定义

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

4.1 类组件

ES6 的加入让 JavaScript 直接支持使用 class 来定义一个类,react 的创建类组件的方式就是使用的类的继承,ES6 class是一种使用 React 组件的写法,它使用了 ES6 标准语法来构建

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// 类的首字母必须大写
// 组件的首字母必须大写
class App extends React.Component {
  // render函数是 类组件必须实现的一个 方法,同时也是react 类组件唯一一个必不可少的方法
  // 在render函数内部一定要返回 jsx 代码(也可以写React.createElement)
  // 如果jsx代码足够复杂,记得使用()包裹jsx代码
  // render 函数是react 类组件的生命周期的钩子函数
  render() {
    return <div>hello react class component</div>;
  }
}

const root = ReactDOM.createRoot(document.getElementById("root"));

// 以标签的形式渲染组件
// ? 也说明了为什么组件的首字母要大写
// 小写被当作固有的HTML标签
root.render(<App />);

4.2 函数组件

定义组件最简单的方式就是编写 JavaScript 函数

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// 组件首字母必须大写
// function App () {
//   return (
//     <div> hello react function component </div>
//   )
// }

// const App = function () {
//   return (
//     <div> hello react function component </div>
//   )
// }

// const App = () => {
//   return (
//     <div> hello react function component </div>
//   )
// }

const App = () => <div> hello react function component </div>;

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

4.3 两组组件的区别

  • 组件的定义方式不同。
  • 生命周期不同:类组件有,函数式组件没有。
  • 副作用操作执行不同:class 组件通过生命周期函数,函数组件用 hook 的 useEffect。
  • state 的定义、读取、修改方式不同:函数组件用 hook 的 useState。
  • this: class 组件有,函数式组件没有。
  • 实例: class 组件有,函数时组件没有。
  • ref 使用不同:类组件可以获取子组件实例,函数式组件不可以,因为函数式组件没有实例。

官方推荐使用函数式组件,以上不同点虽然现在不明白是啥意思,没有关系,会随着大家的学习印象加深。

五、Props

5.1 Props 详解

props是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props

React 非常灵活,但它也有一个严格的规则:

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

纯函数:输入一定,输出一定确定

总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。

通过箭头函数创建的组件,需要通过函数的参数来接收props

通过类创建的组件,需要通过 this.props来接收

组件可以在其输出中引用其他组件。

这就可以让我们用同一组件来抽象出任意层次的细节。

按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

5.2 父子组件通信

5.2.1 构建一个父子组件

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

import App from "./01_props/01_Parent_Child"; // 省略.jsx

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/01_Parent_Child.jsx

// src/01_props/01_Parent_Child.jsx
import React from "react";

class Child extends React.Component {
  render() {
    return (
      <div>
        <h3>子组件</h3>
      </div>
    );
  }
}
class Parent extends React.Component {
  render() {
    return (
      <div>
        <h3>父组件</h3>
        <Child />
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>父子组件</h1>
        <Parent />
      </div>
    );
  }
}

export default App;

5.2.2 父组件给子组件传值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
import App from "./01_props/02_Parent_Child_value"; // 父组件给子组件传值

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/02_Parent_Chil_value.jsx

// src/01_props/02_Parent_Chil_value.jsx
import React from "react";
// 父组件在调用子组件的地方,添加自定义的属性,
// 如果属性的值是变量,boolean类型,number类型,对象,数组,null,undefined,函数,
// 需要使用{}包裹

// 如果子组件是类组件,可以通过 this.props 访问到父组件传递的值
// 如果子组件是函数式组件,可以通过 函数的默认参数 props 访问到父组件传递的值
const Child2 = (props) => {
  console.log("props2", props);
  return (
    <div>
      <h3>子组件2</h3>
      <div>str的值为{props.str}</div>
      <div>flag的值为{props.flag + ""}</div>
      <div>num的值为{props.num}</div>
      <div>obj的a值为{props.obj.a}</div>
      <div>arr的值为{props.arr}</div>
    </div>
  );
};

class Child1 extends React.Component {
  render() {
    console.log("props1", this.props);
    return (
      <div>
        <h3>子组件1</h3>
        <div>str的值为{this.props.str}</div>
        <div>flag的值为{this.props.flag + ""}</div>
        <div>num的值为{this.props.num}</div>
        <div>obj的a值为{this.props.obj.a}</div>
        <div>arr的值为{this.props.arr}</div>
      </div>
    );
  }
}
class Parent extends React.Component {
  render() {
    const str = "hello world";
    return (
      <div>
        <h3>父组件</h3>
        <Child1
          str={str}
          flag={true}
          num={100}
          obj={{ a: 1, b: 2 }}
          arr={["a", "b", "c"]}
        />
        <Child2
          str={str}
          flag={false}
          num={10000}
          obj={{ a: 10, b: 20 }}
          arr={["aa", "bb", "cc"]}
        />
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>父子组件</h1>
        <Parent />
      </div>
    );
  }
}

export default App;

5.2.3 父组件给子组件传值设置默认值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
import App from "./01_props/03_Parent_Child_default"; // 父组件给子组件传值,子组件设置默认值

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/03_Parent_Chil_default.jsx

// src/01_props/03_Parent_Chil_default.jsx
import React from "react";
// 父组件在调用子组件的地方,添加自定义的属性,
// 如果属性的值是变量,boolean类型,number类型,对象,数组,null,undefined,函数,
// 需要使用{}包裹

// 如果子组件是类组件,可以通过 this.props 访问到父组件传递的值
// 如果子组件是函数式组件,可以通过 函数的默认参数 props 访问到父组件传递的值

// 如果子组件是函数式组件,就在组件定义之后通过 组件.defaultProps 设置默认值
// 如果子组件是类组件,其中一种方式就在组件定义之后通过 组件.defaultProps 设置默认值
// 如果子组件是类组件, 另一种方式就是在定义子组件时,通过类的静态属性设置 defaultProps
const Child2 = (props) => {
  console.log("props2", props);
  return (
    <div>
      <h3>子组件2</h3>
      <div>str的值为{props.str}</div>
      <div>flag的值为{props.flag + ""}</div>
      <div>num的值为{props.num}</div>
      <div>obj的a值为{props.obj.a}</div>
      <div>arr的值为{props.arr}</div>
    </div>
  );
};
Child2.defaultProps = {
  str: "hello react function",
};

class Child1 extends React.Component {
  static defaultProps = {
    str: "hello react static props",
  };
  render() {
    console.log("props1", this.props);
    return (
      <div>
        <h3>子组件1</h3>
        <div>str的值为{this.props.str}</div>
        <div>flag的值为{this.props.flag + ""}</div>
        <div>num的值为{this.props.num}</div>
        <div>obj的a值为{this.props.obj.a}</div>
        <div>arr的值为{this.props.arr}</div>
      </div>
    );
  }
}
// Child1.defaultProps = {
//   str: 'hello react class'
// }
class Parent extends React.Component {
  render() {
    const str = "hello world";
    return (
      <div>
        <h3>父组件</h3>
        <Child1
          str={str}
          flag={true}
          num={100}
          obj={{ a: 1, b: 2 }}
          arr={["a", "b", "c"]}
        />
        <Child2
          flag={false}
          num={10000}
          obj={{ a: 10, b: 20 }}
          arr={["aa", "bb", "cc"]}
        />
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>父子组件</h1>
        <Parent />
      </div>
    );
  }
}

export default App;

5.2.4 使用 prop-types 属性验证

自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 库 代替。

$ cnpm i prop-types -D
import PropTypes from "prop-types";

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 一个 React 元素类型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(["News", "Photos"]),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message),
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number,
  }),

  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number,
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的必需数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
  customProp: function (props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        "Invalid prop `" +
          propName +
          "` supplied to" +
          " `" +
          componentName +
          "`. Validation failed."
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function (
    propValue,
    key,
    componentName,
    location,
    propFullName
  ) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        "Invalid prop `" +
          propFullName +
          "` supplied to" +
          " `" +
          componentName +
          "`. Validation failed."
      );
    }
  }),
};

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
import App from "./01_props/04_Parent-Child_type"; // 父组件给子组件传值,子组件设置默认值,并且验证数据类型

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/04_Parent_Chil_type.jsx

// src/01_props/04_Parent_Chil_type.jsx
import React from "react";
// 父组件在调用子组件的地方,添加自定义的属性,
// 如果属性的值是变量,boolean类型,number类型,对象,数组,null,undefined,函数,
// 需要使用{}包裹

// 如果子组件是类组件,可以通过 this.props 访问到父组件传递的值
// 如果子组件是函数式组件,可以通过 函数的默认参数 props 访问到父组件传递的值

// 如果子组件是函数式组件,就在组件定义之后通过 组件.defaultProps 设置默认值
// 如果子组件是类组件,其中一种方式就在组件定义之后通过 组件.defaultProps 设置默认值
// 如果子组件是类组件, 另一种方式就是在定义子组件时,通过类的静态属性设置 defaultProps

// 如果需要给子组件的值设置数据类型的校验,通过第三方的 prop-types 来完成
// 定义好组件之后, 通过 组件.propTypes 设置
import PropTypes from "prop-types";

const Child2 = (props) => {
  console.log("props2", props);
  return (
    <div>
      <h3>子组件2</h3>
      <div>str的值为{props.str}</div>
      <div>flag的值为{props.flag + ""}</div>
      <div>num的值为{props.num}</div>
      <div>obj的a值为{props.obj.a}</div>
      <div>arr的值为{props.arr}</div>
    </div>
  );
};
Child2.defaultProps = {
  str: "hello react function",
};
Child2.propTypes = {
  str: PropTypes.string.isRequired, // 该属性必须传递,但是只要设置了默认值,即可不传(vue即使设置默认值,但是还需要传递值)
  flag: PropTypes.bool,
  // num: PropTypes.string,
  // num: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, // 多个类型
  obj: PropTypes.object,
  arr: PropTypes.array,
  num: function (props, propName, componentName) {
    console.log(props);
    console.log(propName);
    console.log(componentName);
    if (typeof props[propName] !== "number") {
      return new Error("请传入number类型数据");
    }
    if (props[propName] < 1000) {
      return new Error("出错了");
    }
  },
};

class Child1 extends React.Component {
  static defaultProps = {
    str: "hello react static props",
  };
  render() {
    console.log("props1", this.props);
    return (
      <div>
        <h3>子组件1</h3>
        <div>str的值为{this.props.str}</div>
        <div>flag的值为{this.props.flag + ""}</div>
        <div>num的值为{this.props.num}</div>
        <div>obj的a值为{this.props.obj.a}</div>
        <div>arr的值为{this.props.arr}</div>
      </div>
    );
  }
}
// Child1.defaultProps = {
//   str: 'hello react class'
// }
class Parent extends React.Component {
  render() {
    const str = "hello world";
    return (
      <div>
        <h3>父组件</h3>
        <Child1
          str={str}
          flag={true}
          num={100}
          obj={{ a: 1, b: 2 }}
          arr={["a", "b", "c"]}
        />
        <Child2
          flag={false}
          num="100"
          obj={{ a: 10, b: 20 }}
          arr={["aa", "bb", "cc"]}
        />
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>父子组件</h1>
        <Parent />
      </div>
    );
  }
}

export default App;

5.3 props.children

我们知道使用组件的时候,可以嵌套。要在自定义组件中使用嵌套结构,就需要使用 props.children

等同于 vue 中的 slot 插槽

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
import App from "./01_props/05_App_props_children"; // 类插槽

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/05_App_props_children.jsx

import React, { Component } from "react";
// props.children 可以看作是类似于vue中的slot
const Content = (props) => {
  return <div>{props.children}</div>;
};
class Header extends Component {
  render() {
    return <header>{this.props.children}</header>;
  }
}
class App extends Component {
  render() {
    return (
      <div>
        <Header>这里是首页头部</Header>
        <Content>这里是首页内容</Content>
        <hr />
        <Header>这里是分类头部</Header>
        <Content>这里是分类内容</Content>
      </div>
    );
  }
}

export default App;

如果需要给组件添加多个元素,并且显示在多个位置,可以如下设置:

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
import App from "./01_props/06_App_mutiple_props_children"; // 类具名插槽(多个插槽)

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/06_App_mutiple_props_children.jsx

import React, { Component } from "react";
// 以数组的下标决定显示的位置
const Header = (props) => {
  console.log(props.children);
  return (
    <header>
      <ul>
        <li>{props.children[0]}</li>
        <li>{props.children[1]}</li>
        <li>{props.children[2]}</li>
      </ul>
    </header>
  );
};

class App extends Component {
  render() {
    return (
      <div>
        <Header>
          <div>城市</div>
          <div>标题</div>
          <div>登录</div>
        </Header>
        <Header>
          <div>返回</div>
          <div>产品名称</div>
          <div>更多</div>
        </Header>
      </div>
    );
  }
}

export default App;

实现类似 vue 的具名插槽,需要通过 props.children 的下标去访问

5.4 render props 特性

使用 Render Props 来解决横切关注点(Cross-Cutting Concerns)

组件是 React 代码复用的主要单元,但如何将一个组件封装的状态或行为共享给其他需要相同状态的组件并不总是显而易见。

以下组件跟踪 Web 应用程序中的鼠标位置:

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
import App from "./01_props/07_App_mouse_tracker"; // 鼠标跟随

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/07_App_mouse_tracker.jsx

还没有学习状态 state 以及事件处理,这里先用

import React, { Component } from "react";

class App extends Component {
  state = {
    // 初始化状态
    x: 0,
    y: 0,
  };
  render() {
    // style 写为对象,对象使用 {} 包裹
    // onMouseMove 事件,使用小驼峰式命名,函数使用 {} 包裹,函数默认参数为event事件对象
    return (
      <div
        style={{ width: "100vw", height: "100vh" }}
        onMouseMove={(event) => {
          // 修改状态 --  不要使用赋值表达式
          this.setState({
            x: event.clientX,
            y: event.clientY,
          });
        }}
      >
        <p>
          鼠标位置:({this.state.x}, {this.state.y})
        </p>
      </div>
    );
  }
}

export default App;

当光标在屏幕上移动时,组件在 <p> 中显示其坐标。

现在的问题是:我们如何在另一个组件中复用这个行为?换个说法,若另一个组件需要知道鼠标位置,我们能否封装这一行为,以便轻松地与其他组件共享它?

render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
import App from "./01_props/08_App_render_props"; // 渲染属性 - 其他组件共享状态

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/01_props/08_App_render_props.jsx

import React, { Component } from "react";
// 渲染属性共享组件的状态
// 在调用需要共享的组件(Mouse)上,添加一个render的自定义属性,该属性是一个自定义函数
// 在自定义函数的内部返回需要共享给的那个组件(Cat)
// 在需要共享的组件(Mouse)内部,通过 this.props.render() 或者 props.render() 即可调用,参数即为需要共享的状态
// 那么在定义自定义render属性的函数内部,就会接收到 参数,通过返回的组件(Cat)传递该参数即可

const Cat = (props) => {
  return (
    <div>
      cat鼠标位置:({props.point.x}, {props.point.y})
    </div>
  );
};
const Dog = (props) => {
  return (
    <div>
      Dog鼠标位置:({props.point.x}, {props.point.y})
    </div>
  );
};

class Mouse extends Component {
  state = {
    // 初始化状态
    x: 0,
    y: 0,
  };
  render() {
    // style 写为对象,对象使用 {} 包裹
    // onMouseMove 事件,使用小驼峰式命名,函数使用 {} 包裹,函数默认参数为event事件对象
    return (
      <div
        style={{ width: "100vw", height: "50vh" }}
        onMouseMove={(event) => {
          // 修改状态 --  不要使用赋值表达式
          this.setState({
            x: event.clientX,
            y: event.clientY,
          });
        }}
      >
        <p>
          mouse鼠标位置:({this.state.x}, {this.state.y})
        </p>
        {this.props.render(this.state)}
      </div>
    );
  }
}
class App extends Component {
  render() {
    return (
      <div>
        {/* <Mouse render = { (point) => {
          return <Cat point = { point }></Cat>
        } }></Mouse>
        <hr />
        <Mouse render = { (point) => {
          return <Dog point = { point }></Dog>
        } }></Mouse> */}
        <Mouse
          render={(point) => {
            return (
              <>
                <Cat point={point}></Cat>
                <Dog point={point}></Dog>
              </>
            );
          }}
        ></Mouse>
      </div>
    );
  }
}

export default App;

此案例实际上完成了 react 中子组件给父组件传值

六、State

stateclass组件的内置对象,用于 class 组件内部数据更新

state就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用state的目的就是为了在不同的状态下使组件的显示不同(自己管理)

6.1 state 及其特点

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件

不要直接修改 state

state 更新可能是异步的:出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

state 更新会被合并:当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

6.2 state 的定义和使用

目前 react 中的状态有两种使用方式:

6.2.1 es6 的类 - 构造函数

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
import App from "./02_state/01_App_state_es6"; // es6 构造函数 初始化状态

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/02_state/01_App_state_es6.jsx

import React, { Component } from "react";
/**
 * ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,
得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。
如果不调用super()方法,子类就得不到自己的this对象。

 ES5 的继承机制,是先创造一个独立的子类的实例对象,
 然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。
 ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,
 然后再将该对象作为子类的实例,即“继承在前,实例在后”
 */
class App extends Component {
  // constructor (props) { //  Useless constructor
  //   super(props)
  // }

  constructor(props) {
    super(props);
    this.state = {
      date: new Date(),
    };
  }
  render() {
    return (
      <div>
        当前时间为:
        {this.state.date.toLocaleDateString() +
          this.state.date.toLocaleTimeString()}
      </div>
    );
  }
}

export default App;

6.2.2 es7 的类 - 属性初始化器

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
import App from "./02_state/02_App_state_es7"; // es7 属性初始化器 初始化状态

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/02_state/02_App_state_es7.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    date: new Date(),
  };

  render() {
    return (
      <div>
        当前时间为:
        {this.state.date.toLocaleDateString() +
          this.state.date.toLocaleTimeString()}
        !!
      </div>
    );
  }
}

export default App;

6.3 如何正确的修改 state

setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式.

setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。

setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。

记住修改状态的三大原则:

  • 不要直接修改 State
state = { a: 10 };
this.state.a = 100; // ❌
  • state 的更新可能是异步的
state = { a: 10 };
this.setState({ a: this.state.a + 1 });
this.setState({ a: this.state.a + 1 });
this.setState({ a: this.state.a + 1 });
console.log(this.state.a); // 10
  • state 的更新会被合并

6.4 this.setState()方法及其特点

setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。

setState()可以添加两个参数,

setState() 的第二个参数为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行

6.4.1 传递函数

参数一为带有形式参数的 updater 函数:

this.setState((state, props) => stateChange[, callback] )

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
import App from "./02_state/03_App_setState_function"; // 修改状态 传递函数

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/02_state/03_App_setState_function.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 100,
  };
  render() {
    return (
      <div>
        {this.state.count}
        <button
          onClick={() => {
            this.setState(
              (state, props) => {
                return {
                  count: state.count + 1,
                };
              },
              () => {
                console.log(4, this.state.count); // 103
              }
            );
            console.log(1, this.state.count); // 100
            this.setState(
              (state, props) => {
                return {
                  count: state.count + 1,
                };
              },
              () => {
                console.log(5, this.state.count); // 103
              }
            );
            console.log(2, this.state.count); // 100

            this.setState(
              (state, props) => {
                return {
                  count: state.count + 1,
                };
              },
              () => {
                console.log(6, this.state.count); // 103
              }
            );
            console.log(3, this.state.count); // 100
          }}
        >
          加1
        </button>
      </div>
    );
  }
}

export default App;

updater 函数中接收的 stateprops 都保证为最新。updater 的返回值会与 state 进行浅合并。

6.4.2 传递对象

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
import App from "./02_state/04_App_setState_object"; // 修改状态 传递对象

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/02_state/04_App_setState_object.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 100,
  };
  render() {
    return (
      <div>
        {this.state.count}
        <button
          onClick={() => {
            this.setState(
              {
                count: this.state.count + 1,
              },
              () => {
                console.log(4, this.state.count); // 101
              }
            );
            console.log(1, this.state.count); // 100

            this.setState(
              {
                count: this.state.count + 1,
              },
              () => {
                console.log(5, this.state.count); // 101
              }
            );
            console.log(2, this.state.count); // 100

            this.setState(
              {
                count: this.state.count + 1,
              },
              () => {
                console.log(6, this.state.count); // 101
              }
            );
            console.log(3, this.state.count); // 100
          }}
        >
          加1
        </button>
      </div>
    );
  }
}

export default App;

这种形式的 setState() 是异步的,并且在同一周期内会对多个 setState 进行批处理,相当于

Object.assign(
      prevState,
      {count: this.state.count + 1},
      {count: this.state.count + 1},
      ...
)

后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。如果后续状态取决于当前状态,建议使用 updater 函数的形式代替(前面案例已经实现)。或者在第二个参数中再继续操作。

思考题:

1.何时以及为什么 setState() 会批量执行?

2.为什么不直接更新 this.state

七、生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载,初始化):已插入真实 DOM
  • Updating(更新,运行时):正在被重新渲染
  • Unmounting(卸载,销毁):已移出真实 DOM

生命周期图谱可以参考链接:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

类组件如何实现类似 vue 的计算属性: https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

$ cnpm i memoize-one -S

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
import App from "./02_state/05_App_computed"; // 类组件实现类似计算属性

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/02_state/05_App_computed.jsx

import React, { Component } from "react";
import memoize from "memoize-one";
class MemoList extends Component {
  state = {
    text: "",
  };
  filter = memoize((list, text) => {
    return list.filter((item) => text !== "" && item.includes(text));
  });
  render() {
    const list = this.filter(this.props.list, this.state.text);
    return (
      <>
        <input
          type="text"
          value={this.state.text}
          onChange={(event) => {
            this.setState({
              text: event.target.value,
            });
          }}
        />
        <ul>
          {list.map((item, index) => {
            return <li key={index}> {item} </li>;
          })}
        </ul>
      </>
    );
  }
}

class App extends Component {
  state = {
    list: ["a", "ab", "abc", "abcd"],
  };
  render() {
    return (
      <div>
        <MemoList list={this.state.list} />
      </div>
    );
  }
}

export default App;

7.1 三个阶段

7.1.1 装载阶段

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor(): 在 React 组件挂载之前,会调用它的构造函数。

    如果不需要对类组件添加初始化数据以及绑定事件,那么就不需要写 constructor

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。

  • render(): render() 方法是 class 组件中唯一必须实现的方法。

  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.2 更新阶段

每当组件的 state 或 props 发生变化时,组件就会更新。

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate(): 在更新后会被立即调用,如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.3 卸载阶段

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

7.1.4 Error boundaries

Error boundaries 是 React 组件,它会在其子组件树中的任何位置捕获 JavaScript 错误,并记录这些错误,展示降级 UI 而不是崩溃的组件树。Error boundaries 组件会捕获在渲染期间,在生命周期方法以及其整个树的构造函数中发生的错误。

项目中需要使用的最多的生命周期的钩子函数为 render, componentDidMount,componentDidUpdate,componentWillUnmount

详细介绍范例:https://zhuanlan.zhihu.com/p/392532496

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
import App from "./02_state/06_App_lifeCycle"; // 类组件生命周期

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    console.log(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}
const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(
  <ErrorBoundary>
    <App root={root} />
  </ErrorBoundary>
);

src/02_state/06_App_lifeCycle.jsx

import React, { Component } from "react";

class App extends Component {
  // constructor (props) { // Useless constructor
  //   super(props)
  // }
  state = { count: 100 };
  // static getDerivedStateFromProps (props, state) { // 一般不使用
  //   // getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  //   // 它应返回一个对象来更新 state,如果返回 null 则不更新任何内容
  //   // state 的值在任何时候都取决于 props
  // }

  componentDidMount() {
    // 等同于 vue中的 mounted
    // 数据请求,实例化操作,DOM操作,定时器,计时器,订阅数据变化
    // 修改状态
    this.setState({ count: this.state.count + 100 });
  }

  shouldComponentUpdate(nextProps, nextState) {
    // 可以作为react组件的性能优化的手段,但是也要慎用
    return true;
  }
  // getSnapshotBeforeUpdate(prevProps, prevState) {
  //   // 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  //   // 它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
  //   // 此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
  // }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 参照vue中 updated
    // 实例化操作,DOM操作, 特定条件可以请求数据以及修改数据
    // if (this.props.userID !== prevProps.userID) { // 监听数据的变化
    //   this.fetchData(this.props.userID);
    // }
  }

  componentWillUnmount() {
    // 参照vue  beforeDestory
    // 清理对象,取消订阅,消除定时器计时器
    // 当count的值等于210的时候销毁组件
  }
  render() {
    // 挂载阶段 依据初始化数据渲染数据
    // 更新阶段 当该组件的状态或者属性发生改变时触发此函数,也就输数据的改变引起视图的二次渲染
    return (
      <div>
        <p>{this.state.count}</p>
        <button
          onClick={() => {
            if (this.state.count === 210) {
              console.log("销毁组件");
              // 销毁组件
              this.props.root.unmount();
            } else {
              this.setState({ count: this.state.count + 1 });
            }
          }}
        >
          加
        </button>
      </div>
    );
  }
}

export default App;

7.2 两个时期

将应用的渲染过程分为mount阶段(应用首次渲染)和update阶段(应用状态更新),无论在mount阶段还是update阶段,都会经历两个子阶段,一个是render阶段,一个是commit阶段。

mount 时
render阶段会根据 jsx 对象构建新的workInProgressFiber树,然后将相应的fiber节点标记为Placement,表示这个fiber节点需要被插入到dom树中,然后会这些带有副作用的fiber节点加入一条叫做Effect List的链表中。
commit阶段会遍历render阶段形成的Effect List,执行链表上相应fiber节点的副作用,比如Placement插入,或者执行Passive(useEffect 的副作用)。将这些副作用应用到真实节点上
update 时
render阶段会根据最新状态的 jsx 对象对比current Fiber,再构建新的workInProgressFiber树,这个对比的过程就是diff算法diff算法又分成单节点的对比和多节点的对比,对比的过程中同样会经历收集副作用的过程,也就是将对比出来的差异标记出来,加入Effect List中,这些对比出来的副作用例如:Placement(插入)、Update(更新)、Deletion(删除)等。
commit阶段同样会遍历Effect List,将这些 fiber 节点上的副作用应用到真实节点上。

参考链接: https://blog.csdn.net/bemystery/article/details/121897223

7.3 入门理解 React Fiber 架构

在 React 16 之前,VirtualDOM 的更新采用的是Stack架构实现的,也就是循环递归方式。不过,这种对比方式有明显的缺陷,就是一旦任务开始进行就无法中断,如果遇到应用中组件数量比较庞大,那么VirtualDOM 的层级就会比较深,带来的结果就是主线程被长期占用,进而阻塞渲染、造成卡顿现象。

为了避免出现卡顿等问题,我们必须保障在执行更新操作时计算时不能超过 16ms,如果超过 16ms,就需要先暂停,让给浏览器进行渲染,后续再继续执行更新计算。而Fiber架构就是为了支持“可中断渲染”而创建的。

React中,Fiber使用了一种新的数据结构fiber tree,它可以把虚拟dom tree转换成一个链表,然后再执行遍历操作,而链表在执行遍历操作时是支持断点重启的,示意图如下。
image.png

官方介绍中,Fiber 被理解为是一种数据结构,但是我们也可以将它理解为是一个执行单元。

Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React Fiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作。React Fiber 与浏览器的交互流程如下图。
image.png

可以看到,React 首先向浏览器请求调度,浏览器在执行完一帧后如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器;如果存在就会执行对应的任务,执行完一个新的任务单元之后会继续判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则将控制权交给浏览器执行渲染,这个流程是循环进行的。

所以,我们可以将Fiber 理解为一个执行单元,并且这个执行单元必须是一次完成的,不能出现暂停。并且,这个小的执行单元在执行完后计算之后,可以移交控制权给浏览器去响应用户,从而提升了渲染的效率。

在官方的文档中,Fiber 被解释为是一种数据结构,即链表结构。在链表结构中,每个 Virtual DOM 都可以表示为一个 fiber,如下图所示。
image.png
通常,一个 fiber包括了 child(第一个子节点)、sibling(兄弟节点)、return(父节点)等属性,React Fiber 机制的实现,就是依赖于上面的数据结构。

通过介绍,我们知道Fiber使用的是链表结构,准确的说是单链表树结构。为了方便理解 Fiber 的遍历过程,下面我们就看下Fiber链表结构。

image.png

在上面的例子中,每一个单元都包含了payload(数据)和nextUpdate(指向下一个单元的指针)两个元素

参考链接:https://segmentfault.com/a/1190000042271919

八、事件绑定

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。\

    * 保证是一个函数
    * 如果需要使用this关键词,保证this指向
    

8.1 ES5 语法绑定事件

8.1.1 无参数的绑定

8.1.1.1 方法一

  • 定义函数
handleClick(e) { // e - 事件对象
  e.preventDefault();
  // doSomething ...
}
  • constructor 中绑定函数执行上下文
this.handleClick = this.handleClick.bind(this);
  • jsx 中调用
<button onClick={this.hanleClick} />

8.1.1.1 方法二

  • 定义函数
handleClick(e) { // e - 事件对象
  e.preventDefault();
  // doSomething ...
}
  • jsx 中调用
<button onClick={this.hanleClick.bind(this)} />

8.1.2 有参数的绑定

  • 定义函数
handleClick(param1, param2, e) {
  e.preventDefault();
  // do something ...
}

注意此时无论多少个参数, e 一定放在最后

  • jsx 中调用
<button onClick={this.hanleClick.bind(this, 'x', 'xx')} />

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
import App from "./03_event/01_App_event_es5"; // 事件处理 es5绑定事件方式

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/03_event/01_App_event_es5.jsx

import React, { Component } from "react";
// es5绑定事件 ---- 主要是对this指向的处理

class App extends Component {
  constructor(props) {
    super(props);
    this.handlerClickFn = this.handlerClick.bind(this);
  }

  handlerClick(event) {
    // event为事件默认参数
    console.log(1, this);
  }

  handlerParamsClick(a, b, event) {
    // 自定义参数  event将作为最后一个参数
    console.log("a", a); // a  1
    console.log("b", b); // b  2
  }
  render() {
    return (
      <div>
        <button onClick={this.handlerClickFn}>es5绑定事件-构造函数</button>
        <button onClick={this.handlerClick.bind(this)}>
          es5绑定事件-jsx改变this指向
        </button>
        <button onClick={this.handlerParamsClick.bind(this, "1", "2")}>
          es5绑定事件-传递参数
        </button>
      </div>
    );
  }
}

export default App;

8.2 ES6 语法绑定事件

8.2.1 无参数绑定

8.2.1.1 方法一

  • 定义函数
handleClick = (e) => {
  e.preventDefault();
  // do something ...
}
  • jsx 中调用
<button onClick={this.hanleClick} />

比起 es 5 中的无参数函数的绑定调用, es 6 不需要使用 bind 函数;

8.2.1.2 方法二

jsx 中定义箭头函数

<button onClick={ () => {}} />

8.2.2 有参数绑定

8.2.2.1 方法一

  • 定义函数
handleClick = (param1, e) => {
  e.preventDefault();
  // do something ...
}
  • jsx 调用
<button onClick={this.hanleClick.bind(this, 'x')} />

有参数时,在绑定时依然要使用 bind;
并且参数一定要传,不然仍然存在 this 指向错误问题;

8.2.2.2 方法二

  • 定义函数
handleClick = (param1, e) => {
  // do something ...
}
  • jsx 调用
<button onClick={() => this.handleClick('c')} />
// 如果需要对 event 对象进行处理的话,需要写成下面的格式
<button onClick={(e) => this.handleClick('c', e)} />

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
import App from "./03_event/02_App_event_es6"; // 事件处理 es6绑定事件方式

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/03_event/02_App_event_es6.jsx

import React, { Component } from "react";
// es5绑定事件 ---- 主要是对this指向的处理

class App extends Component {
  handlerClickFn = (event) => {
    // event 默认参数
    console.log(1, this);
  };
  handlerParamsClick = (a, b, event) => {
    console.log("a", a); // a 1
    console.log("b", b); // b 2
  };
  render() {
    return (
      <div>
        <button onClick={this.handlerClickFn}>es6绑定事件-定义箭头函数</button>
        <button
          onClick={(event) => {
            // event 默认参数
            console.log(2, this);
          }}
        >
          es6绑定事件-jsx写箭头函数
        </button>
        <button onClick={this.handlerParamsClick.bind(this, "1", "2")}>
          es6绑定事件-传递参数
        </button>
        <button
          onClick={(event) => {
            // event 默认参数
            console.log(2, this);
            // this.fetchData({ count: this.state.count })
          }}
        >
          es6绑定事件-jsx写箭头函数,直接使用参数
        </button>
      </div>
    );
  }
}

export default App;

8.3 合成事件的特点

8.3.1 事件机制

  • react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
  • react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
  • 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。

8.3.2 对合成事件的理解

(1)对原生事件的封装

react会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEventreact合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的,原生 js 的 事件对象为 PointerEvent

(2)不同浏览器事件兼容的处理

在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理

8.3.3 事件机制的流程

1、事件注册

在组件挂载阶段,根据组件内声明的事件类型-onclickonchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent

2、事件存储

完成事件注册后,将 react dom ,事件类型,事件处理函数fn放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 listenerBank(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。

开始事件的存储,在 react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。
react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件 id 和 事件类型查找到对应的 事件 fn

3、事件执行

1、进入统一的事件分发函数(dispatchEvent)
2、结合原生事件找到当前节点对应的ReactDOMComponent对象
3、开始 事件的合成

  • 根据当前事件类型生成指定的合成对象
  • 封装原生事件和冒泡机制
  • listenerBank事件池中查找事件回调并合成到 event(合成事件结束)

4.处理合成事件内的回调事件(事件触发完成 end)

8.3.4 合成事件、原生事件之间的冒泡执行关系

结论:

  • 原生事件阻止冒泡肯定会阻止合成事件的触发。

  • 合成事件的阻止冒泡不会影响原生事件。

原因:

  • 浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段

节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。

九、条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。

React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。

9.1 &&

你可以通过用花括号包裹代码在 JSX 中嵌入任何表达式 ,也包括 JavaScript 的逻辑与 &&,它可以方便地条件渲染一个元素。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
import App from "./04_condition/01_App_condition_yu"; // 条件渲染 &&

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/01_App_condition_yu.jsx

import React, { Component } from "react";

class MainBox extends Component {
  render() {
    return (
      <div>
        {this.props.unReadMessage.length > 0 && (
          <span>还有{this.props.unReadMessage.length}条未读消息</span>
        )}
      </div>
    );
  }
}

class App extends Component {
  state = {
    message: ["a", "b", "c", "d"],
  };
  render() {
    return (
      <div>
        {this.state.message.map((item, index) => {
          return (
            <p key={index}>
              {item}
              <button
                onClick={() => {
                  const arr = this.state.message; // 获取数据
                  arr.splice(index, 1); // 处理数据
                  this.setState({
                    // 修改状态
                    message: arr,
                  });
                }}
              >
                已读
              </button>
            </p>
          );
        })}
        <MainBox unReadMessage={this.state.message}></MainBox>
      </div>
    );
  }
}

export default App;

9.2 三元运算符

条件渲染的另一种方法是使用 JavaScript 的条件运算符:

condition ? true : false。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
import App from "./04_condition/02_App_condition_san"; // 条件渲染 三元运算符

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/02_App_condition_san.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    loginState: false,
  };
  render() {
    return (
      <div>
        {this.state.loginState + ""}
        <button
          onClick={() => {
            this.setState({ loginState: !this.state.loginState });
          }}
        >
          切换
        </button>
        {this.state.loginState ? <button>退出</button> : <button>登录</button>}
      </div>
    );
  }
  // render() {
  //   if (this.state.loginState) {
  //     return (
  //       <div>
  //         { this.state.loginState + '' }
  //         <button onClick={ () => { this.setState({ loginState: !this.state.loginState }) } }>切换</button>
  //          <button>退出</button>
  //       </div>
  //     );
  //   } else {
  //     return (
  //       <div>
  //         { this.state.loginState + '' }
  //         <button onClick={ () => { this.setState({ loginState: !this.state.loginState }) } }>切换</button>
  //          <button>登录</button>
  //       </div>
  //     )
  //   }
  // }
  // render () {
  //   // let test = null
  //   // if (this.state.loginState) {
  //   //   test = <button>退出</button>
  //   // } else {
  //   //   test = <button>登录</button>
  //   // }
  //   const test = this.state.loginState ? <button>退出</button> : <button>登录</button>
  //   return (
  //     <div>
  //       { this.state.loginState + '' }
  //       <button onClick={ () => { this.setState({ loginState: !this.state.loginState }) } }>切换</button>
  //       { test }
  //     </div>
  //   )
  // }
}

export default App;

9.3 动态 className

Vue 中有很方便的动态绑定class 属性的方式,v-bind:class,那么 react 怎么实现这样的效果呢?

<button class="btn btn-success btn-sm"></button>

<button class="btn btn-danger btn-sm"></button>

<button class="btn btn-warning btn-sm"></button>

{ this.state.type === 'success' ? 'btn btn-success btn-sm' : 'btn btn-sm'}

通过 classnames 这个插件可以实现

$ cnpm i classnames -S

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
import App from "./04_condition/03_App_condition_classname"; // 条件渲染 动态的class

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/03_App_condition_classname.jsx

import React, { Component } from "react";
import classnames from "classnames";
class App extends Component {
  state = {
    type: "default",
    size: "sm",
  };
  render() {
    return (
      <div>
        <button
          className={classnames({
            btn: true,
            "btn-sm": this.state.size === "sm",
            "btn-success": this.state.type === "default",
          })}
        >
          default
        </button>
        <button
          className={classnames({
            btn: true,
            "btn-md": this.state.size === "md",
            "btn-success": this.state.type === "success",
          })}
        >
          success md
        </button>
      </div>
    );
  }
}

export default App;

补充:

  • css-in-js
$ cnpm i styled-components -S

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
import App from "./04_condition/04_App_condition_cssinjs"; // cssInJs

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/04_App_condition_cssinjs.jsx

import React, { Component } from "react";
import styled from "styled-components";

// css-in-js 内部写的都是纯css
const ODiv = styled.div`
  font-size: 30px;
  color: #f66;
`;
const Button = styled.button`
  padding: 10px 30px;
`;
class App extends Component {
  render() {
    return (
      <div>
        <ODiv>你好</ODiv>
        <Button>按钮</Button>
      </div>
    );
  }
}

export default App;
  • 模块化 css

可以解决类似于 vue 中 scoped

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
import App from "./04_condition/05_App_module_css"; // 模块化css

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/05_App_module_css.jsx

import React, { Component } from "react";
// import './style.module.css'
import style from "./style.module.css";

class App extends Component {
  render() {
    return (
      // <div className='container'>
      //   <div className="header"></div>
      //   <div className="box"></div>
      //   <div className="footer"></div>
      // </div>
      <div className={style.container}>
        <div className={style.header}></div>
        <div className={style.box}></div>
        <div className={style.footer}></div>
      </div>
    );
  }
}

export default App;

src/04_condition/style.module.css

.container {
  width: 100%;
  height: 600px;
  display: flex;
  flex-direction: column;
}

.header {
  width: 100%;
  height: 50px;
  background-color: #f66;
}

.box {
  width: 100%;
  flex: 1;
}

.footer {
  width: 100%;
  height: 50px;
  background-color: #ccc;
}

9.4 动态 style

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
import App from "./04_condition/06_App_style"; // 动态style

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/04_condition/06_App_style.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    size: 12,
    color: "#333",
  };
  render() {
    return (
      <>
        <button
          onClick={() => {
            this.setState({
              size: this.state.size + 2,
            });
          }}
        >
          字号+2
        </button>
        <input
          type="color"
          value={this.state.color}
          onChange={(event) => {
            this.setState({
              color: event.target.value,
            });
          }}
        />
        <div style={{ fontSize: this.state.size, color: this.state.color }}>
          前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊前端就是好啊
        </div>
      </>
    );
  }
}

export default App;

十、列表渲染

  • map()方法、key

使用 map() 方法遍历数组

组件接收数组参数,每个列表元素分配一个 key,不然会出现警告 a key should be provided for list items,意思就是需要包含 key:

Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的 key

当元素没有确定的 id 时,你可以使用他的序列号索引 index 作为 key

如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
import App from "./05_list/01_App_map"; // 列表渲染 使用map

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/05_list/01_App_map.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    proList: [],
  };

  componentDidMount() {
    fetch("http://121.89.205.189:3001/api/pro/list")
      .then((res) => res.json())
      .then((res) => {
        console.log(res.data);
        this.setState({
          proList: res.data,
        });
      });
  }
  // 边遍历边渲染
  // render() {
  //   const { proList } = this.state
  //   return (
  //     <div>
  //       {
  //         // proList && proList.forEach((item, index) => { // forEach没有返回值
  //         //   return (
  //         //     <p key={ item.proid }>{index + 1} - { item.proname }</p>
  //         //   )
  //         // })
  //         proList && proList.map((item, index) => { // map没有返回值
  //           return (
  //             <p key={ item.proid }>{index + 1} - { item.proname }</p>
  //           )
  //         })
  //       }
  //     </div>
  //   );
  // }

  // 先遍历后渲染 - 利用数组装填jsx代码思想
  render() {
    const arr = [];
    this.state.proList.forEach((item, index) => {
      arr.push(
        <p key={item.proid}>
          {index + 1} --- {item.proname}
        </p>
      );
    });
    return <div>{arr}</div>;
  }
}

export default App;

接口 http://121.89.205.189:3001/api/city/sortCity

实现多层遍历

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
import App from "./05_list/02_App_mutiple_map"; // 列表渲染 多层遍历

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/05_list/02_App_mutiple_map.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    cityList: [],
  };

  componentDidMount() {
    fetch("http://121.89.205.189:3001/api/city/sortCity")
      .then((res) => res.json())
      .then((res) => {
        console.log(JSON.parse(res.data));
        this.setState({
          cityList: JSON.parse(res.data),
        });
      });
  }
  render() {
    const { cityList } = this.state;
    return (
      <div>
        <ul>
          {cityList &&
            cityList.map((item) => {
              return (
                <li key={item.letter}>
                  {item.letter}
                  <ol>
                    {item.data &&
                      item.data.map((itm) => (
                        <li key={itm.cityId}> {itm.name}</li>
                      ))}
                  </ol>
                </li>
              );
            })}
        </ul>
      </div>
    );
  }
}

export default App;

十一、表单绑定

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:

<form>
  <label>
    名字:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="提交" />
</form>

此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

表单元素的 value 值受 state 的控制

11.1 各种表单的绑定与取值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
import App from "./06_form/01_App_form"; // 表单绑定 受控组件

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/06_form/01_App_form.jsx

import React, { Component } from 'react';

class App extends Component {
  state = {
    userName: '',
    password: '',
    sex: '女',
    hobby: [],
    lesson: 1,
    note: '',
    flag: false
  }
  // handlerUserNameChange = (event) => {
  //   this.setState({ userName: event.target.value })
  // }
  // handlerPasswordChange = (event) => {
  //   this.setState({ password: event.target.value })
  // }

  // handlerChange = (type, event) => {
  //   this.setState({ [type]: event.target.value })
  // }

  handlerChange = (event) => {
    console.log(event.target.name)
    this.setState({ [event.target.name]: event.target.value })
  }

  handlerHobbyChange = (event) => {
    const checked = event.target.checked
    const value = event.target.value
    const arr = this.state.hobby
    // checked 为真选中,加入数组,为假 删除
    if (checked) {
      arr.push(value)
    } else {
      const index = arr.findIndex(item => item === value)
      arr.splice(index, 1)
    }
    console.log(arr)
    this.setState({ hobby: arr })
  }

  handlerFlagChange = (event) => {
    this.setState({ flag: event.target.checked })
  }
  render() {
    return (
      <div>
        <div>
          {/* <input type="text" placeholder='用户名' value={ this.state.userName } onChange = { this.handlerUserNameChange }/> { this.state.userName } */}
          {/* <input type="text" placeholder='用户名' value={ this.state.userName } onChange = { this.handlerChange.bind(this, 'userName') }/> { this.state.userName } */}
          <input type="text" placeholder='用户名' name="userName" value={ this.state.userName } onChange = { this.handlerChange }/> { this.state.userName }
        </div>
        <div>
          {/* <input type="password" placeholder='密码' value={ this.state.password } onChange = { this.handlerPasswordChange }/> { this.state.password } */}
          {/* <input type="password" placeholder='密码' value={ this.state.password } onChange = { this.handlerChange.bind(this, 'password') }/> { this.state.password } */}
          <input type="password" placeholder='密码' name="password" value={ this.state.password } onChange = { this.handlerChange }/> { this.state.password }
        </div>
        <div>
          <input type="radio" value="男" name="sex" checked={ this.state.sex === '男'} onChange = { this.handlerChange }/>男
          <input type="radio" value="女" name="sex" checked={ this.state.sex === '女'} onChange = { this.handlerChange }/>女 --- { this.state.sex }
        </div>
        <div>
          <input type="checkbox" name="hobby" value="🏀" onChange={ this.handlerHobbyChange }/>🏀
          <input type="checkbox" name="hobby" value="⚽" onChange={ this.handlerHobbyChange }/>⚽
          <input type="checkbox" name="hobby" value="🏐" onChange={ this.handlerHobbyChange }/>🏐
          <input type="checkbox" name="hobby" value="🏓" onChange={ this.handlerHobbyChange }/>🏓 ---
          {
            this.state.hobby && this.state.hobby.map(item => {
              return <span key = { item }>{item}</span>
            })
          }
        </div>
        <div>
          <select name="lesson" value={this.state.lesson} onChange={ this.handlerChange }>
            <option value={1}>1阶段</option>
            <option value={2}>2阶段</option>
            <option value={3}>3阶段</option>
          </select> --- { this.state.lesson }
        </div>
        <div>
          <textarea name='note' value={ this.state.note } onChange = { this.handlerChange }></textarea>
        </div>
        <div>
          <input type="checkbox" checked = { this.state.flag } onChange = { this.handlerFlagChange } /> ***** 用户协议 -- { this.state.flag + ''}
        </div>
      </div>
    );
  }
}

export default App;

11.2 受控表单以及受控组件

在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

inputtextareaselect 受控组件: value 的属性受了 state 的控制

  • 使用了受控组件,一定要写 value 属性以及onChange事件

radio、‘checkbox’ 受控组件: checked 的属性受了state的控制

如果需要设置默认值,那么需要通过 defaultValue 以及defaultChecked设置

案例如上

十二、状态提升

在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”。

12.1 父子组件通信

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
import App from "./07_state_up/01_App-parent-child-value"; // 状态提升 多组件数据共享

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/07_state_up/01_App-parent-child-value.jsx

import React, { Component } from "react";

class Child1 extends Component {
  state = { count: 1 };
  render() {
    return (
      <>
        <h1>child1组件</h1>
        {this.state.count}
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          加1
        </button>
      </>
    );
  }
}
class Child2 extends Component {
  state = { count: 1 };
  render() {
    return (
      <>
        <h1>child2组件</h1>
        {this.state.count}
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          加1
        </button>
      </>
    );
  }
}
class App extends Component {
  render() {
    return (
      <div>
        <Child1></Child1>
        <hr></hr>
        <Child2></Child2>
      </div>
    );
  }
}

export default App;

我们发现 Child1 和 Child2 都是两个独立的个体,并没有实现数据共享

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
import App from "./07_state_up/02_App_state_up"; // 状态提升 多组件数据共享

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/07_state_up/02_App_state_up.jsx

import React, { Component } from "react";

class Child1 extends Component {
  // state = { count: 1 }
  render() {
    return (
      <>
        <h1>child1组件</h1>
        {/* { this.state.count } */}
        {this.props.count}
        {/* <button onClick={ () => { this.setState({count: this.state.count + 1} ) }}>加1</button> */}
        <button onClick={this.props.onClick}>加1</button>
      </>
    );
  }
}
class Child2 extends Component {
  // state = { count: 1 }
  render() {
    return (
      <>
        <h1>child2组件</h1>
        {/* { this.state.count } */}
        {this.props.count}
        {/* <button onClick={ () => { this.setState({count: this.state.count + 1} ) }}>加1</button> */}
        <button onClick={this.props.onClick}>加1</button>
      </>
    );
  }
}
class App extends Component {
  state = { count: 1 };
  add = () => {
    this.setState({ count: this.state.count + 1 });
  };
  render() {
    return (
      <div>
        <Child1 count={this.state.count} onClick={this.add}></Child1>
        <hr></hr>
        <Child2 count={this.state.count} onClick={this.add}></Child2>
      </div>
    );
  }
}

export default App;

12.2 状态提升解读

实现方式是 利用最近的共同的父级组件中,用props的方式传过去到两个子组件,props中传的是一个setState的方法,通过子组件触发props传过去的方法,进而调用父级组件的setState的方法,改变了父级组件的state,调用父级组件的add方法,进而同时改变了两个子级组件的数据

这是 两个有关连的同级组件的传值,因为react的单项数据流,所以不在两个组件中进行传值,而是提升到 最近的共同的父级组件中,改变父级的state,进而影响了两个子级组件的render

注意如果两个组件是同级组件(这两个组件的父组件是同一个)才考虑状态提升共享数据

十三、组合 vs 继承

React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。

13.1 理解组件化

组件化是 React 的核心思想

  • 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。

  • 任何的应用都会被抽象成一颗组件树。

组件化思想的应用

  • 有了组件化的思想,我们在之后的开发中就要充分的利用它。

  • 尽可能的将页面拆分成一个个小的、可复用的组件。

  • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

React 的组件相对于 Vue 更加的灵活和多样,按照不同的方式可以分成很多类 组件

  • 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);

vue 中有没有类组件和函数式组件?vue2 中有

<template></template>

<script>
export default {
  props: [],
  data() {
    return {};
  },
};
</script>
  • vue 中的函数式组件 - 无状态组件,所有的数据来源均来自父组件
<template functional>
  <div>{{props.msg}}</div>
</template>

  • vue 中的类组件 - 兼容 ts 时
<template>
  <div>hello vue</div>
</template>

class Home extends Vue {} // export default {}
  • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);

  • 根据组件的不同职责,可以分成:展示型组件(Presentational Component - 只做数据的展示,一般不需要写更多的业务逻辑-数据请求不出现在展示型组件-顶多发出请求的指令-具体的请求交给容器型组件)和容器型组件(Container Component - 负责给展示性组件提供数据以及处理展示型组件需要的具体的业务逻辑) - 状态管理器-更容易理解;

这些概念有很多重叠,但是他们最主要是关注数据逻辑和 UI 展示的分离:

  • 函数组件、无状态组件、展示型组件主要关注 UI 的展示;
  • 类组件、有状态组件、容器型组件主要关注数据逻辑;

13.2 使用组合而非继承实现 React 组件化

有些组件无法提前知晓它们子组件的具体内容,建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中。

参照5.3章节

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
import App from "./08_com/01_App_props_slot"; // 组合VS继承

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/08_com/01_App_props_slot.jsx

import React, { Component } from "react";

class Header extends Component {
  render() {
    return (
      <header>
        <ul>
          <li>{this.props.left}</li>
          <li>{this.props.default}</li>
          <li>{this.props.right}</li>
        </ul>
        {this.props.children}
      </header>
    );
  }
}

class Header1 extends Component {
  render() {
    return (
      <header>
        <ul>
          <li>{this.props.children[0]}</li>
          <li>{this.props.children[1]}</li>
          <li>{this.props.children[2]}</li>
        </ul>
      </header>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div>
        <Header
          left={<span>logo</span>}
          default={<span>搜索框</span>}
          right={<span>登录</span>}
        >
          1111111111
        </Header>

        <Header
          left={<span>返回</span>}
          default={<span>标题</span>}
          right={<span>更多</span>}
        ></Header>
        <hr />
        <Header1>
          <span>logo</span>
          <span>搜索框</span>
          <span>登录</span>
        </Header1>
        <Header1>
          <span>返回</span>
          <span>标题</span>
          <span>更多</span>
        </Header1>
      </div>
    );
  }
}

export default App;

像 App 组件中的leftdefault以及 right 的 属性对应的之类的React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起 vue 中“插槽”(slot)的概念,但在 React 中没有“插槽”这一概念的限制,你可以将任何东西作为 props 进行传递。

13.3 封装 Modal 弹窗

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
import App from "./08_com/02_App_modal"; // 组合VS继承

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/08_com/02_App_modal.jsx

import React, { Component } from "react";
class Modal extends Component {
  render() {
    return (
      <div
        style={{
          position: "fixed",
          top: 0,
          left: 0,
          bottom: 0,
          right: 0,
          backgroundColor: "rgba(0,0,0, 0.4)",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <div
          style={{
            width: "50%",
            minHeight: "300px",
            backgroundColor: "#fff",
          }}
        >
          这里是一个模态框
          <button onClick={this.props.onClick}>关闭</button>
        </div>
      </div>
    );
  }
}
class App extends Component {
  state = { show: false };
  close = () => {
    this.setState({
      show: false,
    });
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ show: true })}>
          打开模态框
        </button>
        {this.state.show ? <Modal onClick={this.close} /> : null}
      </div>
    );
  }
}

export default App;

审查元素发现 Modal 组件是渲染在原来的组件的位置的,如果想要让它渲染到不同的位置怎么办呢?

13.4 ReactDOM.createPortal()

普通的组件,子组件的元素将挂载到父组件的 DOM 节点中。

有时需要将元素渲染到 DOM 中的不同位置上去,这是就用到的 portal 的方法。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
import App from "./08_com/03_App_portal"; // 封装模态框 portal

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/08_com/03_App_portal.jsx

import React, { Component } from "react";
import ReactDOM from "react-dom"; // 这里不要使用 'react-dom/client' 引入
class Modal extends Component {
  modalRef = React.createRef();
  render() {
    return ReactDOM.createPortal(
      // <div id='modal' ref="modal" style={ {
      <div
        id="modal"
        ref={this.modalRef}
        style={{
          position: "fixed",
          top: 0,
          left: 0,
          bottom: 0,
          right: 0,
          backgroundColor: "rgba(0,0,0, 0.4)",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
        // onClick = { this.props.onClick }
        onClick={(event) => {
          console.log(event.target);
          console.log(document.getElementById("modal"));
          console.log(this.modalRef);
          // if (event.target === document.getElementById('modal')) { // ? id ?
          // if (event.target === this.refs.modal) { // ? ref 删除线?
          if (event.target === this.modalRef.current) {
            // ref的使用
            // 如何判断当前点击的是自身而不是子元素
            this.props.onClick();
          }
        }}
      >
        <div
          style={{
            width: "50%",
            minHeight: "300px",
            backgroundColor: "#fff",
          }}
          // onClick = { event => {
          //   event.stopPropagation()
          // } }
        >
          这里是一个模态框
          <button onClick={this.props.onClick}>关闭</button>
        </div>
      </div>,
      document.getElementsByTagName("body")[0]
    );
  }
}
class App extends Component {
  state = { show: false };
  close = () => {
    this.setState({
      show: false,
    });
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ show: true })}>
          打开模态框
        </button>
        {this.state.show ? <Modal onClick={this.close} /> : null}
      </div>
    );
  }
}

export default App;

一个 portal 的典型用例是当父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:

十四、上下文 Context

14.1 理解上下文、作用及其特点

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

14.2 使用 React.createContext()

14.2.1 逐层传递数据

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
import App from "./09_context/01_App_next_value"; // 逐层传递数据

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/01_App_next_value.jsx

import React, { Component } from "react";
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>
        <div>{this.props.val}</div>
      </div>
    );
  }
}
const Second = (props) => {
  return (
    <div>
      <h2>second</h2>
      <Third val={props.val} />
    </div>
  );
};
const First = (props) => {
  return (
    <div>
      <h1>first</h1>
      <Second val={props.val} />
    </div>
  );
};
class App extends Component {
  render() {
    return (
      <div>
        <First val="传家宝" />
      </div>
    );
  }
}

export default App;

14.2.2 使用 Context 传值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
import App from "./09_context/02_App_context"; // 上下文对象Context传值

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/02_App_context.jsx

import React, { Component } from "react";
// 1.创建上下文对象 - 首字母大写,大驼峰式命名
const LangContext = React.createContext();

// 3.如果后代组件是类组件,可以有三种方式接收祖先组件的传递的值
//   3.1 定义好组件后 通过  组件.contextType = 上下文对象,这样就可以在组件中通过 this.context 获取到 组件组件的数据
// class Third extends Component {
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         { this.context }
//       </div>
//     )
//   }
// }
// Third.contextType = LangContext

// 3.2 使用类的静态属性contextType 等于上下文对象,然后通过 this.context 获取值
// class Third extends Component {
//   static contextType = LangContext
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         { this.context }
//       </div>
//     )
//   }
// }

// 3.3 使用上下文对象的 Consumer 组件来获取值,需要组件内容写一个回调函数,回调函数参数即为祖先组件传递的值
// 发现第三种写法看似麻烦,但是如果祖先组件给后代组件通过多个上下文对象传递很多个数据时就很有用了,而前两种方法显得无能为力
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>
        <LangContext.Consumer>
          {(val) => {
            return <div>语言为: {val}</div>;
          }}
        </LangContext.Consumer>
      </div>
    );
  }
}

const Second = () => {
  return (
    <div>
      <h2>Second</h2>
      <Third />
    </div>
  );
};

const First = () => {
  return (
    <div>
      <h1>First</h1>
      <Second />
    </div>
  );
};

class App extends Component {
  state = {
    lang: "zh",
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ lang: "zh" })}>中文</button>
        <button onClick={() => this.setState({ lang: "en" })}>英文</button>
        {/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}
        <LangContext.Provider value={this.state.lang}>
          <First />
        </LangContext.Provider>
      </div>
    );
  }
}

export default App;

14.2.3 传递多个值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
import App from "./09_context/03_App_context_multiple_value"; // 上下文对象Context传值 多个上下文对象传递数据

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/03_App_context_multiple_value.jsx

import React, { Component } from "react";
// 1.创建上下文对象 - 首字母大写,大驼峰式命名
const LangContext = React.createContext();
const ColorContext = React.createContext();

// 3.如果后代组件是类组件,可以有三种方式接收祖先组件的传递的值
//   3.1 定义好组件后 通过  组件.contextType = 上下文对象,这样就可以在组件中通过 this.context 获取到 组件组件的数据
//   3.2 使用类的静态属性contextType 等于上下文对象,然后通过 this.context 获取值
//   3.3 使用上下文对象的 Consumer 组件来获取值,需要组件内容写一个回调函数,回调函数参数即为祖先组件传递的值
// 发现第三种写法看似麻烦,但是如果祖先组件给后代组件通过多个上下文对象传递很多个数据时就很有用了,而前两种方法显得无能为力
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>

        <ColorContext.Consumer>
          {(color) => {
            return (
              <>
                <LangContext.Consumer>
                  {(val) => {
                    return <div style={{ color: color }}>语言为: {val}</div>;
                  }}
                </LangContext.Consumer>
              </>
            );
          }}
        </ColorContext.Consumer>
      </div>
    );
  }
}

const Second = () => {
  return (
    <div>
      <h2>Second</h2>
      <Third />
    </div>
  );
};

const First = () => {
  return (
    <div>
      <h1>First</h1>
      <Second />
    </div>
  );
};

class App extends Component {
  state = {
    lang: "zh",
    color: "#f66",
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ lang: "zh" })}>中文</button>
        <button onClick={() => this.setState({ lang: "en" })}>英文</button>
        <input
          type="color"
          value={this.state.color}
          onChange={(event) => {
            this.setState({
              color: event.target.value,
            });
          }}
        />
        {/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}
        <LangContext.Provider value={this.state.lang}>
          <ColorContext.Provider value={this.state.color}>
            <First />
          </ColorContext.Provider>
        </LangContext.Provider>
      </div>
    );
  }
}

export default App;

上述案例,还可以通过一个上下文对象传递多个值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
import App from "./09_context/04_App_one_context_multiple_value"; // 上下文对象Context传值 1个上下文对象传递多个数据

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/04_App_one_context_multiple_value.jsx

import React, { Component } from "react";
// 1.创建上下文对象 - 首字母大写,大驼峰式命名
const MyContext = React.createContext();

// 3.如果后代组件是类组件,可以有三种方式接收祖先组件的传递的值
//   3.1 定义好组件后 通过  组件.contextType = 上下文对象,这样就可以在组件中通过 this.context 获取到 组件组件的数据
// class Third extends Component {
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         <div style = {{ color: this.context.color }}>1语言为: { this.context.lang }</div>
//       </div>
//     )
//   }
// }
// Third.contextType = MyContext

// 3.2 使用类的静态属性contextType 等于上下文对象,然后通过 this.context 获取值
// class Third extends Component {
//   static contextType = MyContext
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         <div style = {{ color: this.context.color }}>2语言为: { this.context.lang }</div>
//       </div>
//     )
//   }
// }

// 3.3 使用上下文对象的 Consumer 组件来获取值,需要组件内容写一个回调函数,回调函数参数即为祖先组件传递的值
// 发现第三种写法看似麻烦,但是如果祖先组件给后代组件通过多个上下文对象传递很多个数据时就很有用了,而前两种方法显得无能为力
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>
        <MyContext.Consumer>
          {(val) => {
            return <div style={{ color: val.color }}>3语言为: {val.lang}</div>;
          }}
        </MyContext.Consumer>
      </div>
    );
  }
}

const Second = () => {
  return (
    <div>
      <h2>Second</h2>
      <Third />
    </div>
  );
};

const First = () => {
  return (
    <div>
      <h1>First</h1>
      <Second />
    </div>
  );
};

class App extends Component {
  state = {
    lang: "zh",
    color: "#f66",
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ lang: "zh" })}>中文</button>
        <button onClick={() => this.setState({ lang: "en" })}>英文</button>
        <input
          type="color"
          value={this.state.color}
          onChange={(event) => {
            this.setState({
              color: event.target.value,
            });
          }}
        />
        {/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}
        <MyContext.Provider
          value={{
            lang: this.state.lang,
            color: this.state.color,
          }}
        >
          <First />
        </MyContext.Provider>
      </div>
    );
  }
}

export default App;

如果遇到函数式组件如何获取 Context 的值

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
import App from "./09_context/05_App_function_context_value"; // 上下文对象Context传值 函数式组件获取值

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/05_App_function_context_value.jsx

import React, { Component } from "react";
import { useContext } from "react";
// 1.创建上下文对象 - 首字母大写,大驼峰式命名
const LangContext = React.createContext();

// 3.如果后代组件是类组件,可以有三种方式接收祖先组件的传递的值
//   3.1 定义好组件后 通过  组件.contextType = 上下文对象,这样就可以在组件中通过 this.context 获取到 组件组件的数据
// class Third extends Component {
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         { this.context }
//       </div>
//     )
//   }
// }
// Third.contextType = LangContext

// 3.2 使用类的静态属性contextType 等于上下文对象,然后通过 this.context 获取值
// class Third extends Component {
//   static contextType = LangContext
//   render () {
//     return (
//       <div>
//         <h3>third</h3>
//         { this.context }
//       </div>
//     )
//   }
// }

// 3.3 使用上下文对象的 Consumer 组件来获取值,需要组件内容写一个回调函数,回调函数参数即为祖先组件传递的值
// 发现第三种写法看似麻烦,但是如果祖先组件给后代组件通过多个上下文对象传递很多个数据时就很有用了,而前两种方法显得无能为力
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>
        <LangContext.Consumer>
          {(val) => {
            return <div>语言为: {val}</div>;
          }}
        </LangContext.Consumer>
      </div>
    );
  }
}
// 4.如果后代组件是函数式组件,可以有两种写法
// 4.1 使用上下文对象.Consumer完成传值
const Second = () => {
  return (
    <div>
      <h2>Second</h2>
      <LangContext.Consumer>
        {(val) => {
          return <div>Second - 语言为: {val}</div>;
        }}
      </LangContext.Consumer>
      <Third />
    </div>
  );
};

// 4.2 可以使用 useContext hooks 来获取值
const First = () => {
  const lang = useContext(LangContext);
  // const color = useContext(ColorContext)
  return (
    <div>
      <h1>First</h1>
      <div>First - 语言为: {lang}</div>
      <Second />
    </div>
  );
};

class App extends Component {
  state = {
    lang: "zh",
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ lang: "zh" })}>中文</button>
        <button onClick={() => this.setState({ lang: "en" })}>英文</button>
        {/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}
        <LangContext.Provider value={this.state.lang}>
          <First />
        </LangContext.Provider>
      </div>
    );
  }
}

export default App;

如果浏览器安装过 react 的开发者工具,打开之后发现上述代码,都显示为 Context.ProviderContext.Consumer,不好区分

加入 上下文对象的 displayName

14.2.4 displayName

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
import App from "./09_context/06_App_context_displayName"; // 上下文对象Context传值 开发者工具显示Context名称

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09_context/06_App_context_displayName.jsx

import React, { Component } from "react";
// 1.创建上下文对象 - 首字母大写,大驼峰式命名
const LangContext = React.createContext();
const ColorContext = React.createContext();

LangContext.displayName = "LangContext";
ColorContext.displayName = "ColorContext";

// 3.如果后代组件是类组件,可以有三种方式接收祖先组件的传递的值
//   3.1 定义好组件后 通过  组件.contextType = 上下文对象,这样就可以在组件中通过 this.context 获取到 组件组件的数据
//   3.2 使用类的静态属性contextType 等于上下文对象,然后通过 this.context 获取值
//   3.3 使用上下文对象的 Consumer 组件来获取值,需要组件内容写一个回调函数,回调函数参数即为祖先组件传递的值
// 发现第三种写法看似麻烦,但是如果祖先组件给后代组件通过多个上下文对象传递很多个数据时就很有用了,而前两种方法显得无能为力
class Third extends Component {
  render() {
    return (
      <div>
        <h3>third</h3>

        <ColorContext.Consumer>
          {(color) => {
            return (
              <>
                <LangContext.Consumer>
                  {(val) => {
                    return <div style={{ color: color }}>语言为: {val}</div>;
                  }}
                </LangContext.Consumer>
              </>
            );
          }}
        </ColorContext.Consumer>
      </div>
    );
  }
}

const Second = () => {
  return (
    <div>
      <h2>Second</h2>
      <Third />
    </div>
  );
};

const First = () => {
  return (
    <div>
      <h1>First</h1>
      <Second />
    </div>
  );
};

class App extends Component {
  state = {
    lang: "zh",
    color: "#f66",
  };
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ lang: "zh" })}>中文</button>
        <button onClick={() => this.setState({ lang: "en" })}>英文</button>
        <input
          type="color"
          value={this.state.color}
          onChange={(event) => {
            this.setState({
              color: event.target.value,
            });
          }}
        />
        {/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}
        <LangContext.Provider value={this.state.lang}>
          <ColorContext.Provider value={this.state.color}>
            <First />
          </ColorContext.Provider>
        </LangContext.Provider>
      </div>
    );
  }
}

export default App;

14.3 常见应用场景解读

  • 共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
  • 配合react hooks 中的useReducer可以实现轻量的 redux

十五、高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。

HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

15.1 理解高阶组件、作用及其特点

一个高阶组件只是一个包装了另外一个 React 组件的 函数。
这种形式通常实现为一个函数,本质上是一个类工厂。

.实现了对原有组件的增强和优化

可以对原有组件中的 state, props 和逻辑执行增删改操作, 一般用于代码重用和组件增强优化

15.2 高阶组件语法详解

我们想要我们的组件通过自动注入一个版权信息

15.2.1 组件嵌套

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
import App from "./10_hoc/01_App-more-use"; // 高阶组件 多个组件引入同一个组件

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/10_hoc/01_App-more-use.jsx

import React, { Component } from "react";

class Footer extends Component {
  render() {
    return <footer>Copyright © 2022 Meta Platforms, Inc.</footer>;
  }
}
class Page1 extends Component {
  state = { userName: "" };
  componentDidMount() {
    this.setState({ userName: localStorage.getItem("userName") });
  }
  render() {
    return (
      <div>
        <h1>page1</h1>
        <Footer />
      </div>
    );
  }
}
class Page2 extends Component {
  state = { userName: "" };
  componentDidMount() {
    this.setState({ userName: localStorage.getItem("userName") });
  }
  render() {
    return (
      <div>
        <h1>page2</h1>
        <Footer />
      </div>
    );
  }
}
class Page3 extends Component {
  state = { userName: "" };
  componentDidMount() {
    this.setState({ userName: localStorage.getItem("userName") });
  }
  render() {
    return (
      <div>
        <h1>page3</h1>
        <Footer />
      </div>
    );
  }
}
class App extends Component {
  render() {
    return (
      <div>
        <Page1 />
        <hr />
        <Page2 />
        <hr />
        <Page3 />
      </div>
    );
  }
}

export default App;

通过Footer组件可以复用 jsx 代码,但是其余的业务逻辑代码显得无能为力,可以通过高阶组件来实现

15.2.2 高阶组件

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
import App from "./10_hoc/02_App-more-use-hoc"; // 高阶组件 高阶组件

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/10_hoc/02_App-more-use-hoc.jsx

import React, { Component } from "react";
localStorage.setItem("userName", "吴大勋");
class Footer extends Component {
  render() {
    return <footer>Copyright © 2022 Meta Platforms, Inc.</footer>;
  }
}

// 高阶组件的本质是一个函数,将一个组件作为参数,返回一个新的组件
const withFooter = (Com) => {
  return class extends Component {
    // 此处可以省略新组件的名称
    // 写公共部分的业务逻辑
    state = { userName: "" };
    componentDidMount() {
      console.log("0000");
      this.setState({ userName: localStorage.getItem("userName") });
    }
    render() {
      // 实际上此操作相当于完成父组件给子组件传值
      return (
        <>
          <Com userName={this.state.userName} />
          <Footer />
        </>
      );
    }
  };
};

class Page1 extends Component {
  // state = { userName: ''}
  // componentDidMount () {
  //   this.setState({ userName: localStorage.getItem('userName') })
  // }

  componentDidMount() {
    console.log("1111");
  }
  render() {
    return (
      <div>
        {/* <h1>page1 - { this.state.userName }</h1> */}
        <h1>page1 - {this.props.userName}</h1>
        {/* <Footer /> */}
      </div>
    );
  }
}
Page1 = withFooter(Page1);

class Page2 extends Component {
  // state = { userName: ''}
  // componentDidMount () {
  //   this.setState({ userName: localStorage.getItem('userName') })
  // }
  render() {
    return (
      <div>
        {/* <h1>page2 - { this.state.userName }</h1> */}
        <h1>page2 - {this.props.userName}</h1>
        {/* <Footer /> */}
      </div>
    );
  }
}
Page2 = withFooter(Page2);

class Page3 extends Component {
  // state = { userName: ''}
  // componentDidMount () {
  //   this.setState({ userName: localStorage.getItem('userName') })
  // }
  render() {
    return (
      <div>
        {/* <h1>page3 - { this.state.userName }</h1> */}
        <h1>page3 - {this.props.userName}</h1>
        {/* <Footer /> */}
      </div>
    );
  }
}
Page3 = withFooter(Page3);

class App extends Component {
  render() {
    return (
      <div>
        <Page1 />
        <hr />
        <Page2 />
        <hr />
        <Page3 />
      </div>
    );
  }
}

export default App;

先执行了 组件的生命周期,后执行了高阶组件的生命周期

但是以后再复用组件的业务时,可以选用函数式组件的自定义 hooks

15.3 常见应用场景解读

1.需要代码重用时, react 如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复

2.需要组件增强优化时, 比如我们在项目中使用的组件有些不是自己写的, 而是从网上撸下来的,但是第三方写的组件可能比较复杂, 有时不能完全满足需求, 但第三方组件不易修改, 此时也可以用高阶组件,在不修改原始组件的前提下, 对组件添加满足实际开发需求的功能

3.可以对原有组件中的 state, props 和逻辑执行增删改操作, 一般用于代码重用和组件增强优化

4.也可以用来替换 mixins 混入

父组件和高阶组件有什么区别?

  • 首先从逻辑的执行流程上来看,高阶组件确实和父组件比较相像
  • 但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;
  • 父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与 DOM 直接相关的,那么这部分逻辑适合放到父组件中实现;
  • 如果逻辑是与 DOM 不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。

十六、ref

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

16.1 ref 访问 DOM

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
import App from "./11_ref/01_App_ref"; // ref的使用以及严格模式

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(
  // 开启React的严格模式
  // https://react.docschina.org/docs/strict-mode.html
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
// react 18 为严格模式引入了一个全新的仅用于开发环境的检查操作。
// 每当第一次安装组件时,这个新的检查将自动卸载并重新安装每个组件,并在第二次挂载时恢复之前的 state。
// 慎用严格模式

src/11_ref/01_App_ref.jsx

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 1,
  };
  componentDidMount() {
    console.log(this.state.count);
    // this.setState({ // Object.assign({})
    //   count: this.state.count + 1
    // })
    this.setState((state) => {
      // state 拿到的最新的值
      return {
        count: state.count + 1,
      };
    });
    console.log("id", document.getElementById("btn1"));
    console.log("refs", this.refs.btn2); // refs已经被废弃,但是没有移除,严格模式下会报警告信息
  }
  // componentWillMount () {} // 警告:被重命名
  // UNSAFE_componentWillMount () {} // 严格模式下识别不安全的生命周期
  render() {
    return (
      <div>
        {/* 如果开启严格模式并且使用函数形式修改状态,输出结果为3 */}
        {this.state.count}
        <button id="btn1">按钮1-id</button>
        <button ref="btn2">按钮2-refs - 废弃</button>
      </div>
    );
  }
}

export default App;

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
import App from "./11_ref/02_App_ref"; // ref的使用以及严格模式

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/11_ref/02_App_ref.jsx

import React, { Component } from "react";

class Com extends Component {
  state = {
    name: "类组件",
  };
  testFn = () => {
    console.log(this.state.name + "!!!!!!!");
  };
  render() {
    return <div>类组件ref的使用</div>;
  }
}

const FunCom = () => {
  return <div>函数式组件ref</div>;
};
class App extends Component {
  btn3Ref = React.createRef(); // 创建一个唯一的 ref
  comRef = React.createRef();
  funRef = React.createRef();
  componentDidMount() {
    console.log("id", document.getElementById("btn1"));
    console.log("refs", this.refs.btn2); // refs已经被废弃,但是没有移除,严格模式下会报警告信息
    console.log("domCreateRef", this.btn3Ref.current);
    console.log("comCreateRef", this.comRef.current);
    console.log("FuncomCreateRef", this.funRef.current); // null
  }
  render() {
    return (
      <div>
        <button id="btn1">按钮1-id</button>
        <button ref="btn2">按钮2-refs - 废弃</button>
        <button ref={this.btn3Ref}>按钮2 - createRef </button>
        {/* 父组件可以直接通过ref 获取 子组件的实例 */}
        <Com ref={this.comRef}></Com>
        {/* 函数式组件没有实例,获取不到子组件实例 */}
        <FunCom ref={this.funRef}></FunCom>
      </div>
    );
  }
}

export default App;

如果在上述案例中,在FunCom组件中上使用 ref,发现报了警告信息

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

其原因是,函数式组件使用 ref,必须使用 React.forwardRef()方法二次包装

16.2 详解 ref 转发

Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
import App from "./11_ref/03_App_ref_forward"; // 转发ref

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/11_ref/03_App_ref_forward.jsx

import React, { Component } from "react";

class Com extends Component {
  state = {
    name: "类组件",
  };
  testFn = () => {
    console.log(this.state.name + "!!!!!!!");
  };
  render() {
    return <div>类组件ref的使用</div>;
  }
}

const FunCom = React.forwardRef((props, ref) => {
  return <div ref={ref}>函数式组件ref</div>;
});
const Form = React.forwardRef((props, ref) => {
  return (
    <form ref={ref}>
      <input type="text" name="userName" />
      <input type="text" name="password" />
      <input type="submit" value="提交" />
      {/* <input type="reset" value="重置" /> */}
    </form>
  );
});
class App extends Component {
  btn3Ref = React.createRef(); // 创建一个唯一的 ref
  comRef = React.createRef();
  funRef = React.createRef();
  formRef = React.createRef();
  componentDidMount() {
    console.log("id", document.getElementById("btn1"));
    console.log("refs", this.refs.btn2); // refs已经被废弃,但是没有移除,严格模式下会报警告信息
    console.log("domCreateRef", this.btn3Ref.current);
    console.log("comCreateRef", this.comRef.current);
    console.log("FuncomCreateRef", this.funRef.current); //

    // 直接给form表单设置初始值
    console.log(this.formRef.current.children); //
    this.setFieldValue({
      userName: "吴大勋",
      password: "123456",
    });
  }
  setFieldValue = (obj) => {
    this.formRef.current.children[0].value = obj.userName;
    this.formRef.current.children[1].value = obj.password;
  };
  render() {
    return (
      <div>
        <button id="btn1">按钮1-id</button>
        <button ref="btn2">按钮2-refs - 废弃</button>
        <button ref={this.btn3Ref}>按钮2 - createRef </button>
        {/* 父组件可以直接通过ref 获取 子组件的实例 */}
        <Com ref={this.comRef}></Com>
        {/* 函数式组件没有实例,获取不到子组件实例 */}
        <FunCom ref={this.funRef}></FunCom>
        <Form ref={this.formRef}></Form>
        <button
          onClick={() => {
            console.log(this.formRef.current);
            this.formRef.current.reset();
          }}
        >
          重置表单
        </button>
      </div>
    );
  }
}

export default App;

16.3 使用 ref 注意事项

  • ref 属性用于 HTML 元素时,使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。如果非要使用,实际上是转发 ref(父组件中获取到了子组件的某个 DOM)

    • https://react.docschina.org/docs/hooks-reference.html#usedebugvalue)

十七、hooks

17.1 为什么使用 hooks

React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。如果你使用过 React 一段时间,你也许会熟悉一些解决此类问题的方案,比如 render props 和 高阶组件。

Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。

react 18 版本以前 hooks

  • 基础 Hook
    • useState
    • useEffect
    • useContext
  • 额外的 Hook
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

17.2 常见的 hooks

17.2.1 useState

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
import App from "./12_hooks/01_App_hooks_useState"; // hooks useState

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/01_App_hooks_useState.jsx

// rsc
import React from "react";
import { useState } from "react";

// 函数式组件内不要使用this
// 如果想要 this 指向函数式组件,本身这个想法就是错误的
const App = () => {
  // 定义了一个初始化状态 为count
  // 定义了一个修改初始化状态的函数 为 setCount
  // 使用 useState 定义了初始化装 count 的值为 1
  const [count, setCount] = useState(1);

  const [name, setName] = useState("吴");

  function add100() {
    setCount(count + 100);
  }

  const add1000 = () => {
    setCount(count + 1000);
  };

  const add10000 = () => {
    setCount((prevCount) => {
      // 函数写法
      console.log(prevCount);
      return prevCount + 10000;
    });
  };
  return (
    <div>
      {count}
      <button
        onClick={() => {
          // 修改函数内部的值为 运算之后的结果
          setCount(count + 1);
        }}
      >
        加1
      </button>
      <button
        onClick={function () {
          // 修改函数内部的值为 运算之后的结果
          setCount(count + 10);
        }}
      >
        加10
      </button>

      <button onClick={add100}>加100</button>
      <button onClick={add1000}>加1000</button>
      <button onClick={add10000}>加10000</button>
      {name}
      <button
        onClick={() => {
          setName("吴大勋");
        }}
      >
        修改name
      </button>
    </div>
  );
};

export default App;

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
import App from "./12_hooks/02_App_hooks_useState_obj"; // hooks useState  object

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/02_App_hooks_useState_obj.jsx

import React from "react";
import { useState } from "react";

const App = () => {
  const [obj, setObj] = useState({
    w: 100,
    h: 100,
    x: 0,
    y: 0,
  });

  const mouseMove = (event) => {
    // 鼠标移动,发现 w 和 h 的数据丢失, { x: 1, y: 1} 代替了原来的对象 obj
    // setObj({
    //   x: event.clientX,
    //   y: event.clientY
    // })

    // setObj({ // 合并新数据到原来的 obj
    //   ...obj,
    //   x: event.clientX,
    //   y: event.clientY
    // })

    setObj((prevObj) => {
      // return {
      //   w: prevObj.w,
      //   h: prevObj.h,
      //   x: event.clientX,
      //   y: event.clientY
      // }
      return Object.assign(
        {},
        prevObj,
        { x: event.clientX },
        { y: event.clientY }
      );
    });
  };

  return (
    <div style={{ width: "100vw", height: "100vh" }} onMouseMove={mouseMove}>
      <div>
        元素的宽为: {obj.w},元素的高为: {obj.h}
      </div>
      <div>
        元素的坐标点为: ({obj.x},{obj.y})
      </div>
    </div>
  );
};

export default App;

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
import App from "./12_hooks/03_App_hooks_useState_state"; // hooks useState  拆分对象状态

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/03_App_hooks_useState_state.jsx

import React from "react";
import { useState } from "react";

const App = () => {
  // 如果不需要修改状态,那么可以不写解构的第二个值
  const [box] = useState({ w: 100, h: 100 });
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const mouseMove = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY,
    });
  };

  return (
    <div style={{ width: "100vw", height: "100vh" }} onMouseMove={mouseMove}>
      <div>
        元素的宽为: {box.w},元素的高为: {box.h}
      </div>
      <div>
        元素的坐标点为: ({position.x},{position.y})
      </div>
    </div>
  );
};

export default App;
  • 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。

  • **只在 React 函数中调用 Hook,**不要在普通的 JavaScript 函数中调用 Hook。你可以:

    • 在 React 的函数组件中调用 Hook
    • 在自定义 Hook 中调用其他 Hook
  • 如果遇到需要以对象的形式定义状态时,根据需求划分对象,因为修改状态使用的是替换

17.2.2 useEffect

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
import App from "./12_hooks/04_App_hooks_useEffect"; // hooks useEffect

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App root={root} />);

src/12_hooks/04_App_hooks_useEffect.jsx

import React, { useState, useEffect } from "react";

const App = (props) => {
  const [proList, setProList] = useState([]);

  const [count, setCount] = useState(1);
  // 数据请求 --- 函数式组件没有 生命周期钩子函数
  // useEffect 的第一个参数是一个回调函数 类似于类组件中的 componentDidMount 以及 componentDidUpdate
  // useEffect 内的代码会自动执行
  // useEffect 如果没有添加第二个依赖的参数,那么只要内部修改果状态,此部分代码就会一直执行
  // useEffect(() => {
  //   fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => {
  //     console.log(res.data) // 第一次打印 - componentDidMount
  //     setProList(res.data) // 发现上一句话一直在打印 - componentDidUpdate 且没有条件限制
  //   })
  // })

  // 可以给useEffect添加第二个参数进行控制内部代码执行的次数
  // useEffect(() => {
  //   fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => {
  //     console.log(res.data)
  //     setProList(res.data)
  //   })
  // }, []) // useEffect 第二个参数为空数组,代表useEffect 内部只会执行类似 componentDidMount 的操作

  // 依赖值发生改变,再次触发代码执行,类似于componentDidUpdate中特定条件下请求数据
  useEffect(() => {
    fetch("http://121.89.205.189:3001/api/pro/list")
      .then((res) => res.json())
      .then((res) => {
        console.log(res.data);
        setProList(res.data);
      });
  }, [count]); // 第二个参数写入值,代表只有当count的值发生改变时,才会再次触发useEffect,再次执行

  // 如何模拟 组件的销毁呢?
  // 在useEffect内部的回调函数中 返回一个函数
  useEffect(() => {
    // 为防止内存泄漏,清除函数会在组件卸载前执行。
    // 另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除。
    return () => {
      // 类似于类组件中的componentWillUnmount
      // 取消订阅的操作,销毁定时器,计时器等操作
      console.log("组件销毁了");
    };
  }, [count]);
  return (
    <div>
      <button
        onClick={() => {
          setCount(count + 1);
          if (count === 5) {
            props.root.unmount();
          }
        }}
      >
        加1
      </button>{" "}
      {count}
      <ul>
        {proList &&
          proList.map((item) => <li key={item.proid}> {item.proname} </li>)}
      </ul>
    </div>
  );
};

export default App;
  1. useEffect() 是个副作用函数。
  2. useEffect() 函数在每次组件重新渲染时,可再次被调用。
  3. 在开发环境中,开启了 React.StrictMode 模式,组件开始时被渲染两次。
  4. useEffect() 通过返回函数来清理副作用。
  5. useEffect() 通过传递第二个参数数组来提高渲染性能,或者说实现 watch 效果。

17.2.3 useRef

利用 useRef 就可以绕过 Capture Value 的特性。可以认为 ref 在所有 Render 过程中保持着唯一引用,因此所有对 ref 的赋值或取值,拿到的都只有一个最终状态,而不会在每个 Render 间存在隔离。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
import App from "./12_hooks/05_App_hooks_useRef"; // hooks useRef

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/05_App_hooks_useRef.jsx

import React from "react";
import { useEffect } from "react";
import { useRef } from "react";

const FunCom = React.forwardRef((props, ref) => {
  return <div ref={ref}>函数式组件</div>;
});

class Com extends React.Component {
  state = { name: "类组件" };
  testFn = () => {
    console.log(this.state.name + "!!");
  };
  render() {
    return <div>类组件</div>;
  }
}

const App = () => {
  const btnRef = useRef(); // 类组件中  React.createRef()
  const comRef = useRef();
  const funRef = useRef();
  useEffect(() => {
    console.log(btnRef.current);
    console.log(comRef.current);
    console.log(funRef.current);
  }, []);

  return (
    <div>
      <button ref={btnRef}>按钮ref</button>
      <Com ref={comRef} />
      <FunCom ref={funRef} />
    </div>
  );
};

export default App;

17.2.4 useReducer

useReducer 践行了 Flux/Redux 思想。使用步骤:

1、创建初始值 initialState

2、创建所有操作 reducer(state, action);

3、传给 userReducer,得到读和写 API

4、调用写 ({type: ‘操作类型’})

总的来说,useReducer 是 useState 的复杂版。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
import App from "./12_hooks/06_App_hooks_useReducer"; // hooks useReducer

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/06_App_hooks_useReducer.jsx

import React, { useEffect } from "react";
import { useReducer } from "react";
// 1.创建初始化状态
const initialState = {
  bannerList: [],
  proList: [],
};

// 2.创建所有操作 reducer(state, action)
const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_BANNER_LIST":
      // 返回一个新的状态 -- 当触发修改轮播图数据时,得到一个新的数据
      // 如何保证得到一份新的数据  -- 推荐使用 对象的合并
      // return { ...state, bannerList: action.payload }
      return Object.assign({}, state, { bannerList: action.payload });
    case "CHANGE_PRO_LIST":
      return { ...state, proList: action.payload };
    default:
      return state;
  }
};

const App = () => {
  // 3.传给 userReducer,得到读和写API
  // state代表所有的数据
  // dispatch可以触发reducer的改变
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    fetch("http://121.89.205.189:3001/api/banner/list")
      .then((res) => res.json())
      .then((res) => {
        dispatch({
          type: "CHANGE_BANNER_LIST",
          payload: res.data,
        });
      });
    fetch("http://121.89.205.189:3001/api/pro/list")
      .then((res) => res.json())
      .then((res) => {
        dispatch({
          type: "CHANGE_PRO_LIST",
          payload: res.data,
        });
      });
  }, []);
  return (
    <div>
      {state.bannerList &&
        state.bannerList.map((item) => (
          <img
            key={item.bannerid}
            alt={item.alt}
            src={item.img}
            style={{ height: 100 }}
          />
        ))}

      <ul>
        {state.proList &&
          state.proList.map((item) => (
            <li key={item.proid}> {item.proname} </li>
          ))}
      </ul>
    </div>
  );
};

export default App;

如果遇到多个组件需要共享状态时,单纯 useReducer 就显得无能为力

17.2.5 useContext

1、使用 C = createContext(initial) 创建上下文

2、使用 <C.Provider> 圈定作用域

3、在作用域内使用 useContext©来使用上下文

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
import App from "./12_hooks/07_App_hooks_useContext"; // hooks useContext

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/07_App_hooks_useContext.jsx

import React from "react";
import { useContext } from "react";
const LangContext = React.createContext();
const ColorContext = React.createContext();
const Second = () => {
  const lang = useContext(LangContext);
  const color = useContext(ColorContext);
  return (
    <>
      <h1>Second</h1>
      {lang} - {color}
    </>
  );
};

const First = () => {
  return (
    <>
      <h1>first</h1>
      <Second />
    </>
  );
};
const App = () => {
  return (
    <div>
      <LangContext.Provider value="中文">
        <ColorContext.Provider value="red">
          <First />
        </ColorContext.Provider>
      </LangContext.Provider>
    </div>
  );
};

export default App;

使用 useReducer 和 useContext 实现轻型 Redux,可以让组件间共享状态

步骤:

1、将数据集中在一个 store 对象

2、将所有操作集中在 reducer

3、创建一个 Context

4、创建对数据的读取 API

5、将第四步的内容放到第三步的 Context

6、用 Context.Provider 将 Context 提供给所有组件

7、各个组件用 useContext 获取读写 API

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
import App from "./12_hooks/08_App_hooks_redux"; // hooks useContext useReducer redux

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/09-App-redux.jsx

import React from "react";
import { useContext } from "react";
import { useEffect } from "react";
import { useReducer } from "react";

const MyContext = React.createContext();
const intitalState = {
  userName: "吴大勋",
  count: 100,
  proList: [],
};

const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_USER_NAME":
      return { ...state, userName: action.payload };
    case "CHANGE_COUNT":
      return { ...state, count: action.payload };
    case "CHANGE_PRO_LIST":
      return { ...state, proList: action.payload };
    default:
      return state;
  }
};

const Home = () => {
  // const [state, dispatch] = useReducer(reducer, intitalState)
  const { state, dispatch } = useContext(MyContext);
  useEffect(() => {
    // 不要直接在回调函数上使用async
    const fetchData = async () => {
      const res = await fetch("http://121.89.205.189:3001/api/pro/list").then(
        (res) => res.json()
      );
      dispatch({
        type: "CHANGE_PRO_LIST",
        payload: res.data,
      });
    };
    fetchData();
  }, [dispatch]);
  return (
    <>
      <h1>Home</h1>
      <ul>
        {state.proList &&
          state.proList.map((item) => (
            <li key={item.proid}> {item.proname} </li>
          ))}
      </ul>
    </>
  );
};
const List = () => {
  // const [state, dispatch] = useReducer(reducer, intitalState)
  const { state } = useContext(MyContext);
  return (
    <>
      <h1>List</h1>
      <ul>
        {state.proList &&
          state.proList.map((item) => (
            <li key={item.proid}> {item.proname} </li>
          ))}
      </ul>
    </>
  );
};

// Home 组件  以及 List 组件共享 列表数据
const App = () => {
  const [state, dispatch] = useReducer(reducer, intitalState);
  return (
    <div>
      <MyContext.Provider
        value={{
          state,
          dispatch,
        }}
      >
        <Home />
        <List />
      </MyContext.Provider>
    </div>
  );
};

export default App;

useContext + useReducer 可以实现轻型 redux,但是不适合应用于多模块管理的大型项目

17.2.6 useMemo

17.2.7 useCallback

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
import App from "./12_hooks/09_App_hooks_useCallback_useMemo"; // hooks useCallback useMemo 提升性能以及计算属性

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/09_App_hooks_useCallback_useMemo.jsx

import React from "react";
import { useMemo } from "react";
import { useCallback } from "react";
import { useState } from "react";

// const Child = (props) => {
//   console.log('child被执行了')
//   return (
//     <div>child - { props.b }</div>
//   )
// }
const Child = React.memo((props) => {
  console.log("child被执行了");
  return <div>child - {props.b}</div>;
});

const Com = React.memo((props) => {
  console.log("com被执行了");
  return <div>com - {props.b}</div>;
});

const App = () => {
  const [a, setA] = useState(0);
  // const [b] = useState(100)
  const [b, setB] = useState(100);

  const [list, setList] = useState([1, 2, 3, 4, 5]);

  const add = () => {
    setA(a + 1);
  };

  const addB = () => {
    setB(b + 1);
  };

  // 每次更新生成一个新的引用,意味这属性发生改变
  // const testFn = () => {}
  // 如果记住这个函数 useCallback 以及 useMemo ,从而达到提升组件性能的目的
  // const testFn = useCallback(() => { // useCallback结构
  //   return (event) => { // 真正的函数实现
  //   }
  // }, []) // 依赖值的改变,重新生成新的引用
  const testFn = useMemo((event) => {
    // 真正的函数
  }, []);

  // 函数式组件中,通常使用useMemo可以实现计算属性
  // const doubleA = a * 2 // 不推荐
  const doubleA = useMemo(() => {
    return a * 2;
  }, [a]);

  const len = useMemo(() => {
    return list.length;
  }, [list]);
  return (
    <div>
      <button
        onClick={() => {
          const arr = JSON.parse(JSON.stringify(list)); // 深拷贝
          arr.push(len + 1);
          setList(arr);
        }}
      >
        追加数据
      </button>
      {list}
      <button onClick={add}>a加1</button> {a} - {doubleA} - {len}
      <button onClick={addB}>b加1</button> {b}
      {/* 理想情况 b的值发生改变,Child 组件才被重新渲染 */}
      {/* 可以使用高阶组件 React.memo() 包裹一下Child组件 */}
      <Child b={b} />
      {/* 组件的状态发生改变,组件重新渲染,重新渲染,就会给函数生成一个新的地址,意味着全新的一个属性 */}
      <Com b={b} testFn={testFn} />
    </div>
  );
};

export default App;

useCallback 应用于 组件的事件,使用 useCallback 包裹组件 - 记忆函数

useMemo 可以是计算属性, 也应用于 组件的事件,使用 useMemo 包裹组件 - 记忆组件

17.2.8 useImperativeHandle

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
import App from "./12_hooks/10_App_hooks_useImperativeHandle.jsx"; // useImperativeHandle 父组件操作子组件的方法

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/10_App_hooks_useImperativeHandle.jsx.jsx

import React from "react";

// const Child = React.forwardRef((props, ref) => {

//   return (
//     <>
//       <input ref = { ref }/>
//     </>
//   )
// })

// 可以实现子组件中多个 ref 的使用
const Child = React.forwardRef((props, ref) => {
  const inputRef = React.useRef();
  const divRef = React.useRef();
  // 父组件通过 ref 即可 调用 内部自定义的函数 通过透传ref解决问题
  React.useImperativeHandle(ref, () => {
    return {
      inputFocus: () => {
        inputRef.current.focus();
      },
      wirteVal(str) {
        divRef.current.innerHTML = str;
      },
    };
  });
  return (
    <>
      <input ref={inputRef} />
      <div ref={divRef}></div>
    </>
  );
});

const App = () => {
  const childRef = React.useRef();
  return (
    <div>
      <button
        onClick={() => {
          // childRef.current.focus()
          childRef.current.inputFocus();
        }}
        style={{ marginRight: 30 }}
      >
        获取焦点
      </button>
      <button
        onClick={() => {
          // childRef.current.focus()
          childRef.current.wirteVal("hello hooks!!");
        }}
        style={{ marginRight: 30 }}
      >
        写入内容
      </button>
      <Child ref={childRef} />
    </div>
  );
};

export default App;

上面这个例子中与直接转发 ref 不同,直接转发 ref 是将 React.forwardRef 中函数上的 ref 参数直接应用在了返回元素的 ref 属性上,其实父、子组件引用的是同一个 ref 的 current 对象,官方不建议使用这样的 ref 透传,而使用 useImperativeHandle 后,可以让父、子组件分别有自己的 ref,通过 React.forwardRef 将父组件的 ref 透传过来,通过 useImperativeHandle 方法来自定义开放给父组件的 current。

useImperativeHandle 的第一个参数是定义 current 对象的 ref,第二个参数是一个函数,返回值是一个对象,即这个 ref 的 current 对象,这样可以像上面的案例一样,通过自定义父组件的 ref 来使用子组件 ref 的某些方法。

useImperativeHandleReact.forwardRef必须是配合使用的,这也是为什么在开头要介绍 ref 的转发。

17.2.9 useLayoutEffect

useLayoutEffect 与 useEffect 的区别:

  • useEffect 是异步执行的,而useLayoutEffect是同步执行的。
  • useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前

举个例子:

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
// import App from './12_hooks/10_App_hooks_useImperativeHandle.jsx' // useImperativeHandle 父组件操作子组件的方法
import App from "./12_hooks/11_App_hooks_useLayoutEffect"; // useLayoutEffect

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/13-App-useLayoutEffect.jsx

import React, { useEffect, useLayoutEffect, useState } from "react";

// const App = () => {
//   const [count, setCount] = useState(1)
//   useEffect(() => { // 副作用操作- 异步操作
//     console.log('useEffect') // 后执行
//     document.title = count + '!!'

//   })
//   useLayoutEffect(() => { // 同步操作 - DOM相关
//     console.log('useLayoutEffect') // 先执行
//     document.title = count
//   })
//   return (
//     <div>
//       <h1>useLayoutEffect</h1>
//       { count }
//       <button onClick={ () => {
//         setCount(count + 1)
//       }}>加1</button>
//     </div>
//   );
// };

function App() {
  const [state1, setState1] = useState("hello");
  const [state2, setState2] = useState("hi");

  useEffect(() => {
    let i = 0;
    while (i < 1000000000) {
      i++;
    }
    setState1("world");
  }, []);
  useLayoutEffect(() => {
    let i = 0;
    while (i < 1000000000) {
      i++;
    }
    setState2("world");
  }, []);

  return (
    <>
      <h1>{state1}</h1>
      <h1>{state2}</h1>
    </>
  );
}

export default App;

注意观察 useEffect 抖动现象

useLayoutEffect 做 DOM 操作

useEffect 中 副作用执行

17.2.10 useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
// import App from './12_hooks/10_App_hooks_useImperativeHandle.jsx' // useImperativeHandle 父组件操作子组件的方法
// import App from './12_hooks/11_App_hooks_useLayoutEffect' // useLayoutEffect
import App from "./12_hooks/12_App_hooks_useDebugValue"; // useDebugValue

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/12_App_hooks_useDebugValue.jsx

import React, { useEffect, useState, useDebugValue } from "react";

// const App = () => {
//   const [count, setCount] = useState(1)
//   const add = () => {
//     setCount(count + 1)
//   }

//   useEffect(() => {
//     document.title = `点击了${count}次`
//   })
//   return (
//     <div>
//       <button onClick={ add }>加1</button>
//     </div>
//   );
// };
// 自定义hook
const useCount = () => {
  const [count, setCount] = useState(1);
  const add = () => {
    setCount(count + 1);
  };
  useDebugValue("myCount"); // 自定义Hook起名
  return {
    count,
    add,
  };
};

const useTitle = (count) => {
  useEffect(() => {
    document.title = `点击了${count}次`;
  });
  useDebugValue("myTitle"); // 自定义Hook起名
};

const App = () => {
  // const [count, setCount] = useState(1)
  // const add = () => {
  //   setCount(count + 1)
  // }

  const { count, add } = useCount();

  // useEffect(() => {
  //   document.title = `点击了${count}次`
  // })

  useTitle(count);

  return (
    <div>
      <button onClick={add}>加1</button>
    </div>
  );
};

export default App;

接下来的 hooks 是属于 react18 版本新增的 hooks

17.2.11 useId

useId是一个钩子,用于生成唯一的 ID,在服务器和客户端之间是稳定的,同时避免 hydration 不匹配。

注意:

useId不是用来生成列表中的键的。Keys 应该从你的数据中生成。

不能用于列表渲染的 key 值

对于一个基本的例子,直接将 id 传递给需要它的元素。

对于同一组件中的多个 ID,使用相同的 ID 附加一个后缀。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
// import App from './12_hooks/10_App_hooks_useImperativeHandle.jsx' // useImperativeHandle 父组件操作子组件的方法
// import App from './12_hooks/11_App_hooks_useLayoutEffect' // useLayoutEffect
// import App from './12_hooks/12_App_hooks_useDebugValue' // useDebugValue
import App from "./12_hooks/13_App_hooks_useId"; // useId

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/13_App_hooks_useId.jsx

import React, { useId } from "react";

//React Hook "useId" cannot be called inside a callback.
// React Hooks must be called in a React function component or a custom React Hook function
// useId不能在回调函数中使用,
// react hooks 必须包裹在函数式组件或者自定义hooks中使用
const App = () => {
  // const [list] = useState(['a', 'b', 'c', 'd'])
  const useNameId = useId();
  const passwordId = useId();
  return (
    <div>
      {
        // list && list.map((item) => {
        //   const id = useId() // 这样的写法是错误的
        //   return (
        //     <p key = { id }> { item } </p>
        //   )
        // })
      }
      {/* HTML 中使用for,react中使用HtmlFor */}
      <div>
        <label htmlFor={useNameId}>用户名</label>
        <input type="text" id={useNameId} />
      </div>
      <div>
        <label htmlFor={passwordId}>密码</label>
        <input type="password" id={passwordId} />
      </div>
    </div>
  );
};

export default App;

注意:

useId 会生成一个包含 : token 的字符串。这有助于确保令牌是唯一的,但在 CSS 选择器或 API(如querySelectorAll)中不支持。

useId支持一个identifierPrefix,以防止在多根应用程序中发生碰撞。要配置,请参阅 hydrateRootReactDOMServer 的选项。

hooks 需要在函数式组件以及自定义 hook 的顶级使用(返回 jsx 代码之前),不要在 jsx 代码中使用 hooks

17.2.12 useDeferredValue

真实需求其实不需要实时渲染所有的数据

useDeferredValue 需要接收一个值, 返回这个值的副本, 副本的更新会在值更新渲染之后进行更新, 以此来避免一些不必要的重复渲染. 打个比方页面中有输入框, 输入框下的内容依赖于输入框的值, 但是输入框是一个高频操作, 如果输入 10 次, 可能用户只想看到最终的结果那么中途的实时渲染就显得不那么重要了, 页面元素少点还好, 一旦元素过多页面就会及其的卡顿, 渲染引擎堵得死死的, 用户就会骂娘了, 此时使用 useDeferredValue 是一个很好的选择

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
// import App from './12_hooks/10_App_hooks_useImperativeHandle.jsx' // useImperativeHandle 父组件操作子组件的方法
// import App from './12_hooks/11_App_hooks_useLayoutEffect' // useLayoutEffect
// import App from './12_hooks/12_App_hooks_useDebugValue' // useDebugValue
// import App from './12_hooks/13_App_hooks_useId' // useId
import App from "./12_hooks/14_App_hooks_useDeferredValue"; // useDeferredValue

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/14_App_hooks_useDeferredValue.jsx 数据量必须过大,否则看不到效果

import React, {
  useDeferredValue,
  useEffect,
  useState,
  useMemo,
  memo,
} from "react";
const List = memo(function List({ count }) {
  const [data, setData] = useState([]);

  useEffect(() => {
    const data = [];
    data.length = 50000;
    for (let i = 0; i < data.length; i++) {
      data.fill(i + 1, i);
    }
    setData(data);
  }, [count]);

  return (
    <div>
      {data.map((item) => {
        return <p key={item}>{count}</p>;
      })}
    </div>
  );
});

export default function UseDeferredValueDemo() {
  const [inpVal, setInpVal] = useState("");
  const deferredValue = useDeferredValue(inpVal); // 备份数据
  const memoList = useMemo(
    () => <List count={deferredValue}></List>,
    [deferredValue]
  );
  return (
    <>
      <h1>UseDeferredValue</h1>
      <input
        type="text"
        value={inpVal}
        onChange={(e) => setInpVal(e.target.value)}
      />
      {memoList}
    </>
  );
}

17.2.13 useTransition

useTransition 又叫过渡, 他的作用就是标记非紧急更新, 这些被标记非紧急更新会在紧急更新完之后进行更新, useTransition 使用场景在应对渲染量很大的页面,需要及时响应某些事件的情况。

举个例子,准备一个进度条, 通过滑动进度条来显示进度条的进度并且渲染相同进度数量的 div, 如果我们不对渲染进行优化那无疑页面会很卡, 此时使用过渡配合 useMemo 来缓存页面结构, diffing 算法就会对比出少量的变化进行局部修改。

src/index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";

// import App from './01_props/01_Parent_Child' // 父子组件省略.jsx
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,并且验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽(多个插槽)
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态
// import App from './02_state/01_App_state_es6' // es6 构造函数 初始化状态
// import App from './02_state/02_App_state_es7' // es7 属性初始化器 初始化状态
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似计算属性
// import App from './02_state/06_App_lifeCycle' // 类组件生命周期
// import App from './03_event/01_App_event_es5' // 事件处理 es5绑定事件方式
// import App from './03_event/02_App_event_es6' // 事件处理 es6绑定事件方式
// import App from './04_condition/01_App_condition_yu' // 条件渲染 &&
// import App from './04_condition/02_App_condition_san' // 条件渲染 三元运算符
// import App from './04_condition/03_App_condition_classname' // 条件渲染 动态的class
// import App from './04_condition/04_App_condition_cssinjs' // cssInJs
// import App from './04_condition/05_App_module_css' // 模块化css
// import App from './04_condition/06_App_style' // 动态style
// import App from './05_list/01_App_map' // 列表渲染 使用map
// import App from './05_list/02_App_mutiple_map' // 列表渲染 多层遍历
// import App from './06_form/01_App_form' // 表单绑定 受控组件
// import App from './07_state_up/01_App-parent-child-value' // 状态提升 多组件数据共享
// import App from './07_state_up/02_App_state_up' // 状态提升 多组件数据共享
// import App from './08_com/01_App_props_slot' // 组合VS继承
// import App from './08_com/02_App_modal' // 封装模态框
// import App from './08_com/03_App_portal' // 封装模态框 portal
// import App from './09_context/01_App_next_value' // 逐层传递数据
// import App from './09_context/02_App_context' // 上下文对象Context传值
// import App from './09_context/03_App_context_multiple_value' // 上下文对象Context传值 多个上下文对象传递数据
// import App from './09_context/04_App_one_context_multiple_value' // 上下文对象Context传值 1个上下文对象传递多个数据
// import App from './09_context/05_App_function_context_value' // 上下文对象Context传值 函数式组件获取值
// import App from './09_context/06_App_context_displayName' // 上下文对象Context传值 开发者工具显示Context名称
// import App from './10_hoc/01_App-more-use' // 高阶组件 多个组件引入同一个组件
// import App from './10_hoc/02_App-more-use-hoc' // 高阶组件 高阶组件
// import App from './11_ref/01_App_ref' // ref的使用以及严格模式
// import App from './11_ref/02_App_ref' // ref的使用以及严格模式
// import App from './11_ref/03_App_ref_forward' // 转发ref
// import App from './12_hooks/01_App_hooks_useState' // hooks useState
// import App from './12_hooks/02_App_hooks_useState_obj' // hooks useState  object
// import App from './12_hooks/03_App_hooks_useState_state' // hooks useState  拆分对象状态
// import App from './12_hooks/04_App_hooks_useEffect' // hooks useEffect
// import App from './12_hooks/05_App_hooks_useRef' // hooks useRef
// import App from './12_hooks/06_App_hooks_useReducer' // hooks useReducer
// import App from './12_hooks/07_App_hooks_useContext' // hooks useContext
// import App from './12_hooks/08_App_hooks_redux' // hooks useContext useReducer redux
// import App from './12_hooks/09_App_hooks_useCallback_useMemo' // hooks useCallback useMemo 提升性能以及计算属性
// import App from './12_hooks/10_App_hooks_useImperativeHandle.jsx' // useImperativeHandle 父组件操作子组件的方法
// import App from './12_hooks/11_App_hooks_useLayoutEffect' // useLayoutEffect
// import App from './12_hooks/12_App_hooks_useDebugValue' // useDebugValue
// import App from './12_hooks/13_App_hooks_useId' // useId
// import App from './12_hooks/14_App_hooks_useDeferredValue' // useDeferredValue
import App from "./12_hooks/15_App_hooks_useTransition"; // useTransition

const root = ReactDOM.createRoot(document.getElementById("root"));

// 标签形式调用
root.render(<App />);

src/12_hooks/15_App_hooks_useTransition

import React, { useTransition, useState, useMemo } from "react";

export default function UseTransition() {
  const [isPending, startTransition] = useTransition();

  const [rangeValue, setRangeValue] = useState(1);
  const [renderData, setRenderData] = useState([1]);
  const [isStartTransition, setIsStartTransition] = useState(false);
  const handleChange = (e) => {
    setRangeValue(e.target.value);
    const arr = [];
    arr.length = e.target.value;
    for (let i = 0; i <= arr.length; i++) {
      arr.fill(i, i + 1);
    }
    if (isStartTransition) {
      startTransition(() => {
        setRenderData(arr);
      });
    } else {
      setRenderData(arr);
    }
  };
  const jsx = useMemo(() => {
    return renderData.map((item, index) => {
      return (
        <div
          style={{
            width: 50,
            height: 50,
            backgroundColor: `#${Math.floor(Math.random() * 16777215).toString(
              16
            )}`,
            margin: 10,
            display: "inline-block",
          }}
          key={"item" + index}
        >
          {item}
        </div>
      );
    });
  }, [renderData]);
  return (
    <div>
      <div style={{ textAlign: "center" }}>
        <label>
          <input
            type="checkbox"
            checked={isStartTransition}
            onChange={(e) => {
              setIsStartTransition(e.target.checked);
            }}
          />
          useTransition
        </label>
        <input
          type="range"
          value={rangeValue}
          min={0}
          max={10000}
          style={{ width: 120 }}
          onChange={handleChange}
        />
        <span>进度条 {rangeValue}</span>
        <hr />
      </div>
      {jsx}
    </div>
  );
}

17.2.14 useSyncExternalStore

React18 的 beta 版本将useMutableSource更新为了useSyncExternalStore,这个新的 api 将会对 React 的各种状态管理库产生非常大的影响,下面我来介绍useSyncExternalStore的用法和场景。

我们可以通过这个 api 自行设计一个 redux + react-redux 的数据方案:

1、设计 store

首先我们要设计一个 store,它必须有如下属性:

  • currentState:当前状态
  • subscribe:提供状态发生变化时的订阅能力
  • getSnapshot: 获取当前状态

以及改变 state 的方法,这里参考 redux,设计了 dispatch、reducer

const store = {
  currentState: { data: 0 },
  listeners: [],
  reducer(action) {
    switch (action.type) {
      case "ADD":
        return { data: store.currentState.data + 1 };
      default:
        return store.state;
    }
  },
  subscribe(l) {
    store.listeners.push(l);
  },
  getSnapshot() {
    return store.currentState;
  },
  dispatch(action) {
    store.currentState = store.reducer(action);
    store.listeners.forEach((l) => l());
    return action;
  },
};

2、应用 store 同步组件状态

import React, { useSyncExternalStore } from "react";
import store from "./store";

export default function UseSyncExternalStoreDemo() {
  const state = useSyncExternalStore(
    store.subscribe,
    () => store.getSnapshot().data
  );

  return (
    <div>
      <div>count: {state}</div>
      <div>
        <button onClick={() => store.dispatch({ type: "ADD" })}>add+</button>
      </div>
    </div>
  );
}

17.2.15 useInsertionEffect

useInsertionEffect 与 useEffect 相同,在所有 DOM 变更之前同步触发。在使用 useLayoutEffect 读取布局之前,使用这个函数将样式注入到 DOM 中。因为这个钩子的作用域是有限的,所以这个钩子不能访问 refs,也不能调度更新。

import React, { useInsertionEffect, useEffect, useLayoutEffect } from "react";

export default function UseInsertionEffect() {
  useInsertionEffect(() => {
    console.log("useInsertionEffect"); // 1
    // const style = document.createElement('style')
    // style.innerHTML = '.box { color: red }'
    // document.head.appendChild(style)
  });

  useEffect(() => {
    console.log("useEffect"); // 3
  });

  useLayoutEffect(() => {
    console.log("useLayoutEffect"); // 2
  });

  return <div className="box">UseInsertionEffect</div>;
}

17.3 自定义 hooks

以 use 开头的小驼峰式的函数

十八、Redux

18.1 理解 Flux 架构

在 2013 年,Facebook 让React亮相的同时推出了 Flux 框架,React的初衷实际上是用来替代jQuery的,Flux实际上就可以用来替代Backbone.jsEmber.js等一系列MVC架构的前端 JS 框架。

其实FluxReact里的应用就类似于Vue中的Vuex的作用,但是在Vue中,Vue是完整的mvvm框架,而Vuex只是一个全局的插件。

React只是一个 MVC 中的 V(视图层),只管页面中的渲染,一旦有数据管理的时候,React本身的能力就不足以支撑复杂组件结构的项目,在传统的MVC中,就需要用到 Model 和 Controller。Facebook 对于当时世面上的MVC框架并不满意,于是就有了Flux, 但Flux并不是一个MVC框架,他是一种新的思想。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MmdF2sj-1692704012460)(image\flux.png)]

  • View: 视图层
  • ActionCreator(动作创造者):视图层发出的消息(比如 mouseClick)
  • Dispatcher(派发器):用来接收 Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒 Views 要更新页面

Flux 的流程:

  1. 组件获取到

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/90480.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端需要理解的跨平台知识

混合开发是指使用多种开发模开发App的一种开发模式&#xff0c;涉及到两大类技术&#xff1a;原生 Native、Web H5。原生 Native 主要指 iOS&#xff08;Objective C&#xff09;、Android&#xff08;Java&#xff09;&#xff0c;原生开发效率较低&#xff0c;开发完成需要重…

leetcode739. 每日温度 单调栈

自己思路&#xff1a; 想到用两个栈&#xff0c;一个维护元素、另一个维护下标。但是还是无法处理有重复元素的问题&#xff08;用哈希表来存储的时候&#xff09;。所以就看了答案的思路。 答案思路&#xff1a; 从前往后遍历&#xff0c;维护一个单调栈。栈存放数组的下标。…

【QT】绘制旋转等待

很高兴在雪易的CSDN遇见你 ,给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 程序中经常会遇到耗时的操作,需要提供等待的窗口,防止用户多次点击造成卡顿等问题。本文分享旋转等待技术,希望对各位小伙伴有所帮助!结果如下:

36款影音娱乐-音乐、电台、直播类APP评测体验报告

为方便开发者更好地衡量APP在同类产品中的表现和竞争力&#xff0c;有针对性地进行产品优化&#xff0c;软件绿色联盟策划了垂类APP评测体验专题&#xff0c;目前已发布了天气、小说、教育和视频类APP评测体验报告。本期将对影音娱乐类中的音乐、电台、直播类APP围绕绿标五大标…

Unity Meta Quest MR 开发教程:(二)自定义透视 Passthrough【透视功能进阶】

文章目录 &#x1f4d5;教程说明&#x1f4d5;动态开启和关闭透视⭐方法一&#xff1a;OVRManager.instance.isInsightPassthroughEnabled⭐方法二&#xff1a;OVRPassthroughLayer 脚本中的 hidden 变量 &#x1f4d5;透视风格 Passthrough Styling⭐Inspector 面板控制⭐代码…

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测。 模型描…

AI绘画StableDiffusion实操教程:斗破苍穹-小医仙

之前分享过StableDiffusion的入门到精通教程&#xff1a;AI绘画&#xff1a;Stable Diffusion 终极炼丹宝典&#xff1a;从入门到精通 但是还有人就问&#xff1a;安装是安装好了&#xff0c;可是为什么生成的图片和你生成的图片差距那么远呢&#xff1f; 怎么真实感和质感一…

Sketchup软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 SketchUp是一款由Trimble公司开发的3D建模软件&#xff0c;广泛应用于建筑、室内设计、城市规划等领域。它以直观的用户界面和强大的功能而闻名&#xff0c;让用户能够轻松地创建和修改三维模型。 1、SketchUp的主要特点 用户…

Linux下Jenkins安装 (最新)

环境概述 随着软件开发需求及复杂度的不断提高&#xff0c;团队开发成员之间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。Jenkins自动化部署可以解决集成、测试、部署等重复性的工作&#xff0c;工具集成的效率明显高于人工操作&#xff1b…

MyBatis 的关联关系配置 一对多,一对一,多对多 关系的映射处理

目录 一.关联关系配置的好处 二. 导入数据库表&#xff1a; 三. 一对多关系&#xff1a;-- 一个订单对应多个订单项 四.一对一关系&#xff1a;---一个订单项对应一个订单 五.多对多关系&#xff08;两个一对多&#xff09; 一.关联关系配置的好处 MyBatis是一…

抓包相关,抓包学习

检查网络流量 - 提琴手经典 (telerik.com) Headers Reference - Fiddler Classic (telerik.com) 以上是fiddler官方文档 F12要勾选保留日志 不勾选的话跳转到新页面之前页面的日志不会在下方显示 会保留所有抓到的包 如果重定向到别的页面 F12抓包可能看不到响应信息,但是…

网络层协议——ip

文章目录 1. 网络层2. IP协议2.1 协议头格式 3. 网段划分3.1 特殊的IP地址3.2 IP地址的数量限制 4. 私有IP地址和公网IP地址 1. 网络层 在应用层解决了如何读取完整报文、序列化反序列化、协议处理问题。在传输层解决了可靠性问题。那么网络层IP的作用是在复杂的网络环境中确定…

基于SSM+vue框架的个人博客网站源码和论文

基于SSMvue框架的个人博客网站源码和论文061 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm &#xff08;设计&#xff09;研究背景与意义 关于博客的未来&#xff1a;在创办了博客中国(blogchina)、被誉为“…

华为云开发工具CodeArts IDE for C/C++ 开发使用指南

简介 CodeArts IDE是一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了开发语言和调试服务。本文主要介绍CodeArts IDE for C/C的基本功能。 1.下载安装 CodeArts IDE for C/C 已开放公测&#xff0c;下载获取免费体验 2.新建C/C工程 CodeArts IDE for C/…

基于Jenkins自动打包并部署docker环境

基于上一章 基于Jenkins自动打包并部署Tomcat环境_学习新鲜事物的博客-CSDN博客 1、安装docker-ce 在tomcat上创建远程命令 [roottomcat ~]# mkdir /data 在tomcat上构建 tomcat基础镜像。在构建基础镜像之前需要先安装 Docker 与 JDK。 [roottomcat ~]# wget -O /etc/yum.…

程序填空技巧1.0

程序填空要先知道这个程序要干什么&#xff0c;然后找到标准模板后对照模板填写&#xff0c;但当然不是让你做题的时候对照模板写&#xff0c;而是要把每种算法的标准模板背下来&#xff0c;但你肯定要问&#xff1a;邹邹&#xff0c;我哪里来的模板呢&#xff1f;&#xff1f;…

unity 模型显示在UI上 并交互(点击、旋转、缩放)

项目工程&#xff1a;unity模型显示在UI上并交互&#xff08;点击、旋转、缩放&#xff09;资源-CSDN文库 1.在Assets创建 Render Texture&#xff08;下面会用到&#xff09;&#xff0c;根据需要设置Size 2.创建UIRawImage&#xff0c;并把Render Texture赋上 3.创建相机&am…

【微服务】05-网关与BFF(Backend For Frontend)

文章目录 1.打造网关1.1 简介1.2 连接模式1.3 打造网关 2.身份认证与授权2.1 身份认证方案2.1.1 JWT是什么2.1.2 启用JwtBearer身份认证2.1.3 配置身份认证2.1.4 JWT注意事项 1.打造网关 1.1 简介 BFF(Backend For Frontend)负责认证授权&#xff0c;服务聚合&#xff0c;目标…

CMake3.27+OpenCV4.8+VS2019+CUDA配置

1、准备工作 CMake3.27+OpenCV4.8+opencv_contrib-4.8.0+CUDA+CUDNN+TensorRT下载好并安装cuda 2、正式开始安装 启动CMake开始配置 打开刚解压的cmake文件夹中找到bin目录下的cmake-gui.exe 点击cmake中左下角的 Configure进行第一次配置,会弹出选择环境对话框 …

Jmeter压测测试

Jmeter安装启动 1、Jmeter下载安装 模拟真正的大量并发请求 下载Jmeter&#xff1a;Apache JMeter - Download Apache JMeter 解压apache-jmeter-5.4.1.zip文件至目录下&#xff08;不要有空格和中文&#xff09; 2、配置JMETER_HOME 在系统中搜索设置 打开设置&#xff0…
最新文章