这可能是你需要的React实战技巧
迪丽瓦拉
2025-05-28 08:02:35
0

一、父组件通过 Ref 调用子组件中的方法

这里同时演示使用函数组件类组件的父子组件如何编写

子组件

  • React.forwardRef
  • React.useImperativeHandle
  • public、private、protected
/** * 声明一个 function component 作为子组件 * 通过 forwardRef 接收父组件传递的 ref * 通过 useImperativeHandle 改造原 ref * 同时定义类型 IFnChildInstance 明确返回的 ref 的类型(非 typescript 不用考虑这个) * 同时演示了组件的 props 应该写在哪里 */
interface IFnChildInstance {show: () => void
}
interface IFnChildProps {testname: string
}
const CompFnChild = React.forwardRef(function (props,  ref
) {const { testname } = propsReact.useImperativeHandle(ref, () => ({show: () => setVisible(true),}))const [visible, setVisible] = React.useState(false)return (

演示下state:{visible ? "显示" : "隐藏"}

演示下props:{testname}

setVisible(false)}>隐藏
) })/** * 声明一个 class component 作为子组件 * 通过 public 明确这就是我们希望父亲组件能调用的方法(public/private/protected) */ interface IClassChildProps {testname: string } interface IClassChildState {visible: boolean } class CompClassChild extends React.Component {constructor(props: IClassChildProps) {super(props)this.state = {visible: false,}}public show = () => {this.setState({visible: true,})}private handleHide = () => {this.setState({visible: false,})}render() {const { visible } = this.stateconst { testname } = this.propsreturn (

演示下state:{visible ? "显示" : "隐藏"}

演示下props:{testname}

隐藏
)} }

父组件

  • React.useRef
  • React.createRef
function CompFnParent() {const RefFnChild = React.useRef(null)const RefClassChild = React.useRef(null)const myname = "tellyourmad"return (<>
RefFnChild.current?.show()}> 调用 CompFnChild 的方法
RefClassChild.current?.show()}> 调用 CompClassChild 的方法
) }class CompClassParent extends React.Component {private RefFnChild = React.createRef()private RefClassChild = React.createRef()componentDidMount() {// TODO}render() {const myname = "tellyourmad"return (<>
this.RefFnChild.current?.show()}> 调用 CompFnChild 的方法
this.RefClassChild.current?.show()}> 调用 CompClassChild 的方法
)} }

总结一下,其实使用 class 方式再配合上 typescript 编写的子组件其实是最能简洁明了的

二、memoize 的应用

get(computed)

平时我们有时候会用 get 对一些数据进行处理:

interface IMyTestProps {}
interface IMyTestState {firstname: stringlastname: string
}
class MyTest extends React.Component {get name() {const { firstname, lastname } = this.statereturn firstname + lastname}render(){return (
我的名字是:{this.name}
)} }

但是在每次触发 render 的时候都需要重新计算 get name,如果这里逻辑非常复杂,那将会消耗大量性能。其实很多时候我们只需要判断入参有没有发生变化即可判断是否需要重新计算。譬如例子中,如果 firstnamelastname 没有发生变化则不需要重新计算。

memoize

这里使用 memoize 包裹后:

import { memoize } from "lodash"interface IMyTestProps {}
interface IMyTestState {firstname: stringlastname: string
}
class MyTest extends React.Component {get url() {const { firstname, lastname } = this.statereturn this.getUrl(firstname, lastname)}private getUrl = memoize(function (firstname, lastname) {return firstname + lastname})render(){return (
我的名字是:{this.name}
)} }
  • 这里用的是 lodash 库里面的方法,有兴趣可以自己去看源码
  • 这种优化方式其实跟 debounce、throttle 都是一个意思,都是根据一定条件来判断是否应该节约本次计算

p.s. 这里只是演示了一个简单的 firstname + lastname 例子,实际是不需要考虑优化的,因为本身 memoize 也是要执行比较逻辑,当入参数非常复杂时,这样优化其实是得不偿失的,所以具体情况要具体分析。

三、实现一个弹窗组件(toast/modal/dialog)

你可以看到不管 antd(react) 还是 element(vue) 中的弹窗组件都是渲染在 document.body 上的,而非当前组件所对应的 render 节点上

import { Modal } from "antd"class Test extends React.Component {componentDidMount() {Modal.info({title: "通过api",visible: true,})}render() {return (
测是是的话i说的
)} }ReactDOM.render(, document.getElementById("root"))

参考 前端进阶面试题详细解答

image.png

上面例子演示了两种弹窗使用方式,分别是 通过 api 调用使用 react 组件,下面会逐个举例如何实现:

通过 api 调用

  1. document.createElement 创建 dom
  2. document.body.appendChild 插入 dom
  3. ReactDOM.render 渲染组件
  4. 调用实例中的方法 或者 直接给实例传递
