react-发布订阅模式实现非父子通信

loading 2022年12月05日 88次浏览

首先先给出原生js的发布订阅模式:

发布订阅模式也称为观察者模式

简单版本:

//调度中心
let bus = {
    list: [],

    subscribe(cb) {
        this.list.push(cb);
    },

    publish(val) {
        this.list.forEach(cb => {
            cb && cb(val);
        })
    }
}

//订阅
bus.subscribe((val) => {
    console.log(111, val); //111,333
})
bus.subscribe((val) => {
    console.log(222, val); //222,333
})

//发布
setTimeout(() => {
    bus.publish(333)
}, 0);

稍全面点的版本:

class EventEmitter {
    constructor() {
        this.bus = {};
    }

    subscribe(eventName, callback) {
        if(!this.bus[eventName]) this.bus[eventName] = [];
        this.bus[eventName].push(callback);
        return {
            unsubscribe: () => {
                this.bus[eventName] = this.bus[eventName].filter(item => item !== callback);
            }
        };
    }

    emit(eventName, args = []) {
        let cbArr = this.bus[eventName];
        if(!cbArr) return [];
        return cbArr.map(cb => cb(...args));
    }
}

/**
 * const emitter = new EventEmitter();
 *
 * // Subscribe to the onClick event with onClickCallback
 * function onClickCallback() { return 99 }
 * const sub = emitter.subscribe('onClick', onClickCallback);
 *
 * emitter.emit('onClick'); // [99]
 * sub.unsubscribe(); // undefined
 * emitter.emit('onClick'); // []
 */

那么react中调用发布订阅模式也是同理(包括常用的数据管理工具redux的原理也是基于该模式)

export default class App extends Component {
    state = {
        filmList : []
    }
    constructor() {
        super()
        axios.get(`/test.json`).then(res => {
            this.setState({
                filmList: res.data.data.films
            })
        })
    }

    render() {
        return (
            <div>
                {
                    // 电影列表组件
                    this.state.filmList.map(item =>
                        <FilmItem key={item.filmId} {...item}></FilmItem>
                    )
                }

                {/* 电影详细信息组件 */}
                <FilmDetail></FilmDetail>
            </div>
        )
    }
}

class FilmItem extends Component {
    render() {
        let { name, poster, synopsis } = this.props

        return (
            // 发布者 将电影详细信息作为参数传给订阅者的回调函数
            <div className='filmItem' onClick={() => {
                bus.publish(synopsis)
            }}>
                <img src={poster} alt={name} />
                <h4>{name}</h4>
            </div>
        )
    }
}

class FilmDetail extends Component {
    // 将接收到的信息存到状态里才能被渲染到页面上
    state = {
        info: ''
    }

    // 将订阅函数写在constructor里 使得组件被初始化时就执行订阅
    constructor() {
        super();
        // 将发布者传过来的信息设置成新的state
        bus.subscribe((info) => {
            this.setState({
                info: info
            })
        })
    }

    render() {
        return (
            <div className='filmDetail'>
                {this.state.info}
            </div>
        )
    }
}
//调度中心
let bus = {
    list: [],

    subscribe(cb) {
        this.list.push(cb);
    },

    publish(val) {
        this.list.forEach(cb => {
            cb && cb(val);
        })
    }
}