React
温馨提示
正在学习整理中...
React
用于构建用户界面的 JavaScript 库
React 官网 - 中文
React 官网 - 英文
Create React App - 官网
React - 教程
webpack - 官网
Create React App
React 16-19 版本
组件化
当前前端的开发, 一定是: 组件化/模块化 的开发 1 有利于团队协作开发 2 便于组件的复用:提高开发效率、方便后期维护、减少页面中的冗余代码
如何划分组件 业务组件: 针对项目的需求封装的 1 普通业务组件 2 通用业务组件() 功能组件: 适用于多个项目( eg:UI组件库 ) 1 通用功能组件
因为组件化的开发, 必定带来 "工程化" 的处理. 也就是基于 webpack / vite 等工具 1 实现组件的合并, 压缩, 打包等 2 代码的编译, 兼容, 校验等 ...
React工程化开发
React的工程化/组件化开发 我们可以基于 webpack 自己搭建一套工程化打包的架子, 但是这样非常的 麻烦/复杂; React官方, 为我们提供了一个脚手架: create-react-app 脚手架: 基于它创建项目, 默认就把 webpack 的打包规则已经处理好啦, 把一些项目需要的基本文件也都创建好了.
搭建React
- 使用 create-react-app
# 项目名称要遵守npm包命名规范: 使用 "数字 小写字母 _" 命名
npx create-react-app 项目名称
cd 项目名称
npm start目录结构 node_modules src: 后续编写的代码, 几乎都是放在此目录下( 打包的时候, 一般只针对这个目录下的代码进行处理 ) index.js: 入口文件 ...(其余的都可以删除) public: 放页面模版 index.html: 应用模版 ...(其余的都可以删除) package.json package-lock.json
vite创建react
- 使用 Vite(更现代的构建工具)
npm create vite@latest 项目名称 -- --template react
cd 项目名称
npm install
npm run dev
# 也可以直接执行 跟着提示选择react
npm create vite@latest一个React项目中, 默认会安装: react: React框架的核心 react-dom: React视图渲染的核心 [ 基于React构建WebApp(HTML页面) ] react-scripts: 脚手架为了项目目录看起来干净一些, 把webpack打包的规则以及相关的插件等 都隐藏到了node_modules目录下
创建 TypeScript 应用
npx create-react-app my-app --template typescript命令
# 启动项目
npm start
# 打包项目
npm run build
# 单元测试
npm test
# 暴露配置项
# 是一个不可逆的操作, 即暴露出来,就不能在回去
# 一般真实的项目中都需要暴露处理
# 暴露配置项, 会多出两个目录 config 和 scripts
# 暴露出来后多数文件的第一行都会有红色提示语 可以删除 package.json 里面的 "babel": { "presets": [ "react-app"]} 配置 就没有了
npm run eject{
...
"babel": {
"presets": [
"react-app"
]
}
}webpack
- 别名配置
// link: config/webpack.config.js
alias:{
// 别名配置 只要是src下面的目录都可以使用 @ 来替换
'@': paths.appSrc,
}- 修改域名
默认是 localhost
// link: scripts/start.js
// 修改端口号
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; // 默认3000
// 修改域名
const HOST = process.env.HOST || '0.0.0.0'; // 将 0.0.0.0 修改成本地的端口号 127.0.0.1
// 若想直接修改环境变量步骤
// 步骤一: 安装 npm i cross-env
// 步骤二: 在 package.json 修改启动项命令 "start": "cross-env PORT=8080 node scripts/start.js",
// 配置环境变量-参考文档: https://blog.csdn.net/gitblog_00125/article/details/150985189- 修改浏览器兼容
主要修改 package.json 里面的 browserslist 属性值
- 跨域代理 link: config/webpackDevServer.config.js
// 步骤一: 安装 npm i http-proxy-middleware
// 步骤二: 在src下面创建 setupProxy.js 文件
// 步骤三; 编写 setupProxy.js 文件
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
// 可以写多个代理
app.use(
createProxyMiddleware("/api", {
target: 'http://www.example.org',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
},
})
);
app.use(
createProxyMiddleware("/fen", {
target: 'http://www.example.org',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/fen': ''
},
})
);
};MVC MVVM
MVC和MVVM模式详解+对比 React框架采用的是MVC体系; Vue框架采用的是MVVM体系. MVC: Model(数据层) + View(视图层) + Controller(控制层) MVVM: Model(数据层) + View(视图层) + ViewModel(数据/视图监听层)
JSX
JSX: JavaScript + xml(html) 把JS和HTML标签混合在一起了. vscode如何支持JSX语法(格式化, 快捷提示...) [1.直接将.js文件的后缀名修改成.jsx] 在HTML中嵌入"JS表达式", 需要基于"{ }胡子语法" 在ReactDOM.createRoot()的时候, 不能直接把HTML/BODY作为根容器, 需要指定一个额外的盒子(eg: #root) 每一个构建的视图, 只能有一个"根节点", 可以使用"<></>(空文档标记标签) 或者 div等标签"包裹起来 { }胡子语法中嵌入不同的值,所呈现出来的特点: number / string: 值是啥,就渲染出来啥 boolean / null / undefined / Symbol / BigInt: 渲染的内容为空 给元素设置样式 行内样式: 使用胡子语法, 小驼峰命名规则 style={ {fontSize:15px} } 使用类名样式: class="box" 引用外部样式表(需要在考前引入 import "./index.scc")
// React语法核心
import React from 'react';
// 构建HTML(WebApp)的核心
import ReactDOM from 'react-dom/client';
// 获取页面中#root的容器, 作为 "根" 容器
const root = ReactDOM.createRoot(document.getElementById('root'));
// 基于 render方法 渲染我们缩写的视图, 把渲染后的内容, 全部插入到 #root 中进行渲染
root.render(
<React.StrictMode>
<div>欢迎来到React世界</div>
</React.StrictMode>
);动态渲染数据
稀疏数组 与 密集数据
const dataList = [
{id:1,text:'欢迎来带React的世界'},
{id:2,text:'React胡子语法很重要'},
{id:3,text:'行内样式需要使用小驼峰的形式的'},
{id:4,text:'MVC体系 和 MVVM体系'},
]
function Lis() {
return dataList.map((item) => {
return <li key={item.id}> {item.id} {item.text} </li>
})
}
export default function Home() {
return (
<>
<div>我是首页的内容</div>
<ul>
<Lis />
</ul>
{/*
扩展需求: 没有数组,就是就是想单独循环五次
使用 new Array()创建数组 fill()方法向数组里面填充内容
稀疏数组: 稀疏数组是指数组中包含未定义的元素,或者说其中某些位置没有被分配值。
new Array(5) 创建的就是稀疏数组
密集数组: 指数组中的元素占据了连续的内存空间,并且数组的长度与数组中元素的数量相等。换句话说,密集数组中没有未定义的元素,每个索引位置都有一个明确的值。
new Array(1,2) new Array('6') new Array(null) 创建的都是密集数组
*/}
<hr />
{
new Array(5).fill(null).map((_, index) => {
return <button>按钮{ index+1}</button>
})
}
</>
)
}关于JSX底层处理机制 BABEL-官网 - virtualDOM
虚拟DOM(virtualDOM) 和真实DOM
- 步骤一: 把我们编写的JSX语法, 编译为 虚拟DOM对象[virtualDOM] 虚拟DOM对象: 框架自己内部构建的一套对象系统(对象的相关成员都是React内部规定的), 基于这些属性描述出, 我们所构建视图中的, DOM节点的相关特征!
- 步骤一: 基于 babel-preset-react-app 把JSX编写为 React.createElement(...)这种格式
- 步骤二: 再把 createElement 方法执行, 创建出virtualDOM元素.
- 步骤二: 把构建的virtualDOM渲染为真实DOM 真实DOM: 浏览器页面中, 最后渲染出来, 让用户看见的DOM元素!
- 补充说明: 第一次渲染页面是直接从virtualDOM==>真实DOM; 但是后期视图更新的时候, 需要经过一个DOM--DIFF的对比, 计算出补丁包PATCH(两次视图差异的部分), 把PATCH补丁包进行渲染!
import React from 'react'
// 创建button按钮
React.createElement('button',null,'提交')函数组件
在SRC目录下, 新建一个目录views, 在创建一个 xxx.jsx 的文件, 即创建了一个组件; 我们在此文件中, 创建一个函数, 让函数返回JSX视图( 或者JSX元素、virtualDOM虚拟DOM对象); 这就是创建了一个"函数组件".
调用: 基于es module 规范, 导入创建的组件(可忽略.jsx后缀名), 然后像写标签一样调用这个组件即可.
命名: 组件的名字, 一般采用PascalCase(大驼峰命名法)这种方式命名.
props
调用组件, 传递进来的属性是"只读"的.( 原理: props对象被冻结了 ) Object.isFrozen(props)==> true 该对象被冻结了 获取: props.xxx 修改: props.xxx = xxx ==> 会报错 作用: 父组件(index.jsx)调用子组件(DemoOne.jsx)的时候, 可以基于属性, 把不同的信息传递给子组件, 子组件接受相应的属性值, 呈现出不同的效果, 让组件复用性更强. 虽然对于传递进来的属性, 我们不能直接修改, 但是可以做一些规则校验.
属性设置默认值 const { title = '嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻', x = 0 } = props 设置其他规则 例如: 数据值格式、是否必传...(依赖于官方的一个插件: prop-types)
// defaultProps propTypes 这两个API在react19中已经废除了
import PropTypes from "prop-types";
// 定义一个函数组件
const DemoOne = function DemoOne(props) {
const { title , x = 0 } = props
console.log(title, x);
return <div className="demo-box">
<h2>{title}</h2>
</div>
}
// 通过把函数当做对象, 设置静态的私有属性方法, 来给其设置属性的校验规则
DemoOne.defaultProps = { // 该方法设置了没有效果 解决办法: 直接在获取的中赋值默认值 const { title = '嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻', x = 0 } = props
title: '嘻嘻嘻嘻嘻嘻',
x:5
}
// 对props设置类型 是否必传
DemoOne.propTypes = {
// 类型是字符串 必传
title: PropTypes.string.isRequired,
// 类型是数值类型
x: PropTypes.number
}你在 React 19 中遇到的 propTypes 和 defaultProps 不生效的问题,并不是你的代码写错了,而是 React 19 正式移除了这两个 API。
根据 React 19 的升级指南和多个技术社区的确认,propTypes(用于运行时类型检查)和函数组件上的 defaultProps(用于设置默认属性值)已经被完全移除,不再起作用 。这解释了为什么你定义的初始值和类型校验都没有效果。
类型定义与必传校验:拥抱 TypeScript React 官方推荐使用 TypeScript 来替代 prop-types 包,在编译时进行类型检查 。这不仅能捕获类型错误,还能提供更好的 IDE 智能提示。 prop-types 教程
// 使用 TypeScript 定义 props 的类型和是否必传
interface DemoOneProps {
title: string; // 必传(没有问号)
x?: number; // 可选(有问号)
}
function DemoOne({ title, x }: DemoOneProps) {
// 组件逻辑...
return <div>{title} - {x}</div>;
}对象的冻结、密封、扩展
扫盲知识点:关于对象的规则设置 冻结 冻结对象:Object.freeze(obj) 检测是否被冻结:Object.isFrozen(obj)=>true/false 被冻结的对象:不能修改成员值、不能新增成员、不能删除现有成员、不能给成员做劫持(object.defineProperty)
密封 密封对象:Object.seal(obj) 检测是否被密封:Object.isSealed(obj) 被密封的对象:可以修改成员的值,但也不能删、不能新增、不能劫持!!
扩展 把对象设置为不可扩展:Object.preventExtensions(obj) 检测是否可扩展:Object.isExtensible(obj) 被设置不可扩展的对象: 除了不能新增, 其余的都可以
// 正常的对象 可以对 对象 添加属性或者删除属性
obj.z = 30;
delete obj.y;
console.log("obj= ", obj);
const obj1 = {
x: 10,
y: 20
}
// 冻结对象
Object.freeze(obj1)
// 对象冻结后就不能操作对象了
// 报错信息: TypeError: Cannot add property z, object is not extensible
obj1.z = 30;
delete obj1.y;
console.log("obj1= ", obj);
// 检测对象是否被冻结
Object.isFrozen(obj) // 放回值: true(被冻结) / false(没有被冻结)父组件 子组件 props
// 子组件 home.tsx
interface Props {
title: string,
x?: number
}
// 传递属性
export const Home = (props: Props) => {
const { title, x = 5 } = props
return (
<>
<div>{title}</div>
<p> {x}</p>
</>
)
}// 父组件 App.tsx
import { Home } from "./views/home"
function App() {
return (
<>
<Home title="哈哈哈哈哈哈哈哈哈哈" x={666} />
<Home title="嘿嘿嘿嘿嘿嘿嘿嘿" />
</>
)
}
export default App插槽
// 子组件
import { Children } from "react";
import type { ReactElement, ReactNode } from "react";
interface Props {
title: string,
x?: number,
// ReactNode是最全面的children类型,可以接受React能够渲染的任何内容:
// JSX元素 字符串 数字 布尔值 null undefined React元素数组 | ReactNode
children?: ReactNode[] | ReactElement
}
// 函数组件
export const Home = (props: Props) => {
const { title, x = 5 } = props
// const 定义的值是常量 需要使用let把children结构出来
let { children } = props
// 对children进行处理 让他变成数组
children = Children.toArray(children)
console.log("children", children);
return (
<>
{children[0]}
<div>{title}</div>
<p>{x}</p>
{/* 获取所有的子节点 类似于Vue的插槽 */}
{children[1]}
</>
)
}// 父组件
import { Home } from "./views/home"
function App() {
return (
<>
<Home title="哈哈哈哈哈哈哈哈哈哈~~~" x={666}>
<span>耶耶耶耶耶==</span>
<span> 哟哟哟哟哟==</span>
</Home>
<hr />
<Home title="嘿嘿嘿嘿嘿嘿嘿嘿~~~" />
<hr />
<Home title="嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻~~~">
<span>啧啧啧啧==</span>
</Home>
</>
)
}
export default App具名插槽
React需要自己实现插槽; Vue不需要自己实现, 直接配置就可以了.
// 子组件
import { Children } from "react";
import type { ReactElement, ReactNode } from "react";
interface Props {
title: string,
x?: number,
// ReactNode是最全面的children类型,可以接受React能够渲染的任何内容:
// JSX元素 字符串 数字 布尔值 null undefined React元素数组 | ReactNode
children?: ReactNode[] | ReactElement
}
// 函数组件
export const Home = (props: Props) => {
const { title, x = 5 } = props
// const 定义的值是常量 需要使用let把children结构出来
let { children } = props
// 对children进行处理 变成数组
children = Children.toArray(children)
const header = children.filter((child: any) => child.props!.slot == 'header')
const footer = children.filter((child: any) => child.props!.slot == 'footer')
const center = children.filter((child: any) => child.props!.slot != 'header' && child.props!.slot != 'footer' )
// 若想使用any来定义类型 并不报错 在vite.config.ts配置以下内容
// 配置一下的规制: 定义any类型 不会有提示信息 建议不要配置一下的内容
// rules: {
// "@typescript-eslint/no-explicit-any": "error"
// }
return (
<>
{header}
<div>{title}</div>
{center}
<p>{x}</p>
{/* 获取所有的子节点 类似于Vue的插槽 */}
{footer}
</>
)
}// 父组件
import { Home } from "./views/home"
function App() {
return (
<>
<Home title="哈哈哈哈哈哈哈哈哈哈~~~" x={666}>
<span slot="footer">我是页脚==</span>
<span>中间的==</span>
<span slot="header">我是页眉==</span>
</Home>
<hr />
<Home title="嘿嘿嘿嘿嘿嘿嘿嘿~~~" />
<hr />
<Home title="嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻~~~">
<span>啧啧啧啧==</span>
</Home>
</>
)
}
export default App在TS中尽量不要使用any 下面是最终方案
// 子组件
import { Children, isValidElement } from "react";
import type { ReactElement, ReactNode } from "react";
interface Props {
title: string,
x?: number,
// ReactNode是最全面的children类型,可以接受React能够渲染的任何内容:
// JSX元素 字符串 数字 布尔值 null undefined React元素数组 | ReactNode
children?: ReactNode[] | ReactElement
}
// interface ChildProps { // 使用 PascalCase 命名法
// slot: string
// }
// 函数组件
export const Home = (props: Props) => {
const { title, x = 5 } = props
// const 定义的值是常量 需要使用let把children结构出来
let { children } = props
// 对children进行处理 变成数组 Children 首字母大写的
children = Children.toArray(children)
let header, footer;
const center: Array<ReactNode> = [];
children.map((child) => {
if (isValidElement(child)) {
// 若属性属性比较少, 可以直接定义, 不使用接口来定义数据类型
const props = (child as ReactElement<{slot:string}>).props;
// const props = (child as ReactElement<ChildProps>).props;
if (props.slot == 'header') {
header = child
} else if (props.slot == 'footer') {
footer = child
} else {
center.push(child)
}
}
})
// const footer = children.filter((child: any) => child.props!.slot == 'footer') // 会有类型报错
return (
<>
{header}
<div>{title}</div>
{center}
<p>{x}</p>
{/* 获取所有的子节点 类似于Vue的插槽 */}
{footer}
</>
)
}// 父组件
import { Home } from "./views/home"
function App() {
return (
<>
<Home title="哈哈哈哈哈哈哈哈哈哈~~~" x={9999999}>
<span slot="footer">我是页脚==</span>
<div>
<p>主要内容6666==</p>
</div>
<div>
<p>66666666666==</p>
</div>
<div>
<p>55555555555==</p>
</div>
<span slot="header">我是页眉==</span>
</Home>
<hr />
<Home title="嘿嘿嘿嘿嘿嘿嘿嘿~~~" />
<hr />
<Home title="嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻~~~">
<span>啧啧啧啧==</span>
</Home>
</>
)
}
export default App组件封装
封装一个简单的dialog组件
// dialog 组件
import {Children} from "react";
import type { ReactElement, ReactNode } from "react";
import "./index.scss"
interface Props{
title?: string,
content: string,
children?:ReactNode[] | ReactElement
}
const Dialog = (props: Props) => {
// 获取传递的插槽信息
const { title = '温馨提示', content } = props
let { children } = props
// 将children转化为数组的形式 即使没有传值也是一个[ ]
children = Children.toArray(children)
return (
<div className="Dialog-box">
<div className="header">
<h3>{title}</h3>
<span>X</span>
</div>
<div className="main">{content}</div>
{
children.length ? <div className="footer">{ children }</div> :null
}
</div>
)
}
export default Dialog;.Dialog-box{
margin: 10px;
border: 1px solid #eee;
border-radius: 8px;
.header{
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
border-bottom: 1px solid #eee;
padding: 0 10px 0 15px;
}
.main{
padding: 20px 15px;
}
.footer{
display: flex;
justify-content: end;
padding: 0 10px 20px 0;
}
}
button{
width: 100px;
height: 30px;
margin-left: 20px;
border-radius: 4px;
background: linear-gradient(135deg, #6253e1, #04befe);
border: none;
color: #fff;
}// 在 App.tsx 里面使用
import Dialog from "./components/Dialog"
import "./components/Dialog/index.scss"
function App() {
return (
<>
<Dialog title="友情提示" content="大家出门做好个人防护!!!"/>
<Dialog content="没事在家学习React教程">
<button>确认</button>
</Dialog>
<Dialog title="警告" content="确认删除此条数据吗?">
<button>取消</button>
<button>确认</button>
</Dialog>
</>
)
}
export default App静态组件( 函数组件 )
函数组件是'静态组件', 第一次渲染组件, 把函数执行. 会产生一个私有的上下文; 把解析出来的props(含children) 传递过来 (但是被冻结了); 对函数返回的JSX元素(virtualDOM)进行渲染. 即, 函数组件第一次渲染完毕后, 组件的内容不会随着某些操作, 在更新视图, 所有称他为静态组件. 除非在父组件中, 重新调用这个函数组件( 可以传递不同的属性信息 )
// 投票组件
interface Props{
title: string,
}
const Vote = (props: Props) => {
const { title } = props;
let supNum = 10, oppNum = 5;
return (
<div>
<h2>{title}</h2>
<p>总人数: {supNum + oppNum}</p>
<p>支持人数: { supNum }</p>
<p>反对人数: {oppNum}</p>
<div>
<button onClick={() => {
supNum++
{/* 打印的值改变了 但是视图渲染的值并没有改变 */}
console.log('supNum: ', supNum);
}}>支持</button>
<button onClick={() => {
oppNum++
console.log('oppNum: ', oppNum);
}}>反对</button>
</div>
</div>
)
}
export default Vote;类组件
首先简单学习一下类class
class Parent{
// new的时候, 执行的构造函数(可写可不写): 需要传递过来的实参信息, 才需要设置constructor
constructor(x:number, y:number) {
console.log(x, y);
// this创建的实例
this.total = x + y
// this.num=200
}
// 等价于 this.num=200 给实例创建私有属性
num = 200;
getNum = () => {
// 箭头函数没有自己的this, 所用到的this是宿主环境中的
// 这是打印的this是当前创建的实例
console.log("this", this);
};
sum() {
// 类似于 sum = function sum(){} 不是箭头函数
// 它是给Parent.prototype上设置公共的方法 ( sum函数是不可枚举的 )
}
// 把构造函数当做一个普通对象, 为其设置静态的私有属性方法
static avg = 1000;
static average(){}
}
// 在外部手动给构造函数原生上设置公共的属性
Parent.prototype.yy = 5000;
const p = new Parent(15, 20);
console.log(p);
console.dir(Parent);// 投票组件 vote 类组件( 动态组件 )
/**
* 创建类组件(动态组件)
* 创建一个构造函数(类), 要求必须继承React.Component/PureComponent这个类
* 我们习惯于使用ES6中的class创建类(因为方便)
* 必须当前类设置一个render的方法(放在其原型上) 在render方法中, 放回需要渲染的视图
*
*
*/
import { Component } from "react"
import type { ReactNode } from "react"
// 属性
interface Props{
title: string,
content?:string
}
// 状态
interface State{
supNum:number,
oppNum:number,
}
class VoteClass extends Component<Props,State> {
// 对传递的值设置默认值 不建议这样使用
// 这样使用后 Props 所有的属性都是可选的
// static defaultProps:Partial<Props> = {
// content:'我就是默认内容'
// }
// 一般不需要写
// constructor(props: Props) {
// super(props)
// }
// 初始化状态
state:State = {
supNum: 20,
oppNum: 15
}
// 修改state 并实现视图更新的方法有以下两种
// 1. this.setState() 部分更新 推荐使用的方式
// 2. this.forceUpdate() 强制更新
// 触发 render 周期函数: 渲染
render(): ReactNode {
// console.log("render 渲染视图");
console.log(this);
// 推荐使用这样赋值 设置默认值
const { title, content = '简单介绍' } = this.props, { supNum, oppNum } = this.state;
// 初始化状态
return (
<div>
<h2>{title}</h2>
<p>{content}</p>
<p>总人数: { supNum + oppNum}</p>
<p>支持人数: { supNum}</p>
<p>反对人数: { oppNum}</p>
<div>
<button onClick={() => {
// supNum++ // 视图不会更新
this.setState({
supNum:supNum+1
})
}}>支持</button>
<button onClick={() => {
this.setState({
oppNum:oppNum+1
})
}}>反对</button>
</div>
</div>
)
}
// 不建议使用该周期函数 控制台有黄色警告⚠️
// componentWillMount(): void {
// console.log('第一次运行之前');
// }
// componentDidMount(): void {
// console.log('第一次运行结束之后');
// }
}
export default VoteClass;
// 简单学习一下class
// class Parent{
// // new的时候, 执行的构造函数(可写可不写): 需要传递过来的实参信息, 才需要设置constructor
// constructor(x:number, y:number) {
// console.log(x, y);
// // this创建的实例
// this.total = x + y
// // this.num=200
// }
// // 等价于 this.num=200 给实例创建私有属性
// num = 200;
// getNum = () => {
// // 箭头函数没有自己的this, 所用到的this是宿主环境中的
// // 这是打印的this是当前创建的实例
// console.log("this", this);
// };
// sum() {
// // 类似于 sum = function sum(){} 不是箭头函数
// // 它是给Parent.prototype上设置公共的方法 ( sum函数是不可枚举的 )
// }
// // 把构造函数当做一个普通对象, 为其设置静态的私有属性方法
// static avg = 1000;
// static average(){}
// }
// // 在外部手动给构造函数原生上设置公共的属性
// Parent.prototype.yy = 5000;
// const p = new Parent(15, 20);
// console.log(p);
// console.dir(Parent);
// class Parent extends Component{
// x = 100;
// getX() { };
// }
// const p = new Parent(10, 20);
// console.log("p= ",p);类组件的第一次渲染的底层逻辑
周期函数的执行顺序
willMount --> render --> didMount
class VoteClass extends Component<Props,State> {
// 不建议使用该周期函数 控制台有黄色警告⚠️
componentWillMount(): void {
console.log('第一次运行之前');
}
// 触发render周期函数 渲染
render() {
console.log("render 渲染视图");
}
componentDidMount(): void {
// 已经把virtualDOM变为真实DOM了( 所以我们可以获取真实DOM了 )
console.log('第一次运行结束之后');
}
}组件更新的逻辑 ( 当修改了相关状态,组件会更新 )
周期函数的执行顺序
showUpdate --> willUpdate --> render --> didUpdate
class VoteClass extends Component<Props,State> {
// 1.此周期函数也是不安全的 在这个周期状态还没有被修改
componentWillUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>, nextContext: any): void {
console.log('我在更新之前执行该函数');
}
// 2.修改状态/属性值( 让this.state.xxx改为最新的值 )
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>, nextContext:Readonly<object>): boolean {
// nextState: 存储要修改的最新状态
// this.state: 存储的还是函数修改前的状态( 此时状态还没有改变 )
console.log( nextState," ==666== ", this.state);
// 此函数周期需要放回 true/false
// true: 允许更新, 会继续执行下一个操作
// false: 不允许更新, 接下来啥都不处理
return true;
}
// 3.触发 render 周期函数: 组件更新
// 按照最新的状态/属性, 把返回的TSX编译为virtualDOM
// 和上一次渲染出来的virtualDOM进行比较( DOM-DIFF )
// 把差异的部分进行渲染( 渲染为真是的DOM )
render() {
console.log("render 渲染视图");
return <div>6666</div>
}
// 4.更新完毕
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any): void {
console.log('组件更新完毕');
}
}父组件更新 深度优先原则 组件更新的逻辑(第二种: 父组件更新, 触发子组件更新) 深度优先原则
函数组件 类组件
函数组件是“静态组件”: 组件第一次渲染完毕后,无法基于“内部的某些操作”让组件更新「无法实现“自更新”;但是,如果调用它的父组件更新了,那么相关的子组件也一定会更新「可能传递最新的属性值进来」 函数组件具备:属性... 优势: 比类组件处理机制简单, 渲染速度快
类组件是“动态组件”: 组件在第一渲染完毕后,除了父组件更新可以触发其更新外,我们还可以通过:this.setState()修改状态或者this.forceUpdate()等方式,让组件实现“自更新”!! 类组件具备:属性、状态、周期函数、ref... { 几乎组件应该有的东西它都具备 } 优势: 功能强大
Hooks组件 推荐使用: 具备了函数组件和类组件的各自优势
Component、PureComponent
import { Component, PureComponent, type ReactNode } from "react";
// 继承Component
export class Demo extends Component {
// 状态
state = {
arr: [10, 20, 30] // 0x001
}
// 类组件 render周期函数 必须要有 并且还需要有放回值
render(): ReactNode {
// 将arr结构出来
const { arr } = this.state // arr的堆地址依旧一样 0x001
return (
<>
<div>
{arr.map((item: number, index: number) => {
return (<span key={index} style={{ display: 'inline-block', width: '100px', height: '100px', margin: '10px', background: 'pink' }}>{item}</span>)
})}
</div>
<button onClick={() => {
arr.push(arr[arr.length -1] + 10)
this.setState({
// 最新修改的地址依旧还是 0x001( 状态地址并没有改变 )
arr // ==> arr : arr
})
console.log("arr= ",arr);
}}>新增SPAN</button>
</>
)
}
// 需要手动添加
// shouldComponentUpdate(nextProps: Readonly<{}>, nextState: Readonly<{}>): boolean {
// const { props, state } = this
// return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
// }
}
// 继承PureComponent
export class PureDemo extends PureComponent {
// 状态
state = {
arr: [10, 20, 30] // 0x001
}
// 类组件 render周期函数 必须要有 并且还需要有放回值
render(): ReactNode {
// 将arr结构出来
const { arr } = this.state // arr的堆地址依旧一样 0x001
return (
<>
<div>
{arr.map((item: number, index: number) => {
return (<span key={index} style={{ display: 'inline-block', width: '100px', height: '100px', margin: '10px', background: 'pink' }}>{item}</span>)
})}
</div>
<button onClick={() => {
arr.push(arr[arr.length -1] + 10)
this.setState({
// 最新修改的地址依旧还是 0x001( 状态地址并没有改变 )
arr // ==> arr : arr
})
// 以下两种方式可以 更新视图 需要知道浅比较规则
// 1. this.forceUpdate() // 强制更新 不推荐使用
// 2. this.setState({ arr: [...arr] }) // 我们让arr状态值变为一个新的数组(即新地址)
console.log("arr= ",arr);
}}>新增SPAN</button>
</>
)
}
// 浅比较 默认就有 自己不需要添加
// shouldComponentUpdate(nextProps: Readonly<{}>, nextState: Readonly<{}>): boolean {
// const { props, state } = this
// return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
// }
}
/**
* Component 和 PureComponent 区别:
* 1.PureComponent会给组件默认加一个shouldComponentUpdate周期函数
* 在此周期函数中, 它对新老的属性/状态 会做一个浅比较
* 如果经过浅比较, 发现属性和状态并没有改变, 则放回false(即不继续更新组件); 有变化才会更新
*
*
* */浅比较
/**
* 浅比较: 只比较对象的第一级, 对于深层次内容, 不会在进行比较.
*/
const obj = {
x: 100
};
const objA = {
x: 10,
y: obj,
arr:[100,200,300]
}
const objB = {
x: 10,
y: obj,
arr:[100,200,300]
}
// 检查是否为对象
const isObject = (obj: object) => {
return obj !== null && /^(object|function)$/.test(typeof obj)
}
/**
* @param objA
* @param objB
* @returns boolean
* false: 不一样
* true: 一样
*/
const shallowEqual = (objA:any, objB:any) => {
// 1.比较objA和objB是不是对象 只要有一个不是对象就返回false
if (!isObject(objA) || !isObject(objB)) return false;
// 两个都是对象
// 2.比较两个对象是不是一样的 一样放回true
if (objA === objB) return true;
// 两个对象不一样
// 3.比较成员数量 不一样放回false
const keysA = Reflect.ownKeys(objA), keysB = Reflect.ownKeys(objB);
if (keysA.length !== keysB.length) return false;
// 4.数量一致, 在逐一比较内部的成员( 只比较一级: 浅比较)
for (let index = 0; index < keysA.length; index++) {
// 成员
const key = keysA[index];
// 如果一个对象中有这个成员, 一个对象中没有; 或者, 都有这个成员, 但成员值不一样; 都应该判定为不相同!
// Object.is(objA[key],objB[key]) ==> 可以比较 NAN 若是NAN放回值true
if (!objB.hasOwnProperty(key) || !Object.is(objA[key], objB[key])) return false;
}
return true;
}
const bool = shallowEqual(objA, objB);
console.log('bool',bool);ref
import { Component, createRef, type ReactNode, type Ref, type RefObject } from "react";
export class DemoA extends Component {
setRef: RefObject<HTMLDivElement | null> = createRef()
// box2: HTMLHeadingElement | null = null;
render(): ReactNode {
return (
<>
{/* 方式一: 推荐方式*/}
<div className="title" ref={this.setRef}>666666</div>
{/* 方式二: */}
{/* <h2 ref={(x: HTMLHeadingElement | null) => this.box2 = x} >温馨提示</h2> */}
</>
)
}
// 第一次渲染完毕( 即virtualDOM已经变成真实DOM ): 此时我们可以获取需要操作的DOM元素
componentDidMount(): void {
// console.log(document.querySelector('.title')); // 原生的获取方式
// React / Vue 可以使用 ref 来获取DOM元素
// console.log("this.setRef!.current",this.setRef!.current);
console.log("demoA= ", this, this.setRef.current); // 使用RefObject这样就不会报类型错误了
}
}
/**
* 受控组件: 基于修改数据/状态, 让视图更新, 达到需要的效果 (推荐)
*
* 非受控组件: 基于ref获取DOM元素, 我们操作DOM元素, 来实现需求和效果 (偶尔) eg: 图表
*
*/将ref定义在 类组件/函组件 上面
import { Component, createRef, forwardRef, type ReactNode, type Ref, type RefObject } from "react";
class Child1 extends Component{
state = {
x: 15,
y:20
}
render(): ReactNode {
return <div>子组件1</div>
}
}
const Child2 = forwardRef(function Child2(props, ref) {
console.log(props, "======", ref);
return <div>子组件2 <button ref={ref}>按钮</button> </div>
})
export class DemoB extends Component {
child1Ref: Ref<Child1> = createRef()
child2Ref:Ref<HTMLDivElement> = createRef()
render(): ReactNode {
return (
<div>
<Child1 ref={this.child1Ref}/>
<Child2 ref={this.child2Ref} />
</div>
)
}
// 第一次渲染完毕( 即virtualDOM已经变成真实DOM ): 此时我们可以获取需要操作的DOM元素
componentDidMount(): void {
console.log( "demob===",this);
}
}
/**
* 给元素标签设置ref 目的: 获取对应的DOM元素
* 给类组件设置ref 目的: 获取当前调用组件创建的实例(后续可以根据实例获取子组件中的相关信息)
* 给函数组件设置ref 没有值; 但是我们可以配合 React.forwardRef 实现ref转发 目的: 获取函数组件内部的某个元素
*/this.setState()
setState 是异步的
周期函数更新的生命周期
showUpdate --> willUpdate --> 修改状态 --> render --> didUpdate --> callback
import React from "react";
import { flushSync } from 'react-dom'
export class DemoC extends React.Component {
state = {
x: 10,
y: 5,
z: 0
}
handle = () => {
const { x, y, z } = this.state
// 周期函数的执行顺序
// showUpdate --> willUpdate --> 修改状态 --> render --> didUpdate --> callback
// 打印的值还是以前的值 所以说明setState是异步的
// this.setState({ x: x + 1 })
// console.log("x=",x); // 10
// this.setState({ y: y + 1 })
// console.log("y=",y); // 5
// this.setState({ z: z + 1 })
// console.log("z=",z); // 0
// 如果需要退出批处理,可以使用 flushSync
// flushSync(() => {
// this.setState({
// x: x + 1
// })
// this.setState({
// y: y + 1
// })
// console.log("state1=", this.state); // 10 5 0
// })
// console.log("state2=", this.state); // 11 6 0
// 在修改z之前, 确保x y都已经更改和让视图更新了
// this.setState({ z: this.state.x + this.state.y })
// console.log("state3=", this.state); // 11 6 0
// 需求: for循环遍历20次 但是视图只更新一次
for (let index = 0; index < 20; index++) {
// 渲染一次 z的值:1
// this.setState({
// z:this.state.z + 1 // 在每一轮循环的时候, x的状态并没有更新, 只是把他放在队列中, 放了20次 setState({x:1})
// })
// 渲染一次 z的值:20
this.setState((prevState:Readonly<{z:number}>) => {
return { z:prevState.z +1}
})
}
}
render(): React.ReactNode {
console.log("视图渲染了: render周期函数执行啦");
const { x, y, z } = this.state
return (<div>
<div>
x:{x} -- y:{y} -- z:{z}
</div>
<button onClick={this.handle}>累加</button>
</div>)
}
// 视图更新完成的周期函数
// componentDidUpdate(): void { // prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any
// console.log("视图更行完毕后 我被执行啦");
// }
// 是否更新视图
// shouldComponentUpdate(): boolean { // nextProps: Readonly<{}>, nextState: Readonly<{}>, nextContext: any
// return false; // true 更新 false 不更新
// }
}
/**
* 在React18以后,setState在任何地方执行,都是“异步操作
* React18以后中有一套更新队列的机制
* 基于异步操作,实现状态的“批处理
* 好处:
* 减少视图更新的次数,降低渲染消耗的性能
* 让更新的逻辑和流程更清晰&稳健
*/
/**
* this.setState(partialState, callback)
* partialState: 支持部分状态的更改
* this.setState({ x:x+1 })
*
* callback: 在状态更改后, 视图更新完毕后触发执行 ( 即执行了setState,callback一定会执行)
* 发生在componentDidUpdate周期函数之后
* DidUpdate会在任何状态更改后都触发执行该周期函数; 而callback的回调函数方式, 可以在指定状态更新后处理一些事情.
* 特殊情况: 即使我们基于shouldComponentUpdate阻止了状态/视图的更新, DidUpdate周期函数肯定不会在执行了, 但是我们设置的这个callback回调函数依然会被触发执行.
* 类似于Vue框架中的 $nextTick
*
* 在React18中, setState操作都是异步的(不论是在哪执行, 例如: 合成事件, 周期函数, 定时器...)
* 目的: 实现状态的批处理(统一处理)
* 有效减少更新次数, 降低性能消耗
* 有效管理代码执行的逻辑顺序
* ...
*
* 原理: 利用了更新队列机制来处理的
* 在当前相同的时间段内, 遇到setState会立即放入到更新队列中
* 当所有的代码操作结束, 会"刷新队列": 把所有放入的setState合并在一起执行, 只触发一次视图更新
*/React 合成事件
synthetic 合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象:它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性! 事件列表
// synthetic 合成的 React的合成事件
// 合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象:它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性!
import React from "react";
export class DemoD extends React.Component{
// 普通函数 打印this是undefined
handle1(){ // DemoD.prototype ==> DemoD.prototype.handle = function handle(){}
console.log(this); // undefined
}
handle2(x: number, y: number, ev: React.MouseEvent<HTMLButtonElement>) {
// 只要方法经过了bind处理了, 那么最后一个实参, 就是传递的是合成事件对象
console.log(x, y , ev); // 10 20 合成事件对象
}
// 箭头函数
handle3 = (ev: React.MouseEvent<HTMLButtonElement>) => { // 实例.handle3=()=>{...}
console.log(this); // 当前的实例
console.log(ev); // SyntheticBaseEvent 合成事件对象 (React内部经过特殊处理,把各个浏览器的事件对象统一后, 构建的一个事件对象)
}
handle4 = (x: number, y: number, ev: React.MouseEvent<HTMLButtonElement>) => {
console.log(x, y , ev); // 10 20 合成事件对象
}
render(): React.ReactNode {
return (
<div>
<button onClick={this.handle1}>点击1</button>
<button onClick={this.handle2.bind(this,10,20)}>点击2</button>
<button onClick={this.handle3}>点击3</button>
{/* this.handle4.bind(参数1 , ...) 参数1传递什么值都可以( 即需要有个值来占位 ) */}
<button onClick={this.handle4.bind(null,10,20)}>点击4</button>
</div>
)
}
}
/**
* bind 和 call 的区别
* bind: bind 会返回一个新函数,这个新函数的 this 被永久绑定到指定的对象,而不会立即执行。你可以稍后调用这个新函数,并且也可以预先传入部分参数(柯里化)。
* call: call 会立即执行函数,并允许你传入一个对象作为 this,后续参数作为函数调用的参数列表。
*/
/**
* bind在React事件绑定中的运用
* 绑定的方法是一个普通函数,需要改变函数中的this实例, 此时需要用到bind( 推荐使用箭头函数 )
* 想给函数传递指定的实参,可以基于bind预先处理( bind会把事件对象以最后一个实参传递给函数 )
*/
/**
* onClick --> 是冒泡操作
* onClickCapture --> 是捕获操作(即在普通的事件后面加Capture都是捕获操作)
*
*
*/点击事件在移动端有300毫秒的延迟,使用插件FastClick, 或者使用移动端专用的组件库
// 在入口文件 编写以下的代码 使用插件
import FastClick from 'fastclick'
FastClick.attach(document.body)如何动态绑定事件
Object.is(参数一: 参数二)
比较两个值是否一样, 相等返回true, 不相等返回false 特别注意: NaN === NaN 结果是false 使用Object.is(NaN,NaN) 结果为true
Hooks组件
在函数组中, 使用Hooks函数 推荐使用的组件 具备了函数组件和类组件的各自优势
React Hooks 组件, 就是基于React中新提供的Hook函数, 可以让函数组件动态化.
- useState 使用状态管理
- useEffect 使用周期函数
- useContent 使用上下文信息
函数组件每一次更新都是重新执行该函数. 和类组件的执行机制不一致.
useState
// 引起antd的按钮组件
import { Button } from 'antd'
import { useState } from 'react'
// import {flushSync} from 'react-dom' // flushSync(()=>{}) 会立即执行 让异步的操作变为同步的操作
interface Props{
title:string,
}
// 投票系统 官方的建议: 需要修改多个状态, 就把useState执行多次 是异步操作
export const VoteFunction = (props:Props) => {
const { title } = props
// 1.分别管理我们的状态值 官方推荐的方式
const [supNum, setSupNum] = useState(10), [oppNum, setOppNum] = useState(5);
const handle1 = (type: string) => {
// 支持人数
if (type == 'sup') {
setSupNum(supNum+1)
} else if (type == 'opp') { // 反对人数
setOppNum(oppNum+1)
}
}
// 2.我们也可以统一管理
const [state, setState] = useState({ supNum: 10, oppNum: 5 })
const handle2 = (type: string) => {
// 支持人数
if (type == 'sup') {
setState({
...state,
supNum:state.supNum+1
})
} else if (type == 'opp') { // 反对人数
setState({
...state,
oppNum:state.oppNum+1
})
}
}
// 支持人数
// const support = () => {}
// 反对人数
// const oppose = () => {}
return (
<>
<div className='m-20'>
{/* 状态分开管理 */}
<header >
<h2>{ title}</h2>
<span>参与人数: { supNum + oppNum}</span>
</header>
<main>
<p>支持人数: {supNum}</p>
<p>反对人数: {oppNum} </p>
</main>
<footer>
<Button type='primary' className='mr-10 mt-20' onClick={handle1.bind(null,'sup')}>支持</Button>
<Button type='primary' danger onClick={handle1.bind(null,'opp')}>反对</Button>
</footer>
</div>
<div className='m-20'>
{/* 状态统一管理 */}
<header >
<h2>{ title}</h2>
<span>参与人数: { state.supNum + state.oppNum}</span>
</header>
<main>
<p>支持人数: {state.supNum}</p>
<p>反对人数: {state.oppNum} </p>
</main>
<footer>
<Button type='primary' className='mr-10 mt-20' onClick={handle2.bind(null,'sup')}>支持</Button>
<Button type='primary' danger onClick={handle2.bind(null,'opp')}>反对</Button>
</footer>
</div>
</>
)
}export const Test = () => {
/**
* 下面执行的代码结果是: render渲染1: 渲染一次 x: 11
*/
console.log('render渲染1');
const [x1, setX1] = useState(10);
const handle1 = () => {
for (let i = 0; i < 10; i++) {
/**
* react内部的优化机制所导致的
* 若上一次的值和这一次的值一样将不会更新
*/
setX1(x1 + 1)
}
}
/**
* 需求: 如何实现函数执行一次; x更新10次,并x值为20
*/
console.log('render渲染2');
const [x2, setX2] = useState(10);
const handle2 = () => {
for (let i = 0; i < 10; i++) {
setX2((prev: number) => {
// prev: 存储上一次的状态值
console.log("prev: ",prev);
return prev + 1;
})
}
}
return (
<div>
<p>X1: {x1}</p>
<Button type='primary' className='mr-10 mt-20' onClick={handle1}>新增</Button>
<p>X2: {x2}</p>
<Button type='primary' className='mr-10 mt-20' onClick={handle2}>新增</Button>
</div>
)
}useState传递一个函数
/**
* 需求:
* 1. 父组件需要传递两个值 X/Y
* 2. 我们需要基于传递的属性值经过处理的结果作为初始值
*
* 若子组件需要父组件的值,并且需要经过特殊处理,最好使用函数的形式设置初始值.这样性能达到最佳
* 此时我们需要对初始值的操作, 进行惰性操作. 即只会执行一次.
*/
import { Button } from "antd"
import { useState } from "react";
interface Props {
x: number,
y: number,
}
export const HooksDemo = (props: Props) => {
console.log("props: ", props);
const [num, setNum] = useState(() => {
/**
* 使用函数 该函数只会执行一次 只要有初始值 就不会执行该函数了
*/
const { x, y } = props;
let total=0;
for (let i = x; i <= y; i++){
total += +String(Math.random()).substring(2)
}
console.log('total',total);
return total;
})
const handle = () => {
console.log('我被点击了');
setNum(num+1)
}
return (
<div>
<p>total: { num }</p>
<Button type="primary" onClick={handle} className="ml-20 mt-20">新增</Button>
</div>
)
}useEffect
/**
* 类组件生命周期回顾
* 第一次渲染的:willMount --> render --> didMount
* 更新后的执行顺序: showUpdate --> willUpdate --> 修改状态 --> render --> didUpdate --> callback
*
* useEffect: 在函数组件中, 使用生命周期函数.
* useEffect(callback): 没有设置依赖
* 第一次渲染完毕后, 执行callback, 等价于类组件的componentDidMount()生命周期函数
* 在组件每一次更新完毕后,也会执行callback, 等价于类组件的componentDidUpdate()生命周期函数
*
* useEffect(callback,[]): 设置了,但没有依赖
* 只有第一次渲染完毕后, 才会执行callback; 以后每一次视图更新完毕后, callback不再执行.
* 等价于类组件中的componentDidMount()生命周期函数
*
* useEffect(callback,[依赖的状态(可以多个状态,使用逗号分隔)]):
* 第一次渲染完毕会执行callback
* 当依赖的状态值(获取多个依赖状态中的一个)发生改变, 也会触发
* 但是依赖的状态没有发生变化, 在组件更新的时候, callback是不会执行的
*
*/
import React, { useEffect, useState } from 'react'
import { Button } from "antd"
export const HooksDemo1 = () => {
const [num, setNum] = useState(0), [x, setX] = useState(100);
const handle = () => {
console.log('点击啦!');
setNum(num + 1)
}
const handle1 = () => {
console.log('点击啦!');
setX(x + 1)
}
// 1.没有设置依赖 useEffect 生命周期函数
useEffect(() => {
// console.log('ok');
// 看一看useEffect在渲染之前还是之后
// console.log('获取dom元素: ' , document.querySelector('.ml-20')); // 拿到结果了 说明在第一渲染之后
console.log('@1: ', num); // 可以拿到最新的状态值
})
// 2.设置了,无依赖 类似与vue的onMount
useEffect(() => {
console.log('@2: ', num);
}, [])
// 3.设置了一个依赖
useEffect(() => {
console.log('@3: ', num);
}, [num])
// 4.设置了多个依赖
useEffect(() => {
console.log('@4: ', num, x);
}, [num, x])
// 5.callback放回一个函数
useEffect(() => {
return () => {
// 获取到上一次的值 类似于类组件的销毁阶段
console.log('@5: ',num, x)
}
})
return (<div className="ml-20">
<p>num: {num}</p>
<p>x: {x}</p>
<p>555555555~~~</p>
<Button type="primary" className="mt-20 mr-20" onClick={handle}>按钮</Button>
<Button type="primary" className="mt-20 " onClick={handle1}>按钮1</Button>
</div>)
}
export class Test extends React.Component {
componentDidMount(): void {}
componentDidUpdate(): void {}
}异步获取服务器数据
/**
*
*
*/
import { useEffect, useState } from 'react'
import { Button } from "antd"
// 模拟从服务器中异步获取
const queryData = () => {
return new Promise<Array<number>>((resolve) => {
setTimeout(() => {
resolve([10,20,30,40,50])
},1000)
})
}
export const HooksDemo2 = () => {
const [num, setNum] = useState(0);
const handle = () => {
setNum(num + 1)
}
/**
* 注意点:
* 1. useEffect必须在函数的最外层上下文中调用, 不能把其嵌入到 条件判断/循环 等操作语句中
*/
useEffect(() => {
if (num > 5) {
console.log('OK');
}
}, [num])
useEffect(() => {
// 方式一:
queryData().then((res) => {
console.log("res=",res);
})
// 方式二:
const next = async () => {
const data = await queryData();
console.log('data= ', data);
setNum(data[2])
}
next()
},[])
return (<div className="ml-20">
<p>num: {num}</p>
<p>555555555~~~</p>
<Button type="primary" className="mt-20 mr-20" onClick={handle}>按钮</Button>
</div>)
}useLayoutEffect
import React, { useState, useEffect, useLayoutEffect } from "react";
import { Button } from 'antd';
import './Demo.less';
const Demo = function Demo() {
// console.log('RENDER');
let [num, setNum] = useState(0);
/* useLayoutEffect(() => {
if (num === 0) {
setNum(10);
}
}, [num]); */
/*
useLayoutEffect会阻塞浏览器渲染真实DOM,优先执行Effect链表中的callback;
useEffect不会阻塞浏览器渲染真实DOM,在渲染真实DOM的同时,去执行Effect链表中的callback;
+ useLayoutEffect设置的callback要优先于useEffect去执行!!
+ 在两者设置的callback中,依然可以获取DOM元素「原因:真实DOM对象已经创建了,区别只是浏览器是否渲染」
+ 如果在callback函数中又修改了状态值「视图又要更新」
+ useEffect:浏览器肯定是把第一次的真实已经绘制了,再去渲染第二次真实DOM
+ useLayoutEffect:浏览器是把两次真实DOM的渲染,合并在一起渲染的
视图更新的步骤:
第一步:基于babel-preset-react-app把JSX编译为createElement格式
第二步:把createElement执行,创建出virtualDOM
第三步:基于root.render方法把virtualDOM变为真实DOM对象「DOM-DIFF」
useLayoutEffect阻塞第四步操作,先去执行Effect链表中的方法「同步操作」
useEffect第四步操作和Effect链表中的方法执行,是同时进行的「异步操作」
第四步:浏览器渲染和绘制真实DOM对象
*/
useLayoutEffect(() => {
console.log('useLayoutEffect'); //第一个输出
}, [num]);
useEffect(() => {
console.log('useEffect'); //第二个输出
}, [num]);
return <div className="demo"
style={{
backgroundColor: num === 0 ? 'red' : 'green'
}}>
<span className="num">{num}</span>
<Button type="primary" size="small"
onClick={() => {
setNum(0);
}}>
新增
</Button>
</div>;
};
export default Demo;
// ===========
import { useEffect, useState ,useLayoutEffect} from 'react'
import { Button } from "antd"
export const HooksDemo3 = () => {
console.log('render渲染');
const [num, setNum] = useState(0);
// 下面两种方式 setNum都会有警告⚠️ 不建议这样使用
// 错误:在效果中同步调用setState会触发级联渲染
// 真实DOM渲染2次 virtualDOM有2次
useEffect(() => {
if (!num) {
setNum(999)
}
},[num])
// 真实DOM渲染1次 virtualDOM有2次
useLayoutEffect(() => {
if (!num) setNum(900)
},[num])
return (<div className="ml-20">
<p style={{fontSize:30,color:num?'green':'red'}}>{num}</p>
<Button type="primary" className="mt-20 mr-20" onClick={() => {
console.log(55555);
setNum(0)
}}>按钮</Button>
</div>)
}useRef
/**
* 知识点回顾: 在类组件中我们基于ref可以做的事情
* 1. 赋值给一个标签: 可以获取DOM元素
* 2. 赋值给一个类子组件: 可以获取子组件的实例( 我们可以基于实例调用子组件中的方法和属性等 )
* 3. 赋值给一个函数子组件: 会报错(需要配合React.forwardRef实现ref转发,获取子组件中的某一个DOM元素)
*
* 设置ref推荐的方式: homeRef = React.createRef()
*
*
* 1. let box; ref={x => box=x}; 在外面定义一个变量box, 也可以获取dom元素(不推荐使用) 类组件和函数组都可以使用
* 2. const box: RefObject<HTMLDivElement | null> = React.createRef() ref={box} 类组件和函数组都可以使用
* 3. 函数组件有一个专门的hooks函数 useRef (推荐使用) const box2 = useRef(null) ref = {box2} 只能在函数组件中使用
*
*
* 总结:
* 类组件使用: React.createRef()
* 函数组件使用: useRef(null)
*
*/
import React, {useState,useEffect, useRef,type RefObject } from 'react'
import { Button } from "antd"
export const HooksDemo3 = () => {
console.log('render渲染');
const box: RefObject<HTMLDivElement | null> = React.createRef()
const box2 = useRef(null)
const [num, setNum] = useState(0);
useEffect(() => {
// console.log('box',box.current);
console.log('box', box2.current);
},[])
return (<div className="ml-20">
<p style={{ fontSize: 30, color: num ? 'green' : 'red' }} ref={box2}>{num}</p>
<Button type="primary" className="mt-20 mr-20" onClick={() => {
setNum(num + 1)
}}>按钮</Button>
</div>)
}
// ========== 从下面的案例中 函数组件使用useRef比较好
import React, { useState, useEffect, useRef } from "react";
import { Button } from 'antd';
import './Demo.less';
let prev1,
prev2;
const Demo = function Demo() {
let [num, setNum] = useState(0);
let box1 = useRef(null),
box2 = React.createRef();
if (!prev1) {
// 第一次DEMO执行,把第一次创建的REF对象赋值给变量
prev1 = box1;
prev2 = box2;
} else {
// 第二次DEMO执行,我们验证一下,新创建的REF对象,和之前第一次创建的REF对象,是否一致?
console.log(prev1 === box1); //true useRef再每一次组件更新的时候(函数重新执行),再次执行useRef方法的时候,不会创建新的REF对象了,获取到的还是第一次创建的那个REF对象!!
console.log(prev2 === box2); //false createRef在每一次组件更新的时候,都会创建一个全新的REF对象出来,比较浪费性能!!
// 总结:在类组件中,创建REF对象,我们基于 React.createRef 处理;但是在函数组件中,为了保证性能,我们应该使用专属的 useRef 处理!!
}
useEffect(() => {
console.log(box1.current);
console.log(box2.current);
});
return <div className="demo">
<span className="num" ref={box1}>{num}</span>
<span className="num" ref={box2}>哈哈哈</span>
<Button type="primary" size="small"
onClick={() => {
setNum(num + 1);
}}>
新增
</Button>
</div>;
};useImperativeHandle
useImperativeHandle 是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。
不使用forwardRef也可以接受子组件的ref返回的方法属性等
参考文档
/**
* 知识点回顾: 在类组件中我们基于ref可以做的事情
* 1. 赋值给一个标签: 可以获取DOM元素
* 2. 赋值给一个类子组件: 可以获取子组件的实例( 我们可以基于实例调用子组件中的方法和属性等 )
* 3. 赋值给一个函数子组件: 会报错(需要配合React.forwardRef实现ref转发,获取子组件中的某一个DOM元素)
*
* 设置ref推荐的方式: homeRef = React.createRef()
*
*
* 1. let box; ref={x => box=x}; 在外面定义一个变量box, 也可以获取dom元素(不推荐使用) 类组件和函数组都可以使用
* 2. const box: RefObject<HTMLDivElement | null> = React.createRef() ref={box} 类组件和函数组都可以使用
* 3. 函数组件有一个专门的hooks函数 useRef (推荐使用) const box2 = useRef(null) ref = {box2} 只能在函数组件中使用
*
*
* 类组件使用: React.createRef()
* 函数组件使用: useRef(null)
*
*
* useImperativeHandle
*
*/
import { useState, useRef, useImperativeHandle, useEffect, type RefObject } from 'react'
import { Button } from "antd"
interface ChildRef {
text: string,
setText: React.Dispatch<React.SetStateAction<string>>,
submit: () => void
}
interface Props {
title: string,
ref: RefObject<ChildRef | null>
}
const Child = ({ ref, ...props }: Props) => {
const [text, setText] = useState('你好世界');
const submit = () => {
console.log('我被点击啦66666666');
setText(String(Math.random()).substring(2))
}
useImperativeHandle(ref, () => {
return {
text,
setText,
submit
}
})
return (
<div>{props.title} 哈哈哈哈 {text}</div>
)
}
export const HooksDemo3 = () => {
const x: RefObject<ChildRef | null> = useRef(null)
useEffect(() => {
console.log("x= ", x);
})
const handle = () => {
console.log(x, "===");
// x.current.submit()
x.current?.setText('我就是玩呀')
console.log("x.current?.text", x.current?.text);
setTimeout(() => {
// 2秒过后执行该方法
x.current?.submit()
}, 2000)
}
return (<div className="ml-20">
<Child title="aaaaaaa" ref={x} />
<Button type="primary" className="mt-20 mr-20" onClick={handle}>按钮</Button>
</div>)
}父子通信
/**
* React的Hooks组件: 就是基于React新提供的Hook函数, 可以让函数组件动态化.
* Hooks函数的基本特征: 1.use开头函数. useXxxxx() 2.可以自定义
*
* 父传子: 父组件通过定义属性的方式传递值, 子组件通过参数接受父组件传递过来的值
*
* 子传父: 子组件通过useImperativeHandle函数暴露出去, 父组件通过定义ref的方式接受到子组件的属性已经方法等.
*/
import { Button } from "antd"
import React, { useEffect, useImperativeHandle, useRef, useState, type RefObject } from "react";
interface Props {
title: string, // 必传
total?: number, // 非必传
submit: () => void // 非必传
}
/**
* 1. 父传子 类组件:
*
* 在HooksDemo4父组件中 引入Child1子组件 将需要传递的值或方法通过属性的方式传递过来
* 类组件通过 this.props 来接收传递过来的值
*/
class Child1 extends React.Component<Props> { // 参数类型一:Props(props) 参数类型二: State(状态)
render(): React.ReactNode {
// console.log('类组件 child1: ', this);
const { title, total, submit } = this.props
return <div>
<p>我是 类组件 child1</p>
<p>{title}</p>
<span>{total}</span>
<Button type="primary" className="mt-20 mr-20" onClick={submit}>按钮1</Button>
</div>
}
}
/**
* 2. 父传子 函数组件:
*
* 在HooksDemo4父组件中 引入Child2子组件 将需要传递的值或方法通过属性的方式传递过来
* 函数组件通过 参数 来接收传递过来的值
*/
const Child2 = (props: Props) => {
// console.log("函数组件 child2: ",props);
const { title, total, submit } = props
return <div>
<p>我是 类组件 child1</p>
<p>{title}</p>
<span>{total}</span>
<Button type="primary" className="mt-20 mr-20" onClick={submit}>按钮2</Button>
</div>
}
/**
* 3. 子传父 类组件:
*
* 在HooksDemo4父组件中 引入Child3子组件 在子组件中写入 状态(state) 方法等
* 在父组件中使用 ref={child3Ref} 来获取子组件的状态方法 有实例(this)
*/
class Child3 extends React.Component {
state = {
title: '我是子组件的数据',
total: 666,
};
submit(val: string) {
console.log('我是类组件 子组件 val: ', val);
};
render(): React.ReactNode {
// console.log('类组件 child1: ', this);
return <div>
<p>我是 类组件 child3</p>
</div>
}
}
/**
* 4. 子传父 函数组件:
*
* 在HooksDemo4父组件中 引入Child4子组件, 定义对应的hooks函数
* 需要使用useImperativeHandle函数将需要的属性或方法暴露给父组件
*/
const Child4 = ({ref,...props}:{ref:RefObject<Props|null>,title?:string}) => {
const [title, setTitle] = useState('我是函数组件 子组件title 的默认值'), [total, setTotal] = useState(0);
const submit = () => {
setTitle('被修改了哟');
setTotal(789)
}
// 使用 hook函数暴露出去
useImperativeHandle(ref, () => {
return {
title,
total,
submit,
setTitle
}
})
useEffect(() => {
// console.log("ref",ref);
// console.log("props",props);
})
return <div>
<p>{title}</p>
<p>{ props.title}</p>
<p>{total}</p>
<p>我是 函数组件 child4</p>
</div>
}
export const HooksDemo4 = () => {
const child3Ref: RefObject<Child3 | null> = useRef(null), child4Ref: RefObject<Props | null> = useRef(null) ;
const handle = () => {
console.log('我被点击啦');
}
useEffect(() => {
// console.log("child3Ref: ", child3Ref, child3Ref.current?.state?.title);
// child3Ref.current?.submit('嘿嘿嘿嘿嘿嘿嘿嘿')
console.log("child4Ref: ", child4Ref, child4Ref.current?.title);
// child4Ref.current?.submit()
// child4Ref.current?.setTitle("78945612333121546787987")
})
return (<div className="ml-20">
<div>
{/* 父传子 */}
<Child1 title="我是父组件传递过来的 child1" total={999} submit={handle} />
<hr className="mt-20 mb-20" />
<Child2 title="我是父组件传递过来的 child2" total={888} submit={handle} />
<hr className="mt-20 mb-20" />
{/* 子传父 */}
<Child3 ref={child3Ref} />
<hr className="mt-20 mb-20" />
<Child4 ref={child4Ref} title="嘿呀嘿呀嘿黑呀" />
<hr className="mt-20 mb-20" />
</div>
<Button type="primary" className="mt-20 mr-20" onClick={handle}>按钮</Button>
</div>)
}useMemo
类似Vue的计算属性
import {Button} from 'antd'
import { useState,useMemo } from 'react'
export const HooksDemo5 = () => {
const [supNum, setSupNum] = useState(0), [oppNum, setOppNum] = useState(0), [hei, setHei] = useState(5);
/**
* 计算支持比例的时候
* 当我们点击 黑呀呀 按钮的时候
* 也会触发 下面的代码
*
* 需求: 需要只点击 支持 和 反对 按钮才会执行比率的计算
* 请使用useMemo(callback, dependencies) Hooks函数 能解决这个问题
* 具备'计算缓存' 类似与Vue的计算属性
*
*/
// let total = supNum + oppNum, ratio = '---';
// if (total) {
// ratio = (supNum / total * 100).toFixed(2) + '%'
// console.log('我被执行啦');
// }
const ratio = useMemo(() => {
const total = supNum + oppNum;
let ratio = '--'
if (total) {
ratio = (supNum / total * 100).toFixed(2) + '%'
console.log('我被执行啦');
}
return ratio
},[supNum,oppNum])
return <div style={{padding:20}}>
<p>支持人数: { supNum}</p>
<p>反对人数: { oppNum}</p>
<p>支持比率: { ratio}</p>
<p>hei: { hei }</p>
<Button type="primary" className="mt-20 mr-20" onClick={()=>setSupNum(supNum+1)}>支持</Button>
<Button type="primary" danger className="mt-20 mr-20" onClick={()=>setOppNum(oppNum+1)}>反对</Button>
<Button type="primary" className="mt-20 mr-20" onClick={()=>setHei(hei+5)}>黑呀呀</Button>
</div>
}useCallback
不能乱用, 会适得其反.
1.父组件嵌套子组件, 父组件要把一个内部的函数, 基于属性传递给子组件. 此时传递的这个方法, 我们基于 useCallback 处理一下. 子组件也需要处理一下 类组件(class Child extends React.PureComponent {}) 函数组件(const child = React.memo(()=>{}))
import {Button} from 'antd'
import { useState,useCallback } from 'react'
let prev;
export const HooksDemo6 = () => {
const [x, setX] = useState(0);
// const handle = () => { // 第一次堆内存 0x0001 ; 第二次堆内存 0x1001
// console.log('======');
// }
// if (!prev) {
// prev =handle
// } else {
// console.log(prev == handle); // false
// }
const handle = useCallback(() => { // 第一次堆内存 0x0001 ; 第二次堆内存 0x0001
console.log('========');
}, [])
if (!prev) {
prev =handle
} else {
console.log(prev == handle); // true
}
return <div style={{padding:20}}>
<p>{ x}</p>
<Button type="primary" className="mt-20 mr-20" onClick={()=>setX(x+1)}>累加</Button>
</div>
}useCallback 使用
import React, { useState, useCallback } from "react";
import { Button } from 'antd';
import './Demo.less';
/* 子组件 */
/* class Child extends React.PureComponent {
render() {
console.log('Child Render');
return <div>
我是子组件
</div>;
}
} */
const Child = React.memo(function Child(props) {
console.log('Child Render');
return <div>
我是子组件
</div>;
});
/* 父组件 */
// 诉求:当父组件更新的时候,因为传递给子组件的属性仅仅是一个函数「特点:基本应该算是不变的」,所以不想再让子组件也跟着更新了!
// + 第一条:传递给子组件的属性(函数),每一次需要是相同的堆内存地址(是一致的) . 基于useCallback处理!!
// + 第二条:在子组件内部也要做一个处理,验证父组件传递的属性是否发生改变,如果没有变化,则让子组件不能更新,有变化才需要更新 . 继承React.PureComponent即可「在shouldComponentUpdate中对新老属性做了浅比较」!! 函数组件是基于 React.memo 函数,对新老传递的属性做比较,如果不一致,才会把函数组件执行,如果一致,则不让子组件更新!!
const Demo = function Demo() {
let [x, setX] = useState(0);
// const handle = () => { }; //第一次:0x001 第二次:0x101 第三次:0x201 ...
const handle = useCallback(() => { }, []); //第一次:0x001 第二次:0x001 第三次:0x001 ...
return <div className="vote-box">
<Child handle={handle} />
<div className="main">
<p>{x}</p>
</div>
<div className="footer">
<Button type="primary" onClick={() => setX(x + 1)}>累加</Button>
</div>
</div>;
};
/* let prev;
const Demo = function Demo() {
let [x, setX] = useState(0);
/!*
const xxx = useCallback(callback,[dependencies])
+ 组件第一次渲染,useCallback执行,创建一个函数“callback”,赋值给xxx
+ 组件后续每一次更新,判断依赖的状态值是否改变,如果改变,则重新创建新的函数堆,赋值给xxx;但是如果,依赖的状态没有更新「或者没有设置依赖“[]”」则xxx获取的一直是第一次创建的函数堆,不会创建新的函数出来!!
+ 或者说,基于useCallback,可以始终获取第一次创建函数的堆内存地址(或者说函数的引用)
*!/
const handle = useCallback(() => {
// ...
}, []); //第一次:0x001 第二次:0x001 .....
if (!prev) {
prev = handle;
} else {
console.log(handle === prev); //true
}
return <div className="vote-box">
<div className="main">
<p>{x}</p>
</div>
<div className="footer">
<Button type="primary" onClick={() => setX(x + 1)}>累加</Button>
</div>
</div>;
}; */
export default Demo;自定义Hooks
import { Button } from 'antd'
import { useState, useEffect } from 'react'
/**
* 需求: 若使用useState定义的值是对象, 若想修改对象中的某一个值, 直接修改里面的某一个值, 会将其他属性属性值清空;
* 我们可以首先结构出以前的值和现在要修改得的值, 一起传递.
* 或者我们自定义Hook的方式来解决
*
* 自定义Hook useState
* 作用: 提取封装一些公共的处理逻辑
* 使用: 创建一个函数, 名字需要是 useXxx , 后期就可以在组件中调佣这个方法!
*
* Partial<T>: 传递的值可以是T中的任意属性
*/
interface State {
supNum: number,
oppNum: number,
}
const usePartialState = (initialValue: State): [State, (partialState: Partial<State>) => void] => {
const [state, setState] = useState(initialValue)
// setState: 不支持部分状态更改的
//
const setPartial = (partialState: Partial<State>) => {
setState({
...state,
...partialState
})
}
// 返回状态值和修改单个属性值的方法
return [state, setPartial]
}
/**
* 需求: 在组件第一次渲染完毕后, 统一处理一系列的问题
*
* 解决方案: 自定义Hook useEffect 统一处理页面标题
*/
const useDidMount = (title: string = "React学习教程") => { // title="React学习教程" title="React学习教程" / title: string = "React学习教程"
useEffect(() => {
document.title = title;
})
}
export const HooksDemo7 = () => {
const [state, setPartial] = usePartialState({
supNum: 10,
oppNum: 5
})
useDidMount()
const handle = (type: 'opp' | 'sup') => {
// 判断 type 是支持还是反对
if (type == 'sup') {
setPartial({ supNum: state.supNum + 1 });
} else if (type == 'opp') {
setPartial({ oppNum: state.oppNum + 1 })
}
}
return <div style={{ padding: 20 }}>
<p>支持人数: {state.supNum}</p>
<p>支持人数: {state.oppNum}</p>
<Button type="primary" className="mt-20 mr-20" onClick={handle.bind(null, 'sup')}>支持</Button>
<Button type="primary" danger className="mt-20 mr-20" onClick={handle.bind(null, 'opp')}>反对</Button>
</div>
}复合组件
组件化, 不同的组件组合成一个组件, 即复合组件. eg: 投票
useContext
上下文对象
样式私有化处理
在真实的项目中推荐使用:CSS Modules 和 Styled Components
1.内联样式
在组价上直接写, 真实项目不推荐使用. 内联样式是通过在JSX元素上使用style属性来直接应用CSS样式。这种方法适用于简单的样式调整。
const MyComponent = () => {
return <div style={{ color: 'blue', fontSize: '14px' }}>Hello World</div>
};CSS 文件
使用 xxx.scss 样式表. 最外层类名不一样, 人为有规范的, 有意识去遵守规则. 命名: 路径 + 组件
- css文件(style.scss)
.my-component {
color: blue;
font-size: 14px;
}- React组件
import './styles.css';
const MyComponent = () => {
return <div className="my-component">Hello World</div>;
};CSS Modules
CSS Modules允许你使用CSS类名,但这些类名是局部的,避免了全局命名冲突。
文件以 Xxxxx.module.css 命名, 不能使用xxx.scss或者xxx.less.
/* index.module.css */
/* 只要加了 :global() 就不会编译,称为全局样式 */
:global(.box){
...
}- css文件
.myComponent {
color: blue;
font-size: 14px;
}- React组件
import styles from './MyComponent.module.css';
const MyComponent = () => {
return <div className={styles.myComponent}>Hello World</div>;
};Styled Components
npm install styled-components目前我定义的css的使用的就是这个插件来完成的. 安装 styled-components 库,通过 styled 函数创建带样式的 React 组件。 Styled Components 官网
在vscode里面需要安装 vscode-styled-components 才会有提示信息.
// 安装
// npm i styled-components
// 基本的使用
// 方式一: 定义一个js
// 方式二: 直接定义tsx内部文件里面
// import "./Menu.scss"; // 类名相同的css会和其他的组件的css覆盖 不能实现私有化
import styled from "styled-components";
import { colorBlue, colorRed, titleSize } from './common'
interface MenuBoxProps {
hover: string,
// 注意点: 尽量不要使用 boolean 类型, 使用了浏览器会出现以下的红色警告
// 尽量使用字符串的形式
// Received `true` for a non-boolean attribute `aaa`.
// If you want to write it to the DOM, pass a string instead: aaa="true" or aaa={value.toString()}.
aaa?: string,
// 或者定义一个 object 的数据类型. 经需要传递的值一次性传递,.
}
const MenuBox = styled.div<MenuBoxProps>`
background-color: darkorange;
padding: 20px;
margin: 10px;
border-radius: 8px;
h2{
font-size: ${titleSize};
}
.ul-box{
list-style-type: none;
li{
color: ${colorBlue};
cursor: pointer;
&:hover{
color: ${colorRed};
}
}
}
ol{
list-style: none;
li{
color: pink;
cursor: pointer;
font-size: ${p => p.aaa ? '30px':'20px' };
&:hover{
color: ${({ hover }) => hover || 'blue'};
}
}
}
`
export const Menu = () => {
return <MenuBox hover='#f40' aaa='true' >
<h2>产品分类列表</h2>
<ul className="ul-box">
<li>手机</li>
<li>电脑</li>
<li>家电</li>
</ul>
<ol>
<li>春天</li>
<li>夏天</li>
<li>秋天</li>
<li>冬天</li>
</ol>
</MenuBox>
}styled-components是一个库,允许你编写组件级别的CSS,这些CSS与组件紧密绑定。
import styled from 'styled-components';
const MyComponent = styled.div`
color: blue;
font-size: 14px;
`;
const App = () => {
return <MyComponent>Hello World</MyComponent>;
};HOC 高阶组件
/**
* React高阶组件: 利用JS中的闭包[柯里化函数]实现的组件的代理
* 我们可以在代理组件中, 经过业务逻辑的处理, 获取一些信息,最后基于属性等方案,传递给我们最终要渲染的组件!!
*/
import type { ComponentType } from "react";
interface Props {
x: number,
y: number,
enable: boolean,
}
interface InjectedProps extends Props {
aaa: string
}
const HooksDemo = (props: InjectedProps) => {
// props里面有四个属性 分别时 x y enable aaa
const { x, y, enable, aaa } = props
return <div>
<p>x: {x}</p>
<p>y: {y}</p>
<p>enable: {enable}</p>
<p>xxx: {aaa}</p>
</div>
}
// 执行 ProxyTest 方法, 传递一个组件
const ProxyTest = (Com: ComponentType<InjectedProps>) => {
// 导出普通函数
return function HOC(props: Props) {
// 高阶函数
return <Com {...props} aaa="我是普通函数哟! 我还是附加的属性" />
}
// 导出箭头函数
// return (props: Props)=>{
// return <Com {...props} aaa="嘿呀嘿呀嘿嘿呀" />
// }
}
const HooksDemo8 = ProxyTest(HooksDemo)
// 把函数执行的返回结果「应该是一个组件」,基于ES6Module规范导出,供App导入使用!!
// 当前案例中,我们导出的是HOC「HOC:higher-order-components」
export default HooksDemo8;