类组件的非父子通信

非父子通信

状态提升(中间人模式)

pubilc里放的是我们整个项目的静态资源,每次可以通过localhost:3000访问资源也是先访问的public里的index.html

import React, { Component } from 'react';
import axios from 'axios'
import './css/02-communination.css'
class App extends Component {
    constructor() {
        super()//类的继承
        this.state = {
            filmList: [],
            //转化为状态
            info:''
        }
        axios.get('http://localhost:3000/test.json').then(res => {
            console.log(res.data.data.films)
            this.setState({
              filmList:res.data.data.films
            })
        })
        }
  render() {
    return (
        <div>
            {/* {this.state.info} */}
            {this.state.filmList.map(item=>
                <FilmItem key={item.filmId} {...item} onEvent={(value) => {
                    //console.log('父组件接收,', value)
                    this.setState({
                        info:value
                    })

                }}></FilmItem>)
            }
       <FilmDetail info={this.state.info}></FilmDetail>
      </div>
    );
  }
}

//受控组件
class FilmItem extends Component {
    render() {
    //   console.log(this.props)
      let {name,poster,grade,synopsis}=this.props
    return (
        <div className='filmitem' onClick={() => {
            // console.log(synopsis)
            this.props.onEvent(synopsis)
        }}>
            <img src={poster} alt={name} />
            <h4> {this.props.name}</h4>
            <div>观众评分{grade}</div>
      </div>
    );
  }
}

class FilmDetail extends Component {
  render() {
    return (
      <div className='filmdetail'>
       {this.props.info}
      </div>
    );
  }
}




export default App

效果是这样

发布订阅模式

发布应该晚于订阅,并且发布应该是异步

发布者是触发事件,发送数据的,你要是没有订阅者,发给谁呢?所以订阅者要先进行订阅,这样订阅者才能接收到数据,订阅者就是监听事件接收数据的那个组件

import React, { Component } from 'react';
import axios from 'axios'
import './css/02-communination.css'


let bus = {
    //订阅

    list:[],
    subscribe(callback) {
        console.log(callback)
        this.list.push(callback)
        
    },
    //发布
    publish(text) {
        //遍历所有的列表,将sub里存入的回调函数们执行
        console.log(this.list)
        this.list.forEach(callback => {
            callback&&callback(text)
        })
    }


}

class App extends Component {
    constructor() {
        super()//类的继承
        this.state = {
            filmList: [],
        }
        axios.get('http://localhost:3000/test.json').then(res => {
            console.log(res.data.data.films)
            this.setState({
              filmList:res.data.data.films
            })
        })
        }
  render() {
    return (
        <div>
            {/* {this.state.info} */}
            {this.state.filmList.map(item=>
                <FilmItem key={item.filmId} {...item} ></FilmItem>)
            }
       <FilmDetail ></FilmDetail>
      </div>
    );
  }
}

//受控组件
class FilmItem extends Component {
    render() {
    //   console.log(this.props)
      let {name,poster,grade,synopsis}=this.props
    return (
        <div className='filmitem' onClick={() => {
          bus.publish(synopsis)
            
        }}>
            <img src={poster} alt={name} />
            <h4> {this.props.name}</h4>
            <div>观众评分{grade}</div>
      </div>
    );
  }
}

class FilmDetail extends Component {
    constructor() {
        super()
        this.state = {
            info:''
        }
        bus.subscribe((info) => {
            console.log('我在filmDetail定义',info)
            this.setState({
                info:info
            })
        })
    }
  render() {
    return (
      <div className='filmdetail'>
       {this.state.info}
      </div>
    );
  }
}




export default App

context方案

const GlobalContext=React.createContext()

使用Provider包住父组件

  render() {
      return (
        <GlobalContext.Provider> 
        <div>
            {/* {this.state.info} */}
            {this.state.filmList.map(item=>
                <FilmItem key={item.filmId} {...item} ></FilmItem>)
            }
       <FilmDetail info={this.state.info}></FilmDetail>
              </div>
              </GlobalContext.Provider>
    );
  }

Customer包住子组件,里面要放回调函数,记得return出来

因为回调函数会把很关键的参数value传过来,

class FilmItem extends Component {
    render() {
    //   console.log(this.props)
      let {name,poster,grade,synopsis}=this.props
    return (
        <GlobalContext.Consumer>
            {
                 ()=>{
                     return (
                         <div className='filmitem' onClick={() => {
                             console.log(synopsis)
                    }}>
                    <img src={poster} alt={name} />
                    <h4> {this.props.name}</h4>
                    <div>观众评分{grade}</div>
                    </div>
                )   
            }
           }

</GlobalContext.Consumer>
    );
  }
}

