大家好!今天给大家分享的是在React项目中如何封装一个可扩展,复用性强的组件。本篇以一个弹框组件为例。回顾我们在开发中常用的弹框有哪些特点:
- 一般弹框都是相对于屏幕上下左右居中的
- 弹框一般都会带有半透明的遮罩层
- 标题可以由使用者传入,有可能只是一串普通的文案,也有可能是一串html,甚至是一个小组件
- 内容可以自定模板
- 关闭按钮使用者可以控制是否显示
- 底部的取消按钮也可以由用户自己控制是否显示
- 点击关闭,取消,确认,有各自的事件响应
技术分析
一二两点非常简单就是一个普通的css布局,相信大家都清楚该怎么做,我们只要关注后面4点就可以了
- 首先来看第3点,如果是在Vue 项目中,我们一定会想到用slot去做,并且使用具名slot,而在React项目中没有slot的概念。那么在React项目中怎样才能实现这个需求呢!其实非常简单,在React项目中我们可以直接通过props传递就可以了,在React项目中props可以传递任何类型的值,包括jsx,组件,普通文本,函数。
- 接下来我们来看第4点,针对第4点如果是在Vue项目中我们肯定使用默认插槽来做,但是在React项目中就不行了,因为前面说过了React中没有插槽的概念, 但是React在props 上提供了一个特殊的属性,children,在子组件中通过children就可以获取到父组件中写在组件标签之间的内容,和Vue中的默认插槽看上去差不多。
- 针对第5点和第6点就非常简单了,就是通过普通的props传递一个标识到子组件进行判断即可
- 针对第7点,在Vue项目中我们可以利用自定义事件来完成这个功能,子组件通过emit触发自定义事件,从而在父组件得到各种事件的响应。而在React中没有自定义事件也没有emit,那么该怎么办呢!同样还是通过props,前面说过props 可以传递任何数据类型,所以函数也不例外。
代码实现(类组件实现)
1. 弹框组件
Dialog.js
js
import React, { Component } from "react";
import "../assets/css/Dialog.css";
export default class Dialog extends Component {
render() {
return (
<div className={this.props.show ? "dialog-wrap show" : "dialog-wrap"}>
<div className="dialog">
<div className="header">
<h3>{this.props.title}</h3>
{this.props.showClose ? (
<span onClick={this.props.close} className="close">
X
</span>
) : (
""
)}
</div>
<div className="content">{this.props.children}</div>
<div className="footer">
{this.props.showClose ? (
<button className="btn cancel" onClick={this.props.cancel}>
取消
</button>
) : (
""
)}
{this.props.showConfirm ? (
<button className="btn confirm" onClick={this.props.confirm}>
确定
</button>
) : (
""
)}
</div>
</div>
</div>
);
}
}
Dialog.css
css
.dialog-wrap{
position: fixed;
width: 100%;
height: 100%;
left: 0%;
top: 0;
background: rgba(0, 0, 0, 0.5);
display: none;
}
.dialog-wrap.show{
display: block;
}
.dialog {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 10px;
width: 500px;
}
.dialog .header {
height: 40px;
line-height: 40px;
font-size: 18px;
display: flex;
justify-content: space-between;
padding: 0 20px;
background: #409EFF;
color: #fff;
}
.dialog h3{
margin: 0;
line-height: 40px;
}
.close {
cursor: pointer;
}
.dialog .content {
min-height: 100px;
padding: 20px;
}
.dialog .footer {
display: flex;
justify-content: flex-end;
padding: 0 20px 20px 20px;
}
.btn {
background: none;
outline: none;
padding: 6px 12px;
border: 1px solid #DCDFE6;
border-radius: 4px;
cursor: pointer;
}
.btn.cancel {
margin-right: 20px;
}
.btn.confirm {
background: #409EFF;
border-color: #409EFF;
color: #fff;
}
2. 使用Dialog
App.js
js
import { Component } from "react";
import "./App.css";
import Dialog from "./components/Dialog";
class App extends Component {
constructor() {
super();
this.state = {
status: false,
title: "删除",
};
}
openDialog = () => {
this.setState({
status: true,
});
};
confirm = () => {
console.log("confirm");
this.setState({
status: false,
});
};
cancel = () => {
console.log("cancel");
this.setState({
status: false,
});
};
close = () => {
console.log("cancel");
this.setState({
status: false,
});
};
render() {
return (
<div>
<button className="btn" onClick={this.openDialog}>
打开弹框
</button>
<Dialog
title={this.state.title}
show={this.state.status}
confirm={() => this.confirm()}
cancel={() => this.cancel()}
close={() => this.close()}
showCancel={true}
showConfirm={true}
showClose={true}
>
<div>你确定要删除吗</div>
</Dialog>
</div>
);
}
}
export default App;
App.css
css
.btn {
background: none;
outline: none;
padding: 6px 12px;
border: 1px solid #DCDFE6;
border-radius: 4px;
cursor: pointer;
}
上述代码可以看到:
- 在Dialog组件中我们通过父组件传过来的show属性来控制弹框的显示与否,在父组件中show的值由state上的status控制,当点击父组件中的打开弹框按钮触发openDialog事件将status改为true时,show也将改为true,弹框就显示了
- 父组件通过向子组件传递confirm,cancel, close函数来响应点击事件,在子组件中就可以通过props获取到对应的函数
- 父组件通过向子组件传递showCancel,showConfirm,showClose 属性来判断是否显示对应的按钮,在子组件中通过props 可以获取到对应的值,然后在render中通过三元表达式判断是否显示对应的按钮
- 父组件中再调用子组件时在组件标签中写入了'你确定要删除吗',在子组件中通过this.props.children就可以获取到对应的内容
3. 演示效果

