组件通信
父传子 - props
function Article(props) {
return (
<div>
<h2>{props.title}</h2>
<p>{props.content}</p>
<p>状态: {props.active ? '显示' : '隐藏'}</p>
</div>
)
}
// 设置默认值方式一
// 使用 defaultProps 设置默认值,当没有父组件的值传入时使用默认值
Article.defaultProps = {
title: '默认title',
content: '默认content'
// ...
}
function App() {
return (
<>
<Article title="标题1" content="内容1" active/>
<Article title="标题2" content="内容2"/>
</>
);
}
export default App;
设置默认值方式二:props 也可以 直接解构并且赋予默认值 比如:{title=“默认标题”}
// 设置默认值方式三:
// 设置默认值也可以写为,作为类的一个静态属性:
static Article.defaultProps = {
title: '默认title',
content: '默认content'
// ...
}
设置默认值方式四:
参数类型声明一:直接使用 :
的形式进行类型声明。
参数类型声明而:也可以写成箭头函数的形式进行泛型类型声明:
const SmallPage:React.FC<Props> = (props) => {
}
该泛型会透传给函数式组件的第一个参数,作为第一个参数的类型。
父传子 - 插槽
function Article({children, title, footer=<div>默认底部</div>}) {
return (
<>
<h1>{title}</h1>
<div>
{children}
</div>
{footer}
</>
)
}
function App() {
return (
<>
<Article title="文章1" footer={<p>这是底部内容1</p>}>
<h2>标题1</h2>
<p>内容1</p>
</Article>
<Article title="文章2" footer={<p>这是底部内容2</p>}>
<h2>标题2</h2>
<p>内容2</p>
</Article>
</>
);
}
export default App;
子传父 - 自定义事件
子传父 => 状态提升
import {useState} from "react";
function Detail({onActive}) {
const [status, setStatus] = useState(false)
function handleClick() {
setStatus(!status)
onActive(status)
}
return (
<div>
<button onClick={handleClick}>点击</button>
<p style={{
display: status ? 'block' : 'none'
}}>Detail 的内容</p>
</div>
)
}
function App() {
const handleActive = (status) => {
console.log(status)
}
return (
<>
{/* 父组件接收子组件通过自定义事件 */}
<Detail
onActive={handleActive}
/>
</>
);
}
export default App;
兄弟组件通信
主要就是使用了发布订阅模式。
// 1. 组件一创建自定义事件
const event = new Event('on-card')
// 2. 组件一创建一个点击事件 触发自定义事件的派发
const clickTap = () => {
event.params = {data: '我是自定义事件的参数'}
window.dispatchEvent(event)
}
// 注:使用自定义事件的参数 需要在组件一 扩充声明
declare global {
interface Event {
params: {data: string}
}
}
// 3. 组件二监听自定义事件
// 这个一般会执行两次试因为 react 的严格模式 在 main.tsx 中删掉 StrictMode 标签即可
window.addEventListener('on-card', (event) => {
console.log('自定义事件触发了', event.params)
})
除了这种方式之外,还可以使用 mitt (在 vue 和 react 中都可以使用)。
多层级通信 - useContext
官方文档:useContext
用法:
- createContext 创建一个上下文对象 MyContext 并返回
- 组件内传递。可以通过将上下文对象 MyContext 传给 useContext() 来读取上下文的值(比如下面的 2 )
- 组件间传递。在顶层组件 MyContext.Provider 组件提供数据,在底层组件 useContext 钩子获取消费数据
import {createContext, useContext, useState} from "react";
function Section({children}) {
// 2. 顶层组件 MyContext.Provider 组件提供数据
const level = useContext(LevelContent)
return (
<section className="section">
{/*逐渐从上层提供的LevelContext中取值*/}
<LevelContent.Provider value={level + 1}>
{/*children 也就是 heading 需要使用 useContext*/}
{children}
</LevelContent.Provider>
</section>
);
}
function Heading({children}) {
// 3. 底层组件 useContext 钩子获取消费数据
const level = useContext(LevelContent)
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('未知的 level:' + level);
}
}
// 1. createContext 创建一个上下文对象
const LevelContent = createContext(0)
function App() {
return (
<Section>
<Heading>主标题</Heading>
<Section>
<Heading>副标题</Heading>
<Heading>副标题</Heading>
<Heading>副标题</Heading>
<Section>
<Heading>子标题</Heading>
<Heading>子标题</Heading>
<Heading>子标题</Heading>
<Section>
<Heading>子子标题</Heading>
<Heading>子子标题</Heading>
<Heading>子子标题</Heading>
</Section>
</Section>
</Section>
</Section>
)
}
export default App;
组件通信小案例 - 汇率转换:
App.jsx
import React, {Component} from 'react';
import Money from "./components/Money";
class App extends Component {
state = {
dollar: '',
rmb: ''
}
transformToRMB = (value) => {
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
this.setState({
dollar: value,
rmb: value === "" ? "" : (value * 7.3255).toFixed(2)
})
} else {
alert('请输入数字')
}
}
transformToDollar = (value) => {
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
this.setState({
dollar: value === "" ? "" : (value / 7.3255).toFixed(2),
rmb: value
})
} else {
alert('请输入数字')
}
}
render() {
return (
<div>
<Money text="美元" money={this.state.dollar} transform={this.transformToRMB}/>
<Money text="人民币" money={this.state.rmb} transform={this.transformToDollar}/>
</div>
);
}
}
export default App;
Money.jsx
import React from 'react';
function Money(props) {
const handleChange = (e) => {
// 将子组件的值传递给父组件 e.target.value 获取输入框的值
props.transform(e.target.value)
}
return (
<>
<fieldset>
<legend>{props.text}</legend>
<input type="text" value={props.money} onChange={handleChange}/>
</fieldset>
</>
);
}
export default Money;
受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。
案例:
- 一个基本的受控组件
- 对用户输入的内容进行限制
- 文本域
- 单选与多选框
- 下拉列表