通过context还有状态,修改info:

import React, { Component } from 'react';
import axios from 'axios'
import './css/02-communination.css'

const GlobalContext=React.createContext()

class App extends Component {
    constructor() {
        super()//类的继承
        this.state = {
            filmList: [],
            info:''
        }
        axios.get('http://localhost:3000/test.json').then(res => {
            console.log(res.data.data.films)
            this.setState({
              filmList:res.data.data.films
            })
        })
        }
  render() {
      return (
          <GlobalContext.Provider value={{
              call: '打电话',
              sms: '短信',
              info: this.state.info,
              changeInfo: (value) => {
                  this.setState({
                      info:value
                  })
              }
        }}> 
        <div>
            {/* {this.state.info} */}
            {this.state.filmList.map(item=>
                <FilmItem key={item.filmId} {...item} ></FilmItem>)
            }
       <FilmDetail info={this.state.info}></FilmDetail>
              </div>
              </GlobalContext.Provider>
    );
  }
}

//受控组件
class FilmItem extends Component {
    render() {
    //   console.log(this.props)
      let {name,poster,grade,synopsis}=this.props
    return (
        <GlobalContext.Consumer>
            {
                (value) => {
                     console.log(value)
                     return (
                         <div className='filmitem' onClick={() => {
                             console.log(synopsis)
                             value.changeInfo(synopsis)
                    }}>
                    <img src={poster} alt={name} />
                    <h4> {this.props.name}</h4>
                    <div>观众评分{grade}</div>
                    </div>
                )   
            }
           }

</GlobalContext.Consumer>
    );
  }
}

class FilmDetail extends Component {
  render() {
    return (
        <GlobalContext.Consumer>
           { (value)=>{
                return <div className='filmdetail'>
                detail-{value.info}
                </div>
            }}
      </GlobalContext.Consumer>
    );
  }
}




export default App

插槽

在App里调用Child组件,在Child组件里再写内容是显示不出来的,会直接把Child组件的内容搬过来

import React, { Component } from 'react';

class Child extends Component {
  render() {
    return (
      <div>
            child
            /*{'插槽'}*/
      </div>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div>
            <Child>
                <div>111</div>
        </Child>
      </div>
    );
  }
}


export default App;

怎么让他显示?使用插槽,在vue里插槽是起了一个新名字叫slot

在子组件单独使用属性this.props.children在子组件里插入

class Child extends Component {
  render() {
    return (
      <div>
            child
            {/* 插槽 */}
            {
                this.props.children//只有这一个写法
            }
      </div>
    );
  }
}

children是不会进行对号入座的,如果写了多个插槽的入口,就会把children的内容复制多遍,巨头是以数组的形式存在的:

import React, { Component } from 'react';

class Child extends Component {
  render() {
    return (
      <div>
            child
            {/* 插槽 */}
            {
                this.props.children
            } {
                this.props.children
            } {
                this.props.children
            }
      </div>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div>
            <Child>
                <div>111</div>
                <div>222</div>
                <div>333</div>
        </Child>
      </div>
    );
  }
}


export default App;

所以如果我们想拆开,可以使用数组的下标来访问:

import React, { Component } from 'react';

class Child extends Component {
  render() {
    return (
      <div>
            child
            {/* 插槽 */}
            {
                this.props.children[0]
            } {
                this.props.children[1]
            } {
                this.props.children[2]
            }
      </div>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div>
            <Child>
                <div>111</div>
                <div>222</div>
                <div>333</div>
        </Child>
      </div>
    );
  }
}


export default App;

改变数组的顺序可以改变显示的顺序

插槽方便组件的复用,一定程度上减少了父子通信

插槽案例

import React, { Component } from 'react';

class Navbar extends Component{
    render() {
        return (
            <div style={{background:'red'}}>
                {this.props.children}
                {/* 插槽获取按钮 */}
                <span>Navbar</span>
            </div>

        )
    }
}
class Slidebar extends Component{
    render() {
        return (
            <div style={{ background: 'yellow',width:'200px' }}>
                <ul>
                    <li>qq</li>
                    <li>qq</li>
                    <li>qq</li>
                    <li>qq</li>
                 
            </ul>
            </div>
            
        )
    }
}

class App extends Component {
    state = {
        isShow:false
    }
   
  render() {
    return (
      <div>
            <Navbar>
                <button>click</button>
            </Navbar>
            {this.state.isShow&&<Slidebar></Slidebar>}
      </div>
    );
  }
}

export default App;

写轮播图也可以使用插槽,给轮播图的图标和图片内容留个插槽,父组件自由往里塞东西

