【React】组件通信

组件通信

父传子 - 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

用法:

  1. createContext 创建一个上下文对象 MyContext 并返回
  2. 组件内传递。可以通过将上下文对象 MyContext 传给 useContext() 来读取上下文的值(比如下面的 2 )
    在这里插入图片描述
  3. 组件间传递。在顶层组件 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;

在这里插入图片描述

受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。

案例:

  • 一个基本的受控组件
  • 对用户输入的内容进行限制
  • 文本域
  • 单选与多选框
  • 下拉列表