/src/views/VoteStyle.jsx
import styled from "styled-components";
const VoteStyle = styled.div`
box-sizing: border-box;
margin: 20px auto;
padding: 10px 20px;
width: 300px;
border: 1px solid #ddd;
.title {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 50px;
font-size: 18px;
border-bottom: 1px dashed #ddd;
span {
color: #ff4d4f;
}
}
.main-box {
padding: 10px 0;
p {
font-size: 14px;
line-height: 30px;
}
}
.footer-box {
.ant-btn {
margin-right: 10px;
}
}
`;
export default VoteStyle;
/src/views/Vote.jsx
import { useState } from "react";
import { Button } from "antd";
import VoteStyle from "./VoteStyle";
export default function Vote() {
let [supNum, setSupNum] = useState(10);
let [oppNum, setOppNum] = useState(5);
// 定义普通函数;
const handle = (type) => {
if (type === `sup`) {
setSupNum(supNum + 1)
return
}
setOppNum(oppNum + 1)
}
return (
React其实也不难!
{supNum + oppNum}
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
}
父传子:父组件想把自己的一些信息传递给子组件进行渲染。
在父组件调用子组件的时候,可以把一些数据或者jsx元素,基于属性传递给子组件。
55
插槽的操作在vue中很常用,因为vue有明确的插槽处理机制。但是在React中,所谓的插槽信息,也都是我们基于props.children与React.Children自己处理的,所以插槽机制用的不多!
子传父:在子组件中,可以把一些自己的信息传递给父亲,或者修改父亲中的一些数据信息。
或者具备相同父亲的兄弟组件
通信:
父子通信『或者是具备相同父亲的兄弟组件通信』:
/src/views/VoteStyle.jsx
import styled from "styled-components";
const VoteStyle = styled.div`
box-sizing: border-box;
margin: 20px auto;
padding: 10px 20px;
width: 300px;
border: 1px solid #ddd;
.title {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 50px;
font-size: 18px;
border-bottom: 1px dashed #ddd;
span {
color: #ff4d4f;
}
}
.main-box {
padding: 10px 0;
p {
font-size: 14px;
line-height: 30px;
}
}
.footer-box {
.ant-btn {
margin-right: 10px;
}
}
`;
export default VoteStyle;
/src/views/Vote.jsx
import { useState } from "react";
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
export default function Vote() {
// 父组件具备状态和修改状态的方法
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
const change = (type) => {
if (type === "sup") {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
return (
React其实也不难!
{supNum + oppNum}
{/* 基于属性把支持数/反对数传递给子组件 */}
{/* 基于属性把修改状态的方法传递给子组件 */}
);
}
/src/views/VoteFooter.jsx
import { PureComponent } from "react";
import { Button } from "antd";
import PT from "prop-types";
export default class VoteFooter extends PureComponent {
// 属性规则校验
static propTypes = {
change: PT.func.isRequired,
};
render() {
let { change } = this.props;
return (
);
}
}
/src/views/VoteMain.jsx
import { memo, useMemo } from "react";
import PT from "prop-types";
const VoteMain = function VoteMain(props) {
let { supNum, oppNum } = props;
let ratio = useMemo(() => {
let result = "- -",
total = supNum + oppNum;
if (total > 0) {
result = ((supNum / total) * 100).toFixed(2) + "%";
}
return result;
}, [supNum, oppNum]);
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
支持比率:{ratio}
);
};
// 属性的规则校验
VoteMain.defaultProps = {
supNum: 0,
oppNum: 0,
};
VoteMain.propTypes = {
supNum: PT.number,
oppNum: PT.number,
};
export default memo(VoteMain);
/src/views/VoteStyle.jsx
import styled from "styled-components";
const VoteStyle = styled.div`
box-sizing: border-box;
margin: 20px auto;
padding: 10px 20px;
width: 300px;
border: 1px solid #ddd;
.title {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 50px;
font-size: 18px;
border-bottom: 1px dashed #ddd;
span {
color: #ff4d4f;
}
}
.main-box {
padding: 10px 0;
p {
font-size: 14px;
line-height: 30px;
}
}
.footer-box {
.ant-btn {
margin-right: 10px;
}
}
`;
export default VoteStyle;
/src/views/Vote.jsx
import { useRef, useEffect } from "react";
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
export default function Vote() {
let mainIns = useRef(),
footerIns = useRef(),
subBox = useRef();
useEffect(() => {
let { supNum, oppNum, change } = mainIns.current;
subBox.current.innerHTML = supNum + oppNum;
let { supBtn, oppBtn } = footerIns.current;
supBtn.current.addEventListener("click", change.bind(null, "sup"));
oppBtn.current.addEventListener("click", change.bind(null, "opp"));
}, []);
return (
React其实也不难!
0
);
}
/src/views/VoteFooter.jsx
import { Component, createRef } from "react";
import { Button } from "antd";
export default class VoteFooter extends Component {
supBtn = createRef();
oppBtn = createRef();
render() {
return (
);
}
}
/src/views/VoteMain.jsx
import { useState, useMemo, forwardRef, useImperativeHandle } from "react";
const VoteMain = function VoteMain(props, ref) {
// 定义状态
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
// 计算支持比率
let ratio = useMemo(() => {
let result = "- -",
total = supNum + oppNum;
if (total > 0) {
result = ((supNum / total) * 100).toFixed(2) + "%";
}
return result;
}, [supNum, oppNum]);
// 修改状态的方法
const change = (type) => {
console.log("AAA");
if (type === "sup") {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
// 暴露信息给外面使用
useImperativeHandle(ref, () => {
return {
supNum,
oppNum,
change,
};
});
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
支持比率:{ratio}
);
};
export default forwardRef(VoteMain);
/src/views/VoteStyle.jsx
import styled from "styled-components";
const VoteStyle = styled.div`
box-sizing: border-box;
margin: 20px auto;
padding: 10px 20px;
width: 300px;
border: 1px solid #ddd;
.title {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 50px;
font-size: 18px;
border-bottom: 1px dashed #ddd;
span {
color: #ff4d4f;
}
}
.main-box {
padding: 10px 0;
p {
font-size: 14px;
line-height: 30px;
}
}
.footer-box {
.ant-btn {
margin-right: 10px;
}
}
`;
export default VoteStyle;
/src/views/Vote.jsx
import { useRef, useEffect, useState } from "react";
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
export default function Vote() {
let mainIns = useRef(), //{current:null}
footerIns = useRef(),
subBox = useRef();
// 基于ref的父子组件通信-复用ref对象可以被修改-结合类props操作
let [total, setTotal] = useState(0);
mainIns.changeTotal = setTotal;
useEffect(() => {
let { supNum, oppNum, change } = mainIns.current;
subBox.current.innerHTML = supNum + oppNum;
let { supBtn, oppBtn } = footerIns.current;
//supBtn.current.addEventListener("click", change.bind(null, "sup"));//这里会造成内存泄露,只绑定不删除。
//oppBtn.current.addEventListener("click", change.bind(null, "opp"));
let callback1 = change.bind(null, "sup");
supBtn.current.addEventListener("click", callback1);
let callback2 = change.bind(null, "opp");
oppBtn.current.addEventListener("click", callback2);
return () => {
supBtn.current.removeEventListener("click", callback1);
oppBtn.current.removeEventListener("click", callback2);
};
}, [total]);
return (
React其实也不难!
{total}
);
}
/src/views/VoteFooter.jsx
import { Component, createRef } from "react";
import { Button } from "antd";
export default class VoteFooter extends Component {
supBtn = createRef();
oppBtn = createRef();
render() {
return (
);
}
}
/src/views/VoteMain.jsx
import { useState, useMemo, forwardRef, useImperativeHandle } from "react";
const VoteMain = function VoteMain(props, ref) {
// 定义状态
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
// 计算支持比率
let ratio = useMemo(() => {
let result = "- -",
total = supNum + oppNum;
if (total > 0) {
result = ((supNum / total) * 100).toFixed(2) + "%";
}
return result;
}, [supNum, oppNum]);
let { changeTotal } = ref;
// 修改状态的方法
const change = (type) => {
console.log("AAA", changeTotal); //如果不在父组件移除旧绑定的事件的话。可以看到这里,会越点打印得越多,也就是内存泄露。
if (type === "sup") {
setSupNum(supNum + 1);
changeTotal(oppNum + supNum + 1);
return;
}
setOppNum(oppNum + 1);
changeTotal(oppNum + supNum + 1);
};
// 暴露信息给外面使用
useImperativeHandle(ref, () => {
return {
supNum,
oppNum,
change,
};
});
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
支持比率:{ratio}
);
};
export default forwardRef(VoteMain);
把需要共享的信息,放在祖先组件的上下文中。
创建一个js文件,并且在js文件创建并导出一个React上下文对象。
// 创建一个上下文对象
import { createContext } from "react";
const ThemeContext = createContext();//名称可以随意起。
export default ThemeContext;
让祖先元素具备:需要共享的状态
及需要共享的修改状态的方法
。
import { useState } from "react";
export default function Vote() {
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
const change = (type) => {
if (type === "sup") {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
console.log(ThemeContext.Provider);
}
在祖先元素中,把需要共享的数据放在上下文中。
import ThemeContext from "../ThemeContext";
export default function Vote() {
return (
//...
);
}
import { useState } from "react";
import ThemeContext from "../ThemeContext";
// console.log(ThemeContext);
export default function Vote() {
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
const change = (type) => {
if (type === "sup") {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
console.log(ThemeContext.Provider);
return (
//...
);
}
后代组件需要什么信息,只需要去上下文中获取即可:
单纯基于React上下文对象.Consumer组件
获取上下文中存放信息。
适用于所有类型的组件。
弊端:用起来麻烦一些,组件的虚拟DOM层级凭空要多两层。
import ThemeContext from "../ThemeContext";
export default function VoteMain() {
return (
{(value) => {
// value:存储的是所有上下文中的信息
console.log(value, `value:存储的是所有上下文中的信息`);
let { supNum, oppNum } = value;
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
}}
);
}
import { Component } from "react";
import { Button } from "antd";
import ThemeContext from "../ThemeContext";
export default class VoteFooter extends Component {
render() {
return (
{(value) => {
// value:存储的是所有上下文中的信息
console.log(value, `value:存储的是所有上下文中的信息`);
let { change } = value;
return (
);
}}
);
}
}
函数组件:基于React上下文对象.Consumer组件
与useContext()
获取上下文中存放信息。
import ThemeContext from "../ThemeContext";
import { useContext } from "react";
export default function VoteMain() {
let context = useContext(ThemeContext); //获取所有上下文信息
console.log(context, `context:存储的是所有上下文中的信息`);
let { supNum, oppNum } = context;
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
}
类组件:基于React上下文对象.Consumer组件
与类的contextType静态属性
获取上下文中存放信息。
import { Component } from "react";
import { Button } from "antd";
import ThemeContext from "../ThemeContext";
export default class VoteFooter extends Component {
// 获取上下文中的信息
static contextType = ThemeContext;
render() {
console.log(`this.context存储获取的上下文信息`, this.context);
let { change } = this.context; // this.context存储获取的上下文信息;
return (
);
}
}
/src/ThemeContext.js
// 创建一个上下文对象
import { createContext } from "react";
const ThemeContext = createContext();
export default ThemeContext;
/src/views/VoteStyle.jsx
import styled from "styled-components";
const VoteStyle = styled.div`
box-sizing: border-box;
margin: 20px auto;
padding: 10px 20px;
width: 300px;
border: 1px solid #ddd;
.title {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 50px;
font-size: 18px;
border-bottom: 1px dashed #ddd;
span {
color: #ff4d4f;
}
}
.main-box {
padding: 10px 0;
p {
font-size: 14px;
line-height: 30px;
}
}
.footer-box {
.ant-btn {
margin-right: 10px;
}
}
`;
export default VoteStyle;
/src/views/Vote.jsx
import { useState } from "react";
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import ThemeContext from "../ThemeContext";
// console.log(ThemeContext);
export default function Vote() {
let [supNum, setSupNum] = useState(10),
[oppNum, setOppNum] = useState(5);
const change = (type) => {
if (type === "sup") {
setSupNum(supNum + 1);
return;
}
setOppNum(oppNum + 1);
};
console.log(ThemeContext.Provider);
return (
React其实也不难!
{supNum + oppNum}
);
}
/src/views/VoteFooter.jsx
import { Component } from "react";
import { Button } from "antd";
import ThemeContext from "../ThemeContext";
export default class VoteFooter extends Component {
// 获取上下文中的信息
static contextType = ThemeContext;
render() {
console.log(`this.context存储获取的上下文信息`, this.context);
let { change } = this.context; // this.context存储获取的上下文信息
return (
);
}
}
/* export default class VoteFooter extends Component {
render() {
return (
{(value) => {
// value:存储的是所有上下文中的信息
console.log(value, `value:存储的是所有上下文中的信息`);
let { change } = value;
return
;
}}
);
}
} */
/src/views/VoteMain.jsx
import ThemeContext from "../ThemeContext";
import { useContext } from "react";
export default function VoteMain() {
let context = useContext(ThemeContext); //获取所有上下文信息
console.log(context, `context:存储的是所有上下文中的信息`);
let { supNum, oppNum } = context;
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
}
/* export default function VoteMain() {
return (
{(value) => {
// value:存储的是所有上下文中的信息
console.log(value,`value:存储的是所有上下文中的信息`);
let { supNum, oppNum } = value;
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
}}
);
} */
父子通信:属性「偶尔获取子组件实例/暴露方法」。
小型项目中,非父子(或者也不是同一个父组件的),可以基于 祖先和后代「主要是上下文方案」进行处理!
因为根组件必定可以是一个项目中所用到的所有业务组件的共同祖先元素。所以这种方式,理论上也可以当成是能通用的方式。
但是一旦项目比较大,需要共享的信息多了,都放在祖先中也不方便维护和管理!
所以除父子外,其余组件通信方式,我们一般采用的 “公共状态管理”
React阶段/day0527/src/views/TodoList.jsx
import { useState, useEffect } from "react";
import { Button, Input, message } from "antd";
import styled from "styled-components";
import TodoItem from "./TodoItem";
// 具备有效期的LocalStorage存储
const storage = {
set(key, value) {
localStorage.setItem(
key,
JSON.stringify({
time: +new Date(),
value,
})
);
},
get(key, cycle = 2592000000) {
cycle = +cycle;
if (isNaN(cycle)) cycle = 2592000000;
let data = localStorage.getItem(key);
if (!data) return null;
let { time, value } = JSON.parse(data);
if (+new Date() - time > cycle) {
storage.remove(key);
return null;
}
return value;
},
remove(key) {
localStorage.removeItem(key);
},
};
// 组件样式
const TodoListStyle = styled.div`
box-sizing: border-box;
margin: 0 auto;
width: 400px;
.header {
display: flex;
align-items: center;
padding-bottom: 20px;
padding-top: 20px;
border-bottom: 1px dashed #aaa;
.ant-btn {
margin-left: 10px;
}
}
.main {
padding-top: 20px;
.item {
margin-bottom: 20px;
font-size: 14px;
.ant-btn {
margin-right: 10px;
}
.ant-input {
width: 60%;
}
.text {
padding-bottom: 10px;
}
}
}
`;
export default function TodoList() {
// 定义状态
let [text, setText] = useState(""),
[list, setList] = useState(() => {
// 只有组件第一次渲染才会执行
let data = storage.get("CACHE_TASKLIST");
return data || [];
});
// 监听任务列表的变化,存储到本地
useEffect(() => {
console.log(`storage版本`);
storage.set("CACHE_TASKLIST", list);
}, [list]);
// 新增任务
const submit = () => {
if (text.length === 0) {
message.warning("任务描述不能为空!");
return;
}
list.push({
id: +new Date(),
text,
});
setList([...list]);
setText("");
};
// 修改/删除任务
const handle = (type, id, updateText) => {
if (type === "update") {
// 修改任务
list = list.map((item) => {
if (+item.id === +id) {
item.text = updateText;
}
return item;
});
setList(list);
return;
}
// 删除任务
list = list.filter((item) => +item.id !== +id);
setList(list);
};
return (
{
setText(ev.target.value.trim());
}}
/>
{list.map((item) => {
return ;
})}
);
}
React阶段/day0527/src/views/TodoItem.jsx
import { useState } from "react";
import { Button, Input, Popconfirm, message } from "antd";
import PT from "prop-types";
const TodoItem = function TodoItem(props) {
// 接收属性 & 定义状态
let { info, handle } = props;
let [isUpdate, setIsUpdate] = useState(false),
[updateText, setUpdateText] = useState("");
// 触发修改状态
const triggerUpdate = () => {
setIsUpdate(true);
setUpdateText(info.text);
};
// 取消修改
const cancelUpdate = () => {
setIsUpdate(false);
setUpdateText("");
};
// 保存任务
const saveInfo = () => {
if (updateText.length === 0) {
message.warning("修改的任务描述不能为空");
return;
}
handle("update", info.id, updateText);
cancelUpdate();
};
// 删除任务
const removeInfo = () => {
handle("delete", info.id);
};
return (
-
{isUpdate ? (
{
setUpdateText(ev.target.value.trim());
}}
/>
) : (
{info.text}
)}
{isUpdate ? (
<>
>
) : (
)}
);
};
/* 属性规则校验 */
TodoItem.defaultProps = {};
TodoItem.propTypes = {
info: PT.object.isRequired,
handle: PT.func.isRequired,
};
export default TodoItem;
封装一个antd按钮,在执行点击事件后,会进行loading。
/src/views/Demo.jsx
import { Button } from "antd";
import { useState } from "react";
const delay = function delay(interval = 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, interval);
});
};
const Demo = function Demo() {
let [loading, setLoading] = useState(false);
const handle = async () => {
setLoading(true);
try {
await delay(2000);
// ...
} catch (_) {}
setLoading(false);
};
return (
);
};
export default Demo;
把传递的属性拷贝一份,这样就可以直接操作props了。
把特殊的几个属性值移除掉并且做特殊处理。
一些事件相关的属性值。
一些要进行二次封装的属性值。
插槽-children属性。
特殊处理要配合状态或bind改指向之类的。
let [submitLoading, setSubmitLoading] = useState(!!loading);
const submit = async (ev) => {
setSubmitLoading(true);
try {
if (typeof onClick === "function") {
await onClick(ev);
}
} catch (_) {}
setSubmitLoading(false);
};
把处理过后的属性及插槽设置在原组件上。
/* 对Antd中Button组件的二次封装:自动加Loading防抖处理 */
import { Button } from "antd";
import { useState } from "react";
const ButtonAgain = function ButtonAgain(props) {
// 把传递的属性拷贝一份,这样就可以直接操作props了
props = { ...props };
// onClick:点击时候要干的事{传递的handle函数}
// children:插槽信息
// loading:我们可以把传递的loading作为我们自己管理的loading的初始值
let { onClick, children, loading } = props;
// 把特殊的几个属性值移除掉
if (props.onClick) delete props.onClick;
if (props.loading) delete props.loading;
if (props.children) delete props.children;
// 自己要做的特殊处理
let [submitLoading, setSubmitLoading] = useState(!!loading);
const submit = async (ev) => {
setSubmitLoading(true);
try {
if (typeof onClick === "function") {
await onClick(ev);
}
} catch (_) {}
setSubmitLoading(false);
};
// 剩下的属性,原封不动的给Antd中的Button即可
return (
);
};
export default ButtonAgain;
/src/components/ButtonAgain.jsx
/* 对Antd中Button组件的二次封装:自动加Loading防抖处理 */
import { Button } from "antd";
import { useState } from "react";
const ButtonAgain = function ButtonAgain(props) {
// 把传递的属性拷贝一份,这样就可以直接操作props了
props = { ...props };
// onClick:点击时候要干的事{传递的handle函数}
// children:插槽信息
// loading:我们可以把传递的loading作为我们自己管理的loading的初始值
let { onClick, children, loading } = props;
// 把特殊的几个属性值移除掉
if (props.onClick) delete props.onClick;
if (props.loading) delete props.loading;
if (props.children) delete props.children;
// 自己要做的特殊处理
let [submitLoading, setSubmitLoading] = useState(!!loading);
const submit = async (ev) => {
setSubmitLoading(true);
try {
if (typeof onClick === "function") {
await onClick(ev);
}
} catch (_) {}
setSubmitLoading(false);
};
// 剩下的属性,原封不动的给Antd中的Button即可
return (
);
};
export default ButtonAgain;
/src/views/Demo.jsx
import ButtonAgain from "../components/ButtonAgain";
const delay = function delay(interval = 1000) {
console.log(1111);
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, interval);
});
};
const Demo = function Demo() {
const handle = async (ev) => {
try {
await delay(2000);
// ...
} catch (_) {}
};
return (
删除
完成
{
console.log(ev);
}}
>
哈哈哈
);
};
export default Demo;