插槽分为具名查插槽和匿名插槽

<think>嗯,用户问的是Vue3中父子组件通信的方法。我需要先回想一下Vue3的相关知识,尤其是组件通信的方式。Vue3中常用的通信方式除了props和emit之外,父子组件之间的通信有哪些呢?首先想到的是全局事件总线,不过Vue3中移除了$on、$off这些方法,可能需要用mitt这样的第三方库来实现。然后是Provide/Inject,这应该是Vue3推荐的方式之一,适合祖先和后代组件之间的通信。还有Vuex或者Pinia状态管理库,用来集中管理状态,适用于多个组件共享数据的场景。另外,还有使用Reactive Objects,通过导出一个响应式对象,让不同组件引入同一个实例来实现数据共享。可能还有LocalStorage或者SessionStorage,不过这些属于浏览器存储,不是Vue特有的方法。 用户可能需要具体的实现例子,所以每种方法都应该给出简单的代码示例。比如mitt的使用步骤,如何安装、创建实例、在组件中触发和监听事件。Provide和Inject的用法,需要注意响应式数据需要使用ref或reactive,并通过provide传递。状态管理的话,可能需要简要介绍Pinia的基本用法,因为Vue3推荐使用Pinia代替Vuex。Reactive Objects的例子需要展示如何导出一个响应式对象,并在不同组件中引用。 还要考虑用户可能的疑问,比如各种方法的适用场景和优缺点。比如事件总线适合组件间松耦合的通信,但需要手动管理事件监听;Provide/Inject适合层级较深的组件,但可能会造成数据来源不明确的问题;状态管理适合大型应用,但需要引入额外库。需要提醒用户根据项目需求选择合适的方式。 需要检查是否有遗漏的方法,比如使用Vue3的app.config.globalProperties来挂载全局对象,但这种方式可能不如其他方法规范。或者使用Composition API中的自定义hook来共享状态,不过这可能和Reactive Objects的方法似。 另外,用户的问题是关于父子组件,所以兄弟组件、跨多级组件的情况都需要涵盖。确保每种方法的说明足够清晰,步骤明确,代码正确。可能需要比较不同方法之间的差异,帮助用户做出选择。 总结下来,回答的结构应该是分点列出各种方法,每种方法包含原理、适用场景、实现步骤和示例代码。同时最后给出选择建议,根据项目规模和需求推荐合适的方式。需要确保语言简洁明了,避免技术术语过于晦涩,让不同层次的开发者都能理解。</think>在 Vue3 中实现父子组件通信主要有以下 5 种常用方案,每种方案都有不同的适用场景: 一、全局事件总线(Event Bus) ```javascript // 使用 mitt 库(需先安装) import mitt from 'mitt'; const emitter = mitt(); // 组件 A(发送事件) emitter.emit('custom-event', { data: 'Hello' }); // 组件 B(接收事件) emitter.on('custom-event', (payload) => { console.log(payload.data); // 输出 Hello }); // 组件卸载时记得移除监听 onUnmounted(() => { emitter.off('custom-event'); }); ``` 二、Provide/Inject(依赖注入) ```javascript // 祖先组件 import { provide, ref } from 'vue'; const sharedData = ref('初始值'); provide('globalData', sharedData); // 后代组件 import { inject } from 'vue'; const receivedData = inject('globalData'); ``` 三、状态管理(Pinia) ```javascript // store/counter.js import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++; } } }); // 组件中使用 import { useCounterStore } from '@/stores/counter'; const store = useCounterStore(); console.log(store.count); // 访问状态 store.increment(); // 修改状态 ``` 四、响应式对象共享 ```javascript // sharedState.js import { reactive } from 'vue'; export const state = reactive({ message: '共享数据' }); // 组件 A import { state } from './sharedState'; state.message = '新消息'; // 组件 B import { state } from './sharedState'; console.log(state.message); // 输出 "新消息" ``` 五、浏览器存储 ```javascript // 组件 A localStorage.setItem('sharedKey', JSON.stringify({ data: 'value' })); // 组件 B const data = JSON.parse(localStorage.getItem('sharedKey')); ``` 方案选择建议: 1. 简单场景:事件总线(少量通信)或响应式对象 2. 层级嵌套:优先使用 Provide/Inject 3. 复杂应用:推荐使用 Pinia 状态管理 4. 页面级持久化:考虑 localStorage 注意事项: - 事件总线需注意及时清理事件监听 - Provide/Inject 要避免过度使用导致组件耦合 - Pinia 需要合理设计模块结构 - 响应式对象共享要注意单例模式实现 - 浏览器存储需处理序列化和存储限制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值