React 项目优化
添加UI/Card 进行样式复用
使用 props.children 表示组件的标签体
创建 src/Components/UI/Card/Card.js
import React from 'react'
import './Card.css'
const Card = (props) => {
console.log(props.children);
return (
<div className={`card ${props.className}`}>
{props.children}
</div>
)
}
export default Card创建 src/Components/UI/Card/Card.css
.card{
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0, .2); /* 阴影 */
}在 src/Components/Logs/Logs.js 中引入Card,使用
/* 日志的容器 */
import LogItem from "./LogItem/LogItem";
import './Logs.css';
import Card from "../UI/Card/Card";
const Logs = () => {
// 模拟一组从服务器中加载的数据
const logsData = [
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
{
id: '003',
date: new Date(2022, 2, 11, 11, 30),
desc: '学习JavaScript',
time: 40
},
{
id: '004',
date: new Date(2022, 2, 15, 10, 30),
desc: '学习React',
time: 80
}
];
// 循环遍历数据,生成对应的item
const logItemData = logsData.map(itme => <LogItem key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
return <Card className="logs">
{
logItemData
// logsData.map(item => <LogItem {...item}/> )
}
</Card>
};
export default Logs;在 src/Components/Logs/Logs.css 中删除 src/Components/UI/Card/Card.css 已经定义的公共样式
.card{
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0, .2); /* 阴影 */
}在 src/Components/Logs/LogItem/LogItem.js 中引入Card
import React from 'react'
import MyDate from './MyDate/MyDate'
import './LogItem.css'
import Card from '../../UI/Card/Card'
const LogItem = (props) => {
// 在函数组件中,属性相当于函数的参数,可通过参数接受
return (
<Card className='item'>
<MyDate date={props.date}></MyDate>
{/* 内容容器 */}
<div className='content'>
<h2 className='desc'>{props.LogDesc}</h2>
<div className='time'>{props.LogTime}</div>
</div>
</Card>
)
}
export default LogItem在 src/Components/Logs/LogItem/LogItem.css 中删除 src/Components/UI/Card/Card.css 已经定义的公共样式
.card{
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0, .2); /* 阴影 */
}设置表单添加
添加 src/Components/LogsForm 用来存放表单代码
src/Components/LogsForm/LogsForm.js
import React from 'react'
import Card from '../UI/Card/Card'
import './LogsForm.css'
const LogsForm = () => {
return (
<Card className='log-form'>
<form>
<div className='form-item'>
<label htmlFor='date'>日期</label>
<input id='date' type='date'></input>
</div>
<div className='form-item'>
<label htmlFor='desc'>内容</label>
<input id='desc' type='text'></input>
</div>
<div className='form-item'>
<label htmlFor='time'>时长</label>
<input id='time' type='text'></input>
</div>
<div className='form-btn'>
<button>添加</button>
</div>
</form>
</Card>
)
}
export default LogsFormsrc/Components/LogsForm/LogsForm.css
.log-form {
background-color: #eae2b7;
margin-bottom: 16px;
padding: 10px;
}
.form-item {
height: 30px;
line-height: 30px;
margin: 10px 0;
display: flex;
flex-flow: row;
}
.form-item label {
text-align: center;
padding: 0 4px;
}
.form-item input {
flex: auto;
}
.form-btn {
text-align: center;
}
.form-btn button {
width: 100px;
height: 50px;
border: none;
background-color: saddlebrown;
color: #fff;
font-weight: bold;
font-size: 30px;
}针对表单项及列表项均需要设置 width 和 margin 故在其父组件中进行设置,即App.css中。同时在 App.js 中引入表单。
src/App.js
import Logs from "./Components/Logs/Logs";
import LogsForm from "./Components/LogsForm/LogsForm";
import './App.css'
const App = () => {
return <div className="app">
<LogsForm/>
<Logs/>
</div>
};
// 导出App
export default App;src/App.css
.app{
width: 800px;
margin: 50px auto;
}获取表单数据
需求:
当表单项发生变化时,获取用户输入的内容。
const descRef = React.useRef()
// 创建响应函数,监听表单项变化
const descChangeHandler = () => {
const desc = document.getElementById('desc')
// desc.value
console.log('descRef.current.value',descRef.current.value);
}
<input id='desc' ref={descRef} onChange={descChangeHandler} type='text'></input>通过 e 事件对象 获取,保存了当前事件触发时的所有信息。 e.target.value。
src/Components/LogsForm/LogsForm.js
import React from 'react';
import Card from "../UI/Card/Card";
import './LogsForm.css';
const LogsForm = () => {
// 创建三个变量,用来存储表单中的数据
let inputDate = '';
let inputDesc = '';
let inputTime = 0;
// 创建一个响应函数,监听日期的变化
const dateChangeHandler = (e) => {
// 获取到当前触发事件的对象
// 事件对象中保存了当前事件触发时的所有信息
// event.target 执行的是触发事件的对象(DOM对象)
//console.log(e.target.value);
inputDate = e.target.value;
};
// 监听内容的变化
const descChangeHandler = (e) => {
inputDesc = e.target.value;
};
//监听时长的变化
const timeChangeHandler = (e) => {
inputTime = e.target.value;
};
// 当表单提交时,汇总表单中的数据
/*
* 在React中,通常表单不需要自行提交
* 而是要通过React提交
* */
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime
};
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={formSubmitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input onChange={dateChangeHandler} id="date" type="date" />
</div>
<div className="form-item">
<label htmlFor="desc">内容</label>
<input onChange={descChangeHandler} id="desc" type="text" />
</div>
<div className="form-item">
<label htmlFor="time">时长</label>
<input onChange={timeChangeHandler} id="time" type="number" />
</div>
<div className="form-btn">
<button>添加</button>
</div>
</form>
</Card>
);
};
export default LogsForm;数据双向绑定
提交表单后,进行表单旧数据清空。
将表单数据存储在state中,然后将state设置为表单项的value值,实现双向绑定。此时表单成为一个受控组件。
import React from 'react';
import Card from "../UI/Card/Card";
import './LogsForm.css';
const LogsForm = () => {
// 创建三个变量,用来存储表单中的数据
const [inputDate, setInputDate] = React.useState('');
const [inputDesc, setInputDesc] = React.useState('');
const [inputTime, setInputTime] = React.useState('');
// 创建一个响应函数,监听日期的变化
const dateChangeHandler = (e) => {
// 获取到当前触发事件的对象
// 事件对象中保存了当前事件触发时的所有信息
// event.target 执行的是触发事件的对象(DOM对象)
//console.log(e.target.value);
setInputDate(e.target.value);
};
// 监听内容的变化
const descChangeHandler = (e) => {
setInputDesc(e.target.value);
};
//监听时长的变化
const timeChangeHandler = (e) => {
setInputTime(e.target.value);
};
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime
};
// 清空旧数据
setInputDate('');
setInputDesc('');
setInputTime('');
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={formSubmitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input onChange={dateChangeHandler} value={inputDate} id="date" type="date" />
</div>
<div className="form-item">
<label htmlFor="desc">内容</label>
<input onChange={descChangeHandler} value={inputDesc} id="desc" type="text" />
</div>
<div className="form-item">
<label htmlFor="time">时长</label>
<input onChange={timeChangeHandler} value={inputTime} id="time" type="number" />
</div>
<div className="form-btn">
<button>添加</button>
</div>
</form>
</Card>
);
};
export default LogsForm;或者创建一个表单对象,存放各个属性。
import React from 'react';
import Card from "../UI/Card/Card";
import './LogsForm.css';
const LogsForm = () => {
// 创建一个表单变量
const [formData, setFormData] = React.useState({
inputDate: '',
inputDesc: '',
inputTime: '',
});
// 创建一个响应函数,监听日期的变化
const dateChangeHandler = (e) => {
// 获取到当前触发事件的对象
// 事件对象中保存了当前事件触发时的所有信息
// event.target 执行的是触发事件的对象(DOM对象)
//console.log(e.target.value);
// setInputDate(e.target.value);
setFormData({
...formData,
inputDate: e.target.value
})
};
// 监听内容的变化
const descChangeHandler = (e) => {
// setInputDesc(e.target.value);
setFormData({
...formData,
inputDesc: e.target.value
})
};
//监听时长的变化
const timeChangeHandler = (e) => {
// setInputTime(e.target.value);
setFormData({
...formData,
inputTime: e.target.value
})
};
// 当表单提交时,汇总表单中的数据
/*
* 在React中,通常表单不需要自行提交
* 而是要通过React提交
* */
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(formData.inputDate),
desc: formData.inputDesc,
time: +formData.inputTime
};
// 清空旧数据
setFormData({
inputDate: '',
inputDesc: '',
inputTime: '',
})
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={formSubmitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input onChange={dateChangeHandler} value={formData.inputDate} id="date" type="date" />
</div>
<div className="form-item">
<label htmlFor="desc">内容</label>
<input onChange={descChangeHandler} value={formData.inputDesc} id="desc" type="text" />
</div>
<div className="form-item">
<label htmlFor="time">时长</label>
<input onChange={timeChangeHandler} value={formData.inputTime} id="time" type="number" />
</div>
<div className="form-btn">
<button>添加</button>
</div>
</form>
</Card>
);
};
export default LogsForm;添加新日志
目前数据列表是在 Logs/LogsData 中进行展示,而表单数据则是在 LogsForm 中,两者的交互需要进行组件间传参,并且 LogsData 可能并不是一个组件中需要使用,故将其放在公用祖先元素- App.js 中进行传递。
state的提升
将 src/Components/Logs/Logs.js 中的数组定义抽取出来,提升至 App.js 中,并使用 ustState() 实现双向绑定,只会通过组件通信,将数组列表的值传递给 Log.js,并在其中接收使用。
src/App.js
import React from 'react';
import Logs from "./Components/Logs/Logs";
import LogsForm from "./Components/LogsForm/LogsForm";
import './App.css'
const App = () => {
// 数据
const [logsData, setLogsData] = React.useState([
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
{
id: '003',
date: new Date(2022, 2, 11, 11, 30),
desc: '学习JavaScript',
time: 40
},
{
id: '004',
date: new Date(2022, 2, 15, 10, 30),
desc: '学习React',
time: 80
}
]);
return <div className="app">
<LogsForm/>
<Logs logsData={logsData}/>
</div>
};
// 导出App
export default App;src/Components/Logs/Logs.js
/* 日志的容器 */
import LogItem from "./LogItem/LogItem";
import './Logs.css';
import Card from "../UI/Card/Card";
const Logs = (props) => {
// 循环遍历数据,生成对应的item
const logItemData = props.logsData.map(itme => <LogItem key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
return <Card className="logs">
{
logItemData
// logsData.map(item => <LogItem {...item}/> )
}
</Card>
};
export default Logs;此时新增的数据是在 src/Components/LogsForm/LogsForm.js 中,对其进行处理需要在 App.js 中添加一个保存的函数 onSaveLogHandler ,并将其通过 onSaveLog 传递给 LogsForm.js ,在其中通过 props.onSaveLog 进行接收,并在点击添加按钮之后调用父组件的方法,将新增的数据作为参数传递给父组件,在父组件中进行添加。
src/App.js
import React from 'react';
import Logs from "./Components/Logs/Logs";
import LogsForm from "./Components/LogsForm/LogsForm";
import './App.css'
const App = () => {
// 数据
const [logsData, setLogsData] = React.useState([
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
{
id: '003',
date: new Date(2022, 2, 11, 11, 30),
desc: '学习JavaScript',
time: 40
},
{
id: '004',
date: new Date(2022, 2, 15, 10, 30),
desc: '学习React',
time: 80
}
]);
// 将LogsForm中的数据传递给App组件,通过App组件进行数据的添加。
const onSaveLogHandler = (newLog) => {
// 向新日志添加id
newLog.id = Date.now() + ''
setLogsData([newLog, ...logsData]);
}
return <div className="app">
<LogsForm onSaveLog={onSaveLogHandler}/>
<Logs logsData={logsData}/>
</div>
};
// 导出App
export default App;src/Components/LogsForm/LogsForm.js
import React from 'react';
import Card from "../UI/Card/Card";
import './LogsForm.css';
const LogsForm = (props) => {
// 创建三个变量,用来存储表单中的数据
const [inputDate, setInputDate] = React.useState('');
const [inputDesc, setInputDesc] = React.useState('');
const [inputTime, setInputTime] = React.useState('');
console.log('props',props.onSaveLog);
// 创建一个响应函数,监听日期的变化
const dateChangeHandler = (e) => {
setInputDate(e.target.value);
};
// 监听内容的变化
const descChangeHandler = (e) => {
setInputDesc(e.target.value);
};
//监听时长的变化
const timeChangeHandler = (e) => {
setInputTime(e.target.value);
};
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
// 获取表单项中的数据日期、内容、时长
// 将数据拼装为一个对象
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime
};
// 添加数据时调用父组件添加方法
props.onSaveLog(newLog)
// 清空旧数据
setInputDate('');
setInputDesc('');
setInputTime('');
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={formSubmitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input onChange={dateChangeHandler} value={inputDate} id="date" type="date" />
</div>
<div className="form-item">
<label htmlFor="desc">内容</label>
<input onChange={descChangeHandler} value={inputDesc} id="desc" type="text" />
</div>
<div className="form-item">
<label htmlFor="time">时长</label>
<input onChange={timeChangeHandler} value={inputTime} id="time" type="number" />
</div>
<div className="form-btn">
<button>添加</button>
</div>
</form>
</Card>
);
};
export default LogsForm;删除日志
首先需要在 src\Components\Logs\LogItem\LogItem.js 中添加删除按钮,点击删除按钮进行删除确认,确认通过后进行删除。
src\Components\Logs\LogItem\LogItem.js
import React from 'react'
import MyDate from './MyDate/MyDate'
import './LogItem.css'
import Card from '../../UI/Card/Card'
const LogItem = (props) => {
const deleteItemHandler = () =>{
const isDel = window.confirm('Are you sure you want to delete');
if(isDel) {
// 删除即从state中移除数据
props.onDeleteLog()
}
}
return (
<Card className='item'>
<MyDate date={props.date}></MyDate>
{/* 内容容器 */}
<div className='content'>
<h2 className='desc'>{props.LogDesc}</h2>
<div className='time'>{props.LogTime}</div>
</div>
<div>
<div className='delete' onClick={deleteItemHandler}>×</div>
</div>
</Card>
)
}
export default LogItemsrc\Components\Logs\LogItem\LogItem.css
/* 设置item */
.item{
background-color: #FCBF49;
display: flex;
margin: 16px 0;
padding: 4px;
}
/* 设置内容样式 */
.content{
flex: auto;
text-align: center;
font-weight: bold;
}
.time{
color: red;
}
.delete {
margin-right: 10px;
color: white;
font-size: 20px;
font-weight: bold;
cursor: pointer;
transition: .3s;
-webkit-transition: .3s;
-moz-transition: .3s;
-ms-transition: .3s;
-o-transition: .3s;
}
.delete:hover {
color: red;
transform: rotate(1turn);
-webkit-transform: rotate(1turn);
-moz-transform: rotate(1turn);
-ms-transform: rotate(1turn);
-o-transform: rotate(1turn);
}删除函数则在 src/App.js 中进行定义,接收 id 作为删除依据,通过 App.js-Logs.js-LogItme.js 进行传递。
src/App.js
import React from 'react';
import Logs from "./Components/Logs/Logs";
import LogsForm from "./Components/LogsForm/LogsForm";
import './App.css'
const App = () => {
// 数据
const [logsData, setLogsData] = React.useState([
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
{
id: '003',
date: new Date(2022, 2, 11, 11, 30),
desc: '学习JavaScript',
time: 40
},
{
id: '004',
date: new Date(2022, 2, 15, 10, 30),
desc: '学习React',
time: 80
}
]);
// 将LogsForm中的数据传递给App组件,通过App组件进行数据的添加。
const onSaveLogHandler = (newLog) => {
// 向新日志添加id
newLog.id = Date.now() + ''
setLogsData([newLog, ...logsData]);
}
const deleteLogByIndex = (id) => {
setLogsData(prevState => {
const newLogs = prevState.filter(log => log.id !== id);
return newLogs;
})
}
return <div className="app">
<LogsForm onSaveLog={onSaveLogHandler}/>
<Logs logsData={logsData} onDeleteLog={deleteLogByIndex}/>
</div>
};
// 导出App
export default App;删除需要进行传参,故可在中间组件 Logs.js 中使用匿名函数来封装 onDeleteLog 回调函数,确保每次进行删除时,onDeleteLog 能正确处理响应的 id 值。
src/Components/Logs/Logs.js
/* 日志的容器 */
import LogItem from "./LogItem/LogItem";
import './Logs.css';
import Card from "../UI/Card/Card";
const Logs = (props) => {
// 循环遍历数据,生成对应的item
const logItemData = props.logsData.map(itme => <LogItem onDeleteLog={()=>props.onDeleteLog(itme.id)} key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
return <Card className="logs">
{
logItemData
// logsData.map(item => <LogItem {...item}/> )
}
</Card>
};
export default Logs;空列表提示
当列表为空时,提示信息
src/Components/Logs/Logs.js
/* 日志的容器 */
import LogItem from "./LogItem/LogItem";
import './Logs.css';
import Card from "../UI/Card/Card";
const Logs = (props) => {
// 循环遍历数据,生成对应的item
let logItemData = props.logsData.map(itme => <LogItem onDeleteLog={()=>props.onDeleteLog(itme.id)} key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
if(logItemData.length === 0) {
logItemData = <p>空日志列表!</p>
}
return <Card className="logs">
{
logItemData
// logItemData.length !== 0 ? logItemData : <p>空日志列表!</p>
// logsData.map(item => <LogItem {...item}/> )
}
</Card>
};
export default Logs;添加confirmModal
在初始删除时,确认删除窗口使用的是 window.confirm() ,将其替换为自己写的确认组件。
创建 src/UI/ConfirmModal 文件夹,在 ConfirmModal 中进行窗口的创建。
src/UI/ConfirmModal/ConfirmModal.js
import React from 'react'
import Card from '../Card/Card'
import './ConfirmModal.css'
const ConfirmModal = props => {
return <Card className="confirmModal">
<div className='confirmText'>
<p>{props.confirmText}</p>
</div>
<div className='confirmBtn'>
<button onClick={props.onConfirm}>确认</button>
<button onClick={props.onCancel}>取消</button>
</div>
</Card>
}
export default ConfirmModalsrc/UI/ConfirmModal/ConfirmModal.css
.confirmModal {
display: flex;
flex-flow: column;
width: 200px;
height: 100px;
padding: 5px;
background-color: #fff;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
.confirmText {
display: flex;
height: 60px;
justify-content: center;
align-items: center;
font-size: 16px;
color: red;
}
.confirmBtn {
flex: auto;
display: flex;
justify-content: flex-end;
}
.confirmBtn button {
width: 50px;
margin: 0 10px;
border: none;
cursor: pointer;
}并在 LogItem.js 中引入使用,通过变量控制窗口显示隐藏,并创建相应的确认,删除函数,将其传递给 ConfirmModal.js 使用。
src\Components\UI\ConfirmModal\ConfirmModal.js
import React from 'react'
import MyDate from './MyDate/MyDate'
import './LogItem.css'
import Card from '../../UI/Card/Card'
import ConfirmModal from '../../UI/ConfirmModal/ConfirmModal'
const LogItem = (props) => {
// 添加state ,控制删除窗口显示
const [showDelConfirm, setshowDelConfirm] = React.useState(false)
const deleteItemHandler = () => {
setshowDelConfirm(true)
}
// 定义取消删除函数
const cancelDeleteHandler = () => {
setshowDelConfirm(false)
}
// 定义确认删除函数
const confirmDeleteHandler = () => {
props.onDeleteLog()
setshowDelConfirm(false)
}
return (
<Card className='item'>
{showDelConfirm && <ConfirmModal confirmText='确认删除吗?' onCancel={cancelDeleteHandler} onConfirm={confirmDeleteHandler} />}
<MyDate date={props.date}></MyDate>
{/* 内容容器 */}
<div className='content'>
<h2 className='desc'>{props.LogDesc}</h2>
<div className='time'>{props.LogTime}</div>
</div>
<div>
<div className='delete' onClick={deleteItemHandler}>×</div>
</div>
</Card>
)
}
export default LogItem添加遮罩层
创建 src\Components\UI\BackDrop 文件夹,进行遮罩层创建。
在 ConfirmModal 中引入遮罩层进行使用,并在 BackDrop.js 的代码中通过 props.children 在遮罩层中进行显示。
src\Components\UI\BackDrop\BackDrop.js
import React from 'react'
import './BackDrop.css'
const BackDrop = (props) => {
return (
<div className='backDrop'>
{props.children}
</div>
)
}
export default BackDropsrc\Components\UI\BackDrop\BackDrop.css
.backDrop{
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0, .3);
}src\Components\UI\ConfirmModal\ConfirmModal.js
import React from 'react'
import Card from '../Card/Card'
import './ConfirmModal.css'
import BackDrop from '../BackDrop/BackDrop'
const ConfirmModal = props => {
return <BackDrop>
<Card className="confirmModal">
<div className='confirmText'>
<p>{props.confirmText}</p>
</div>
<div className='confirmBtn'>
<button onClick={props.onConfirm}>确认</button>
<button onClick={props.onCancel}>取消</button>
</div>
</Card>
</BackDrop>
}
export default ConfirmModal遮罩层优化 Protal
目前遮罩层在文档结构中,输入 LogItem 的一个子组件,某种程度上其只作用于其父元素。
目前遮罩层可以正常遮罩整个文档流。但是如果将父元素的定位设置为 相对定位,那么遮罩层就会变为只遮罩其父元素,而不是整个文档流。
此时可通过为遮罩层设置较高的 z-index 进行解决,但是其仍然在父元素中,如果父元素层级不够,可能会造成遮罩不完全的问题。
主要问题还是因为遮罩层是单个列表项的子元素。
问题类型:
在React中,父组件引入子组件后,子组件会直接在父组件内部染。换句话说,React元素中的子组件,在DOM中,也会是其父组件对应DOM的后代元素。
但是,在有些场景下如果将子组件直接渲染为父组件的后代,在网页显示时会出现一些问题。
比如,需要在React中添加一个会盖住其他元素的Backdrop组件,Backdrop显示后,页面中所有的元素都会被遮盖。很显然这里需要用到定位,但是如果将遮罩层直接在当前组件中染的话,遮罩层会成为当前组件的后代元素。
如果此时,当前元素后边的兄弟元素中有开启定位的情况出现,且层级不低于当前元素时,便会出现盖住遮罩层的情况。
Protal
- 组件默认会作为父组件的后代渲染到页面中,但是有些情况下,这种方式会带来一些问题。
- 通过portal可以将组件渲染到页面中的指定位置。
- 使用方法:
- 在index.html添加一个新的元素。
- 修改组件的渲染方式。
- 通过ReactDOM.createPortal()作为返回值创建元素。
- 参数:
- jsx(修改前return后的代码)。
- 目标位置(DOM元素)。
在 public/index.html 中创建遮罩层根元素容器。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学习log</title>
</head>
<body>
<div id="root"></div>
<!-- 遮罩层容器 -->
<div id="backdrop-root"></div>
</body>
</html>在 ConfirmModal 中的内容最后也是在 backdrop 中显示,故在 backdrop 中进行设置。
src\Components\UI\BackDrop\BackDrop.js
import React from 'react'
import ReactDOM from 'react'
import './Backdrop.css'
// 获取backdrop-root
const BackdropRoot = document.getElementById('backdrop-root')
const Backdrop = (props) => {
return ReactDOM.createPortal(<div className='backdrop'>
{props.children}
</div>, BackdropRoot)
}
export default Backdrop年份过滤
创建 src/Components/LogsFilter/LogsFilter.js ,接收父组件传入的年份信息,并创建选择框变化的监听函数,在选择框变化时,调用父组件传入的函数,并将改变的值作为参数传送。
import React from 'react'
const LogsFilter = (props) => {
// 监听事件
const changeYear = e => {
console.log(+e.target.value);
props.onChange(+e.target.value);
}
return (
<div>
年份:
<select value={props.year} onChange={changeYear}>
<option value={2021}>2021</option>
<option value={2022}>2022</option>
<option value={2023}>2023</option>
</select>
</div>
)
}
export default LogsFilter在 src/Components/Logs/Logs.js 中,定义 setYear-state,并创建年份改变的函数,将其传递给子组件。设置根据年份过滤数据,并在循环遍历数据,生成item时使用过滤后的数据。
src/Components/Logs/Logs.js
/* 日志的容器 */
import React from "react";
import LogItem from "./LogItem/LogItem";
import './Logs.css';
import Card from "../UI/Card/Card";
import LogsFilter from "../LogsFilter/LogsFilter";
const Logs = (props) => {
// 创建存储年份的state
const [year, setYear] = React.useState(2022)
// 过滤数据,只显示某一年的数据
let filterData = props.logsData.filter(item => item.date.getFullYear() === year)
const changeYear = (year)=>{
setYear(year)
}
// 循环遍历数据,生成对应的item
let logItemData = filterData.map(itme => <LogItem onDeleteLog={()=>props.onDeleteLog(itme.id)} key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
if(logItemData.length === 0) {
logItemData = <p>空日志列表!</p>
}
return <Card className="logs">
<LogsFilter year={year} onChange={changeYear}/>
{
logItemData
// logItemData.length !== 0 ? logItemData : <p>空日志列表!</p>
// logsData.map(item => <LogItem {...item}/> )
}
</Card>
};
export default Logs;