可以看到点击对应的事件也得到了对应的回应,符合预期。
函数组件,Hooks方式实现
在最新的React版本中比较流行函数组件和Hooks写法,下面我们将上述代码改成函数和Hooks来实现下。
Dialog.js
js
import "../assets/css/Dialog.css";
export default function Dialog(props) {
return (
<div className={props.show ? "dialog-wrap show" : "dialog-wrap"}>
<div className="dialog">
<div className="header">
<h3>{props.title}</h3>
{props.showClose ? (
<span onClick={props.close} className="close">
X
</span>
) : (
""
)}
</div>
<div className="content">{props.children}</div>
<div className="footer">
{props.showClose ? (
<button className="btn cancel" onClick={props.cancel}>
取消
</button>
) : (
""
)}
{props.showConfirm ? (
<button className="btn confirm" onClick={props.confirm}>
确定
</button>
) : (
""
)}
</div>
</div>
</div>
);
}
App.js
js
import { useState } from "react";
import "./App.css";
import Dialog from "./components/Dialog";
function App() {
const [status, setStatus] = useState(false);
const [title] = useState("删除");
const openDialog = () => {
setStatus({
status: true,
});
};
const confirm = () => {
console.log("confirm");
setStatus(false);
};
const cancel = () => {
console.log("cancel");
setStatus(false);
};
const close = () => {
console.log("cancel");
setStatus(false);
};
return (
<div>
<button className="btn" onClick={openDialog}>
打开弹框
</button>
<Dialog
title={title}
show={status}
confirm={() => confirm()}
cancel={() => cancel()}
close={() => close()}
showCancel={true}
showConfirm={true}
showClose={true}
>
<div>你确定要删除吗</div>
</Dialog>
</div>
);
}
export default App;
可以看到将类组件改造成函数组件,将state 改成Hooks形式也非常简单
- class 改成function
- 将extends Component去掉
- 将constructor 和里面的内容去掉
- state 数据的定义和修改改成通过React中的useState中返回值结构出来的第一个值作为变量,第二个值作为修改的方法
- 将render 方法去掉,直接return
- 将return 中用到的this 直接去掉
- 将子组件中的this.props通通改成参数函数参数上的props
来看下效果

可以看到显示依然正常。
总结
本篇通过一个弹框组件的封装,展示了在React中如何封装一个可扩展,复用性强的组件。并且和Vue实现方式进行对比,更容易掌握React组件的封装技巧和基础知识。然后在将类组件的形式改造成函数组件的形式,将state改成Hooks,进一步加深对React基础的巩固。