当前位置: 首页 > news >正文

【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 -globalnpm i xxx -S  ->  yarn add xxxnpm i xxx -D  ->  yarn add xxx -devnpm 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 startStarts the development server.npm run buildBundles the app into static files for production.npm testStarts the test runner.npm run ejectRemoves this tool and copies build dependencies, configuration filesand scripts into the app directory. If you do this, you can’t go back!We suggest that you begin by typing:cd react-basicnpm startHappy 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.1getFID(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-basicAdding /config/env.js to the projectAdding /config/getHttpsConfig.js to the projectAdding /config/modules.js to the projectAdding /config/paths.js to the projectAdding /config/webpack.config.js to the projectAdding /config/webpackDevServer.config.js to the projectAdding /config/jest/babelTransform.js to the projectAdding /config/jest/cssTransform.js to the projectAdding /config/jest/fileTransform.js to the projectAdding /scripts/build.js to the projectAdding /scripts/start.js to the projectAdding /scripts/test.js to the projectAdding /config/webpack/persistentCache/createEnvironmentHash.js to the projectUpdating the dependenciesRemoving react-scripts from dependenciesAdding @babel/core to dependenciesAdding @pmmmwh/react-refresh-webpack-plugin to dependenciesAdding @svgr/webpack to dependenciesAdding babel-jest to dependenciesAdding babel-loader to dependenciesAdding babel-plugin-named-asset-import to dependenciesAdding babel-preset-react-app to dependenciesAdding bfj to dependenciesAdding browserslist to dependenciesAdding camelcase to dependenciesAdding case-sensitive-paths-webpack-plugin to dependenciesAdding css-loader to dependenciesAdding css-minimizer-webpack-plugin to dependenciesAdding dotenv to dependenciesAdding dotenv-expand to dependenciesAdding eslint to dependenciesAdding eslint-config-react-app to dependenciesAdding eslint-webpack-plugin to dependenciesAdding file-loader to dependenciesAdding fs-extra to dependenciesAdding html-webpack-plugin to dependenciesAdding identity-obj-proxy to dependenciesAdding jest to dependenciesAdding jest-resolve to dependenciesAdding jest-watch-typeahead to dependenciesAdding mini-css-extract-plugin to dependenciesAdding postcss to dependenciesAdding postcss-flexbugs-fixes to dependenciesAdding postcss-loader to dependenciesAdding postcss-normalize to dependenciesAdding postcss-preset-env to dependenciesAdding prompts to dependenciesAdding react-app-polyfill to dependenciesAdding react-dev-utils to dependenciesAdding react-refresh to dependenciesAdding resolve to dependenciesAdding resolve-url-loader to dependenciesAdding sass-loader to dependenciesAdding semver to dependenciesAdding source-map-loader to dependenciesAdding style-loader to dependenciesAdding tailwindcss to dependenciesAdding terser-webpack-plugin to dependenciesAdding webpack to dependenciesAdding webpack-dev-server to dependenciesAdding webpack-manifest-plugin to dependenciesAdding workbox-webpack-plugin to dependenciesUpdating the scriptsReplacing "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.jsonAdding Jest configurationAdding Babel presetRunning npm install...up to date in 4s203 packages are looking for fundingrun `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/6571sideEffects: 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/6571sideEffects: 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/6571sideEffects: 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/6571sideEffects: 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/listapp.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 变量加{} 变 reactfunction 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 变量加{} 变 reactfunction 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"; // 省略.jsxconst 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><Child1str={str}flag={true}num={100}obj={{ a: 1, b: 2 }}arr={["a", "b", "c"]}/><Child2str={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><Child1str={str}flag={true}num={100}obj={{ a: 1, b: 2 }}arr={["a", "b", "c"]}/><Child2flag={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 propertiesoptionalObjectWithStrictShape: 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><Child1str={str}flag={true}num={100}obj={{ a: 1, b: 2 }}arr={["a", "b", "c"]}/><Child2flag={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 (<divstyle={{ 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 (<divstyle={{ 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> */}<Mouserender={(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}<buttononClick={() => {this.setState((state, props) => {return {count: state.count + 1,};},() => {console.log(4, this.state.count); // 103});console.log(1, this.state.count); // 100this.setState((state, props) => {return {count: state.count + 1,};},() => {console.log(5, this.state.count); // 103});console.log(2, this.state.count); // 100this.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}<buttononClick={() => {this.setState({count: this.state.count + 1,},() => {console.log(4, this.state.count); // 101});console.log(1, this.state.count); // 100this.setState({count: this.state.count + 1,},() => {console.log(5, this.state.count); // 101});console.log(2, this.state.count); // 100this.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 (<><inputtype="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 使下一次渲染可以显示降级 UIreturn { hasError: true };}componentDidCatch(error, info) {// "组件堆栈" 例子://   in ComponentThatThrows (created by App)//   in ErrorBoundary (created by App)//   in div (created by App)//   in Appconsole.log(info.componentStack);}render() {if (this.state.hasError) {// 你可以渲染任何自定义的降级 UIreturn <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><buttononClick={() => {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  1console.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 1console.log("b", b); // b 2};render() {return (<div><button onClick={this.handlerClickFn}>es6绑定事件-定义箭头函数</button><buttononClick={(event) => {// event 默认参数console.log(2, this);}}>es6绑定事件-jsx写箭头函数</button><button onClick={this.handlerParamsClick.bind(this, "1", "2")}>es6绑定事件-传递参数</button><buttononClick={(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}<buttononClick={() => {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 + ""}<buttononClick={() => {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"; // 条件渲染 动态的classconst 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><buttonclassName={classnames({btn: true,"btn-sm": this.state.size === "sm","btn-success": this.state.type === "default",})}>default</button><buttonclassName={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"; // cssInJsconst 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"; // 模块化cssconst 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"; // 动态styleconst 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 (<><buttononClick={() => {this.setState({size: this.state.size + 2,});}}>字号+2</button><inputtype="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"; // 列表渲染 使用mapconst 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.checkedconst value = event.target.valueconst 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}<buttononClick={() => {this.setState({ count: this.state.count + 1 });}}>加1</button></>);}
}
class Child2 extends Component {state = { count: 1 };render() {return (<><h1>child2组件</h1>{this.state.count}<buttononClick={() => {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><Headerleft={<span>logo</span>}default={<span>搜索框</span>}right={<span>登录</span>}>1111111111</Header><Headerleft={<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 (<divstyle={{position: "fixed",top: 0,left: 0,bottom: 0,right: 0,backgroundColor: "rgba(0,0,0, 0.4)",display: "flex",justifyContent: "center",alignItems: "center",}}><divstyle={{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"; // 封装模态框 portalconst 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={ {<divid="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();}}}><divstyle={{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><inputtype="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><inputtype="color"value={this.state.color}onChange={(event) => {this.setState({color: event.target.value,});}}/>{/* 2.祖先组件通过 上下文对象的Provider 组件 配合value进行传值 */}<MyContext.Providervalue={{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><inputtype="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(); // 创建一个唯一的 refcomRef = 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"; // 转发refconst 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(); // 创建一个唯一的 refcomRef = 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><buttononClick={() => {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 useStateconst 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 的值为 1const [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}<buttononClick={() => {// 修改函数内部的值为 运算之后的结果setCount(count + 1);}}>加1</button><buttononClick={function () {// 修改函数内部的值为 运算之后的结果setCount(count + 10);}}>加10</button><button onClick={add100}>加100</button><button onClick={add1000}>加1000</button><button onClick={add10000}>加10000</button>{name}<buttononClick={() => {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  objectconst 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 useEffectconst 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><buttononClick={() => {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 useRefconst 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 useReducerconst 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) => (<imgkey={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 useContextconst 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 reduxconst 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(() => {// 不要直接在回调函数上使用asyncconst 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.Providervalue={{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><buttononClick={() => {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><buttononClick={() => {// childRef.current.focus()childRef.current.inputFocus();}}style={{ marginRight: 30 }}>获取焦点</button><buttononClick={() => {// 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"; // useLayoutEffectconst 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"; // useDebugValueconst 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"; // useIdconst 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"; // useDeferredValueconst 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><inputtype="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"; // useTransitionconst 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 (<divstyle={{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><inputtype="checkbox"checked={isStartTransition}onChange={(e) => {setIsStartTransition(e.target.checked);}}/>useTransition</label><inputtype="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. 组件获取到

相关文章:

【React基础全篇】

文章目录 一、关于 React二、脚手架2.1 create-react-app 脚手架的使用2.2 项目目录解析2.3 抽离配置文件2.4 webpack 二次封装2.4.1 集成 css 预处理器2.4.2 配置解析别名 2.5 setupProxy 代理 三、JSX3.1 jsx 语法详解3.2 React.createElement 四、组件定义4.1 类组件4.2 函数…...

如何使用 Vue.js 侦听嵌套数据?

new Vue({el: "#app",data: {target: {list: [],},},watch: {"target.list": {handler(newVal, oldVal) {},deep: true,},} }); 给target的list属性增加侦听器&#xff0c;需要在watch中使用字符串的写法 "target.list" 来标记侦听的内容 han…...

Spring AOP详解

Spring AOP是Spring框架中的一个模块&#xff0c;它允许开发人员使用面向切面编程(AOP)的思想来解耦系统的不同层次。 Spring AOP的核心概念是切面(aspect)、连接点(join point)、通知(advice)、切点(pointcut)和引入(introduction)。 切面(aspect)&#xff1a;切面是一个类, 它…...

linux iptables安全技术与防火墙

linux iptables安全技术与防火墙 1、iptables防火墙基本介绍1.1netfilter/iptables关系1.2iptables防火墙默认规则表、链结构 2、iptables的四表五链2.1四表2.2五链2.3四表五链总结2.3.1 规则链之间的匹配顺序2.3.2 规则链内的匹配顺序 3、iptables的配置3.1iptables的安装3.2i…...

TCP性能机制

延迟应答 为什么有延迟应答 发送方如果长时间没有收到ACK应答&#xff0c;则会触发超时重传机制&#xff0c;重新发送数据包。但如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小&#xff0c;发送方一次只能发少量数据&#xff0c;效率较低。 举个例子理解一…...

qt信号槽同步问题

目录 信号槽&#xff1a; 注意事项&#xff1a; 具体例子&#xff1a; 线程安全问题的例子&#xff1a; 信号槽&#xff1a; 在Qt编程中&#xff0c;信号&#xff08;Signal&#xff09;和槽&#xff08;Slot&#xff09;是一种用于在对象之间进行通信的机制。信号用于发出…...

七夕特惠-8折抢购,从速

在七夕这个特殊的日子&#xff0c;我们推出了8折优惠活动&#xff0c;具体如下&#xff1a; 不管是充值会员&#xff0c;还是购买套路文章&#xff0c;一律享受8折优惠&#xff0c;活动截止时间为2023年8月24日12时。 甚至还有免费抽奖活动 兑奖方式&#xff0c;复制兑奖码…...

[NLP]LLM--transformer模型的参数量

1. 前言 最近&#xff0c;OpenAI推出的ChatGPT展现出了卓越的性能&#xff0c;引发了大规模语言模型(Large Language Model, LLM)的研究热潮。大规模语言模型的“大”体现在两个方面&#xff1a;模型参数规模大&#xff0c;训练数据规模大。以GPT3为例&#xff0c;GPT3的参数量…...

5 Python的面向对象编程

概述 在上一节&#xff0c;我们介绍了Python的函数&#xff0c;包括&#xff1a;函数的定义、函数的调用、参数的传递、lambda函数等内容。在本节中&#xff0c;我们将介绍Python的面向对象编程。面向对象编程&#xff08;Object-Oriented Programming, 即OOP&#xff09;是一种…...

卷积神经网络——上篇【深度学习】【PyTorch】【d2l】

文章目录 5、卷积神经网络5.1、卷积5.1.1、理论部分5.1.2、代码实现5.1.3、边缘检测 5.2、填充和步幅5.2.1、理论部分5.2.2、代码实现 5.3、多输入多输出通道5.3.1、理论部分5.3.2、代码实现 5.4、池化层 | 汇聚层5.4.1、理论部分5.4.2、代码实现 5、卷积神经网络 5.1、卷积 …...

【从零学习python 】54. 内存中写入数据

文章目录 内存中写入数据StringIOBytesIO进阶案例 内存中写入数据 除了将数据写入到一个文件以外&#xff0c;我们还可以使用代码&#xff0c;将数据暂时写入到内存里&#xff0c;可以理解为数据缓冲区。Python中提供了StringIO和BytesIO这两个类将字符串数据和二进制数据写入…...

速通蓝桥杯嵌入式省一教程:(九)AT24C02芯片(E2PROM存储器)读写操作与I2C协议

AT24C02芯片&#xff08;又叫E2PROM存储器、EEPROM存储器&#xff09;&#xff0c;是一种通过I2C(IIC)协议通信的掉电保存存储器芯片&#xff0c;其内部含有256个8位字节。在介绍这款芯片之前&#xff0c;我们先来粗略了解一下I2C协议。 I2C总线是一种双向二线制的同步串行总线…...

负载均衡:优化性能与可靠性的关键

在现代互联网时代&#xff0c;数以万计的用户访问着各种在线服务&#xff0c;从即时通讯、社交媒体到电子商务和媒体流媒体&#xff0c;无不需要应对海量的请求和数据传输。在这个高并发的环境下&#xff0c;负载均衡成为了关键的技术&#xff0c;它旨在分散工作负载&#xff0…...

T113-S3-TCA6424-gpio扩展芯片调试

目录 前言 一、TCA6424介绍 二、原理图连接 三、设备树配置 四、内核配置 五、gpio操作 总结 前言 TCA6424是一款常用的GPIO&#xff08;通用输入输出&#xff09;扩展芯片&#xff0c;可以扩展微控制器的IO口数量。在T113-S3平台上&#xff0c;使用TCA6424作为GPIO扩展芯…...

奥威BI数据可视化工具:个性化定制,打造独特大屏

每个人都有自己独特的审美&#xff0c;因此即使是做可视化大屏&#xff0c;也有很多人希望做出不一样的报表&#xff0c;用以缓解审美疲劳的同时提高报表浏览效率。因此这也催生出了数据可视化工具的个性化可视化大屏制作需求。 奥威BI数据可视化工具&#xff1a;个性化定制&a…...

13 秒插入 30 万条数据,批量插入!

数据库表 CREATE TABLE t_user (id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户id,username varchar(64) DEFAULT NULL COMMENT 用户名称,age int(4) DEFAULT NULL COMMENT 年龄,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT用户信息表; User实体 /*** …...

Nginx代理转发地址不正确问题

使用ngix前缀去代理转发一个地址&#xff0c;貌似成功了&#xff0c;但是进不到正确的页面&#xff0c;能够访问&#xff0c;但是一直404远处出来nginx会自动拼接地址在后面 后面才知道要将这段代码加上去&#xff0c;去除前缀转发...

HyperMotion高度自动化云迁移至华为HCS8.1解决方案

项目背景 2020 年以来&#xff0c;金融证券已经成为信创落地最快的领域。2021 年证监会发布的《证券期货业科技发展十四五规划》中&#xff0c;将“加强信创规划与实施”作为证券行业重点建设任务之一。为了符合国家信创标准&#xff0c;某证券企业计划将网管系统、呼叫中心管…...

pbootcms系统安全防护设置大全

PbootCMS系统简介 PbootCMS是全新内核且永久开源免费的PHP企业网站开发建设管理系统&#xff0c;是一套高效、简洁、 强悍的可免费商用的PHP CMS源码&#xff0c;能够满足各类企业网站开发建设的需要。系统采用简单到想哭的模板标签&#xff0c;只要懂HTML就可快速开发企业网站…...

【环境】docker时间与宿主同步

1.容器创建后 docker cp /etc/localtime 容器名:/etc/2.容器创建时 加入 -v /ect/localtime/:/etc/localtime:ro参考链接...

亮点!视频云存储/安防监控视频智能分析平台睡岗离岗检测

在生产过程中&#xff0c;未经领导允许的擅自离岗、睡岗会带来很多的潜在危害。TSINGSEE青犀推出的视频云存储/安防监控视频智能分析平台得睡岗离岗检测根据AI视频分析技术建立人工智能算法&#xff0c;对视频画面展开分析与识别。自动识别出人员睡岗、离岗、玩手机与抽烟等动作…...

编程锦囊妙计——快速创建本地Mock服务

点击上方&#x1f446;蓝色“Agilean”&#xff0c;发现更多精彩。 前情提要 在本系列上一篇文章《全文干货&#xff1a;打破前后端数据传递鸿沟&#xff0c;高效联调秘笈》中我们分享了使用Zod这一运行时类型校验库来对后端服务响应结果进行验证达到增加项目质量的方式。 这次…...

简单认识镜像底层原理详解和基于Docker file创建镜像

文章目录 一、镜像底层原理1.联合文件系统(UnionFS)2.镜像加载原理3.为什么Docker里的centos的大小才200M? 二、Dockerfile1.简介2.Dockerfile操作常用命令 三、创建Docker镜像1.基于已有镜像创建2.基于本地模板创建3.基于Dockerfile创建4.Dockerfile多阶段构建镜像 一、镜像底…...

加速乐(__jsl_clearance_s)动态cookie生成分析实战

文章目录 一、写在前面二、抓包分析三、逆向分析 一、写在前面 加速乐&#xff08;JSL&#xff09;是阿里推出的一项反爬虫服务&#xff0c;其生成cookie的原理基于浏览器的行为特征 我们知道普通网站生成cookie是在请求时生成&#xff0c;而它先生成cookie&#xff0c;然后向服…...

启动Vue项目踩坑记录

前言 在启动自己的Vue项目时&#xff0c;遇到一些报错&#xff0c;当时很懵&#xff0c;解决了以后豁然开朗&#xff0c;特写此博客记录一下。 一、<template>里多加了个div标签 [vite] Internal server error: At least one <template> or <script> is req…...

vue-pc上传优化-uni-app上传优化

vue-pc上传优化 当我们使用自己搭建的文档服务器上传图片时候&#xff0c;在本地没问题&#xff0c;上线上传会比较慢 这时候我们最简单的方法就是写一个加载组件&#xff0c;上传之前打开组件&#xff0c;掉完接口关闭组件 或者不想写直接使用element的loading写一个遮罩层加…...

【计算机视觉|生成对抗】StackGAN:使用堆叠生成对抗网络进行文本到照片逼真图像合成

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;StackGAN: Text to Photo-realistic Image Synthesis with Stacked Generative Adversarial Networks 链接&#xff1a;[1612.03242] StackGAN: Text to Photo-realistic Image Synthesis…...

跟随角色镜头时,解决地图黑线/白线缝隙的三种方案

下面一共三个解决方案&#xff0c;这里我推荐第二个方案解决&#xff0c;因为够快速和简单。 现象&#xff1a; 解决方案一&#xff1a; 参考【Unity2D】去除地图中的黑线_unity选中后有线_香菇CST的博客-CSDN博客&#xff0c;博主解释是因为抗锯齿采样导致的问题。 具体到这…...

redis7高级篇2 redis的BigKey的处理

一 Bigkey的处理 1.1 模拟造数 1.截图 2.代码 &#xff1a;使用pipe 批量插入10w的数据量 cat /root/export/monidata.txt | redis-cli -h 127.0.0.1 -a 123456 -p 6379 --pipe [rootlocalhost export]# for((i1;i<10*10;i)); do echo "set k$i v$i" >>…...

启英泰伦通话降噪方案,采用深度学习降噪算法,让通话更清晰

生活中的通话应用场景无处不在&#xff0c;如电话、对讲机、远程会议、在线教育等。普遍存在的问题是环境噪音、干扰声导致通话声音不清晰&#xff0c;语音失真等。 为了解决这一问题&#xff0c;启英泰伦基于自适应线性滤波联合非线性滤波的回声消除方案和基于深度学习的降噪…...

将SonarLint集成到Git

1、搭建SonarQube服务器 下载SonarQube安装包 访问SonarQube官网&#xff08;https://www.sonarqube.org/downloads/&#xff09;下载最新版本的SonarQube Community Edition。解压安装包 将下载的压缩包解压到一个目录&#xff0c;例如&#xff1a;D:\sonarqube-community-7.…...

【Jenkins】rpm方式安装Jenkins(2.401,jdk版本17)

目录 【Jenkins】rpm方式安装Jenkins 1、主机初始化 2、软件要求 RPM包安装的内容 配置文件说明 3、web操作 【Jenkins】rpm方式安装Jenkins 1、主机初始化 [rootlocalhost ~]# hostname jenkins[rootlocalhost ~]# bash[rootjenkins ~]# systemctl stop firewalld[roo…...

vue3跳转统一页面,path一样,传递的参数不一样时页面不刷新

vue3中当路由一样&#xff0c;参数quary不一样的跳转不刷新 当路由的path都是一样的&#xff0c;quary不一样&#xff0c;在跳转的时候&#xff0c;不会执行onMounted等方法&#xff0c;页面也就不会刷新。 方法&#xff1a; 修改router-view&#xff0c;在app.vue页面给标签…...

升级还是不升级?iPhone 15和iPhone 14 Plus性能比较

预览iPhone 15 Pro Max与三星Galaxy S23 Ultra之战是有正当理由的。显然,三星的旗舰智能手机为2023年的所有其他旗舰产品定下了基调——由于其超长的电池寿命和一流的摄像头,证明了它是最受欢迎的产品。 毫不奇怪,Galaxy S23 Ultra不仅是最好的照相手机之一,也是花钱能买到…...

关于LED电子显示屏幕的显示功能

因为LED显示屏的发光颜色和发光效率与制作LED的材料和工艺相关&#xff0c;目前广泛采用的有红、绿、蓝三种颜色的LED。这些LED的独特之处在于它们工作时需要的电压极低&#xff08;仅1.5-3V&#xff09;&#xff0c;能够主动发光&#xff0c;并且具有一定的亮度。这亮度可以通…...

计算机视觉--利用HSV和YIQ颜色空间处理图像噪声

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天我们将利用HSV和YIQ颜色空间处理图像噪声。在本次实验中&#xff0c;我们使用任意一张图片&#xff0c;通过RGB转HSV和YIQ的操作&#xff0c;加入了椒盐噪声并将其转换回RGB格式&#xff0c;最终实现对图像的噪声处理…...

Android Studio中引入MagicIndicator

1.github中下载文件 GitHub - hackware1993/MagicIndicator: A powerful, customizable and extensible ViewPager indicator framework. As the best alternative of ViewPagerIndicator, TabLayout and PagerSlidingTabStrip —— 强大、可定制、易扩展的 ViewPager 指示器框…...

webrtc学习(六)重要信令级时序图

一.四个重要信令 1.用户登录信令 SignIn 2..用户登出信令 SignOut 3..用户等待信令 wait信令是指从服务器的消息队列中获取暂存的中转消息&#xff0c;比如说sdp消息&#xff0c;对于信令服务器来说&#xff0c;他没有办法给用户推送消息&#xff0c;只能是用户推送消息给…...

Leetcode刷题笔记--Hot21-30

1--全排列&#xff08;46&#xff09; 主要思路1&#xff1a; 经典全排列&#xff0c;每次枚举每一位时&#xff0c;重头开始枚举&#xff0c;用一个访问数组记录当前已经被访问过的数字&#xff1b; 这道题不包含重复数字&#xff0c;所以不需要进行树层上的剪枝&#xff1b; …...

【MyBatis八股】MyBatis面试题

目录 MyBatis是什么&#xff1f;Mybaits的优缺点&#xff1f;为什么说Mybatis是半自动ORM映射工具&#xff1f;它与全自动的区别在哪里&#xff1f;Hibernate 和 MyBatis 的区别&#xff1f;JDBC编程有哪些不足之处&#xff0c;MyBatis是如何解决这些问题的&#xff1f;MyBatis…...

Apache Hudi初探(二)(与flink的结合)--flink写hudi的操作(JobManager端的提交操作)

背景 在Apache Hudi初探(一)(与flink的结合)中&#xff0c;我们提到了Pipelines.hoodieStreamWrite 写hudi文件,这个操作真正写hudi是在Pipelines.hoodieStreamWrite方法下的transform(opName("stream_write", conf), TypeInformation.of(Object.class), operatorFa…...

Office ---- excel ---- 怎么批量设置行高

解决方法&#xff1a; 调整行高即可...

Wlan——STA上线流程与802.11MAC帧讲解

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标准属于以太网的一种帧格式…...

HTTP的并发连接限制和连接线程池

为什么有并发连接限制和连接线程池 大量的客户端连接到服务器&#xff0c;会导致服务器端需要大量的维护连接资源&#xff0c;同时需要处理客户端的请求&#xff0c;这是如何高效的执行任务成了一个关键的问题&#xff0c;所以&#xff0c;并发连接限制和连接线程池的出现就是…...

【从零学习python 】45.Python中的类方法和静态方法

文章目录 类方法、静态方法类方法静态方法使用场景 进阶案例 类方法、静态方法 类方法 类方法是以类对象作为第一个参数的方法。需要使用装饰器classmethod来标识其为类方法。对于类方法&#xff0c;第一个参数必须是类对象&#xff0c;一般以cls作为第一个参数。 class Dog…...

基于 VisualFoxPro 环境开发应用程序的过程

应用程序开发前开发者要与用户之间广泛沟通&#xff0c;作大量的调查研究和分析工 作&#xff0c;从而明确用户的要求、程序应具备的功能及可以完成的任务。为此要进行两方 面的分析&#xff0c;数据分析和功能分析。数据分析的目的是收集系统应包含的数据、数据 的真实性、…...

SpringBoot整合Quartz,实现数据库方式执行定时任务

springboot整合quartz&#xff0c;实现数据库方式执行定时任务。把定时任务信息存进数据库&#xff0c;项目启动后自动执行定时任务。 1.引入依赖包&#xff1a; <dependency> <groupId>org.springframework.boot</groupId> <ar…...

java中多个list怎么用List表示?

如果你有多个List对象&#xff0c;想要将它们合并成一个List对象&#xff0c;可以使用addAll()方法来实现。addAll()方法将会把一个List中的元素逐个添加到另一个List中。 以下是一个示例&#xff0c;展示了如何将多个List对象合并为一个List对象&#xff1a; import java.ut…...

postgresql 数据排序

postgresql 常见操作 排序总结 排序 -- 排序的时候null是最大的值(看一下) select employee_id,manager_id from employeesorder by manager_id desc;-- nulls first使null值排在第一位 select employee_id,manager_id from employeesorder by manager_id nulls first;-- null…...

虚拟机 net、桥接、主机三种网络模式寻根问底

虚拟机使用物理主机上的网络适配器直接连接到物理网络中。 这意味着虚拟机就像是通过网线直接连接到路由器一样,成为物理网络中的一个独立设备。 虚拟机可以获取一个永久的IP地址,通过DHCP或手动设置。 虚拟机和物理主机都可以访问对方以及公共网络中的其他设备,比如文件服务…...