import React from "react"
import ReactDOM from "react-dom"const show = function (props: IModalProps) {const node = document.createElement("div")document.body.appendChild(node)// 顺便说下,其实不必在这里写样式,没意义的// node.style.zIndex = "999";const handleClose = function () {/**     * 在 modal 关闭后会触发销毁     * 目前这里是 setState({visible: false}) 之后就立马触发销毁的     * 如果想 antd 那样还有消失过渡效果(transition)的话,还得加个延迟哦~     *     * p.s. 不销毁会导致性能等问题     */ReactDOM.unmountComponentAtNode(node) // 卸载 react 组件document.body.removeChild(node) // 删除 dom 节点}/**  ReactDOM.render(     {        props.onClose && props.onClose()        handleClose()      }}    />,    node  ).show() // render 之后调用实例中的 show 方法  **/// 因为在未来的 react 中,组件的渲染又可能是异步的,所以不建议直接使用 render 返回的实例,应该用下面方式ReactDOM.render( {        props.onClose && props.onClose()        handleClose()      }}    />,node,function(this: Modal) {this.show() // 在 callback 之后调用实例的方法})
}interface IModalProps {title: stringonClose?: () => void
}
interface IModalState {visible: boolean
}
class Modal extends React.Component {constructor(props: IModalProps) {super(props)this.state = {visible: false,}}// 暴露 show 方法public show = () => {this.setState({visible: true,})}private handleClose = () => {this.setState({visible: false,})this.props.onClose && this.props.onClose()}render() {return ({// 其他样式就应该不用演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.state.visible ? "visible" : "hidden",        }}      >
{this.props.title}
关闭
)} }function Test() {return (
show({ title: "标题" })}>

通过调用 show 方法即可显示弹窗

) }

使用 react 组件

  • ReactDOM.createPortal 建立传送门
  • 通过 props 控制
interface IModalProps {visible: booleantitle: stringonClose: () => void
}
class Modal extends React.Component {private el: HTMLDivElementprivate readonly container = document.bodyconstructor(props: IModalProps) {super(props)this.el = document.createElement("div")}componentDidMount() {this.container.appendChild(this.el)}componentWillUnmount() {this.container.removeChild(this.el)}private handleClose = () => this.props.onClose()render() {return ReactDOM.createPortal({// 其他样式就应该不用演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.props.visible ? "visible" : "hidden",        }}      >
{this.props.title}
关闭
,this.el)} }function Test() {const [visible, setVisible] = React.useState(false)return (
setVisible(true)}>

使用 react 组件

setVisible(false)} />
) }

合体

我们的期望是这个 Modal 组件像 antd 的一样,既能通过 Modal.show() 方式使用,也是通过 方式使用,这里就将上面两个例子进行合并

const show = function (props: Omit) {const node = document.createElement("div")document.body.appendChild(node)const handleClose = function () {ReactDOM.unmountComponentAtNode(node)document.body.removeChild(node)}return ReactDOM.render( {        props.onClose && props.onClose()        handleClose()      }}    />,node).show()
}interface IModalProps {visible?: booleantitle: stringonClose?: () => void
}
interface IModalState {visible: boolean
}
class Modal extends React.Component {/**   * 将 show 方法放到 class component 的静态方法中   */static show = showprivate el: HTMLDivElementprivate readonly container = document.bodyconstructor(props: IModalProps) {super(props)this.el = document.createElement("div")this.state = {visible: false,}}componentDidMount() {this.container.appendChild(this.el)}componentWillUnmount() {this.container.removeChild(this.el)}get visible() {/**     * props 优先级比 state 高     * 保证如果外部在控制弹窗状态,则根据外部的来     */if (typeof this.props.visible !== "undefined") {return this.props.visible}return this.state.visible}public show = () => {this.setState({visible: true,})/**     * return 当前实例,提供链式调用的操作方式     * 譬如: Modal.show().hide()     */return this}public hide = () => {this.setState({visible: false,})return this}private handleClose = () => {this.setState({visible: false,})this.props.onClose && this.props.onClose()}render() {return ReactDOM.createPortal({// 其他样式就应该不用演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.visible ? "visible" : "hidden",        }}      >
{this.props.title}
关闭
,this.el)} }function Test() {const [visible, setVisible] = React.useState(false)return (<>Modal.show({ title: "标题", }) } >

使用 api 调用

setVisible(true)}>

使用 react 组件

setVisible(false)} />
) }

总结一下,最后的这个组件有多种实现方式,这里只是很简单的演示一下,关键点在你要掌握 ReactDOM.renderReactDOM.createPortal 的使用,当你掌握了这两者,诸如 Toast、Dialog、Dropdown 大体都是一个实现原理。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...