目录
3. 子传父:任务表单AddItemForm组件向任务列表List组件传参
TodoList的添加、删除、任务列表回显体现props的强大功能!注意props是只读的不能修改。
1. 项目结构及配置运行命令
运行起来的效果图
ConfirmModal组件实现的删除弹窗的效果图
2. 父传子:任务列表List组件向任务项Item组件传参
2.1 List组件
import "./List.css"
import Item from "./Item/Item.js"
import { useState } from "react"
const List = () => {
const [toDoList, setToDoList] = useState([
{
id: 1,
des: "做作业",
date: "2024-05-16",
isDone: true
}
])
return <div>
<div className="list">
{
toDoList.map(a => {
return <Item key={a.id} des={a.des} isDone={a.isDone} date={a.date} onDelete={() => handleDelete(a.id)}></Item>
})
}
</div>
</div >
}
export default List
2.2 Item组件
import "./Item.css"
const Item = (props) => {
return <div className="item" >
<div>
<div>事项:{props.des}</div>
<div>预计开始时间: {props.date}</div>
</div>
<div className="is-done">
是否已完成:{props.isDone ? '完成' : '未完成'}
</div>
</div>
}
export default Item
3. 子传父:任务表单AddItemForm组件向任务列表List组件传参
3.1 AddItemForm组件
父组件通过属性给子组件传事件(方法),通过props.属性名()调用
import "./AddItemForm.css"
import { useState } from "react"
const AddItemForm = (props) => {
const [inputDate, setInputDate] = useState("")
const [inputDesc, setInputDesc] = useState("")
// 日期选择
const dateChangeHandler = (e) => {
setInputDate(e.target.value)
}
// 事项填写触发
const descChangeHandler = (e) => {
setInputDesc(e.target.value)
}
// 添加触发
const formSubmitHandler = (e) => {
// 取消表单的默认行为
e.preventDefault();
const obj = {
date: inputDate,
des: inputDesc
}
// 触发List组件的方法
// 父组件通过属性给子组件传事件(方法),通过props.属性名()调用
props.onAddItem(obj)
// 添加完情空表单
setInputDate("")
setInputDesc("")
}
return <div className="form-contanier">
<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-btn">
<button>添加</button>
</div>
</form>
</div>
}
export default AddItemForm
3.2 List组件
属性=事件名
- 属性: 供子组件调用
- 事件名: 父组件自己定义的方法,此处必须是个方法,不是则需要{()=>{事件名()}}
import "./List.css"
import Item from "./Item/Item.js"
import AddItemForm from "../AddItemForm/AddItemForm.js"
import { useState } from "react"
const List = () => {
const [toDoList, setToDoList] = useState([
{
id: 1,
des: "做作业",
date: "2024-05-16",
isDone: true
}
])
// 新增
const handleSave = (obj) => {
setToDoList([...toDoList, { ...obj, id: new Date().getTime() + '', isDone: false }])
}
return <div>
{
/* 属性=事件名
属性: 供子组件调用
事件名: 父组件自己定义的方法,此处必须是个方法,不是则需要{()=>{事件名()}} */
}
<AddItemForm onAddItem={handleSave}></AddItemForm>
<h3>任务列表:</h3>
<div className="list">
{
toDoList.map(a => {
return <Item key={a.id} des={a.des} isDone={a.isDone} date={a.date} onDelete={() => handleDelete(a.id)}></Item>
})
}
</div>
</div >
}
export default List
4. 类插槽功能
- props.children 表示组件的标签体,可接受父组件传的JSX
- props.className 表示组件的类名,可接受父组件传的类名
4.1 Card组件,将阴影和圆角封装成组件
- Card.js
import React from 'react';
import './Card.css';
const Card = (props) => {
/*
* props.children 表示组件的标签体
* */
// console.log(props.children);
return <div className={`card ${props.className}`}>{props.children}</div>;
};
export default Card;
- Card.css
.card{
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,.2);
}
4.2 将Item组件外层容器换成card组件
- Item.js
import "./Item.css"
import Card from "../../../UI/Card/Card"
const Item = (props) => {
return <Card className="item" >
<div>
<div>事项:{props.des}</div>
<div>预计开始时间: {props.date}</div>
</div>
<div className="is-done">
是否已完成:{props.isDone ? '完成' : '未完成'}
</div>
</Card>
}
export default Item
- 此时的item已经删除css的阴影和圆角属性了
5. portal 可以将组件渲染到页面中的指定位置
因为组件默认会作为父组件的后代渲染到页面中,但是有些情况下,这种方式会带来一些问题。于是就出现了 portal
使用方法:
- 在index.html(要渲染的地方)添加一个新的元素
- 修改组件的渲染方式
- 通过ReactDOM.createPortal()作为返回值创建元素
- 参数:
1. jsx(修改前return后的代码)
2. 目标位置(DOM元素)
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!--这个容器专门用来渲染遮罩层-->
<div id="backdrop-root"></div>
</body>
</html>
- Backdrop组件,跟root是兄弟元素,不会因为其他元素的定位影响自身的位置。
Backdrop.js
import React from 'react';
import './Backdrop.css';
// createPortal方法 是直接在 react-dom 下的
// 而 createRoot方法 是在 react-dom/client 下的
import ReactDOM from "react-dom";
// 获取backdrop的根元素
const backdropRoot = document.getElementById('backdrop-root');
const Backdrop = (props) => {
return ReactDOM.createPortal(<div className="backdrop">
{props.children}
</div>, backdropRoot);
};
export default Backdrop;
Backdrop.css
.backdrop{
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,.3);
z-index: 9999;
}