我正在尝试在 React Native 类中实现两个组件之间的 EventEmitter/Subscriber 关系。我看到参考了以下材料:
这些解决方案足以满足我想要完成的任务,但是,它们需要在接收组件上使用
mixins: [Subscribable.Mixin]
才能与 Subscriber
正常工作。不幸的是,我正在使用 ES6 并从 Component
扩展我的类,所以我不能使用这个 mixin 语法。
我的问题是:如何在不使用 mixins 的情况下在 ES6 中实现上述解决方案?
您不需要 mixin 来使用 EventEmitters。
简单演示:
import EventEmitter from 'EventEmitter';
let x = new EventEmitter();
function handler(arg) {
console.log(`event-name has occurred! here is the event data arg=${JSON.stringify(arg)}`);
}
x.addListener('event-name', handler);
x.emit('event-name', { es6rules: true, mixinsAreLame: true });
addListener
的完整签名需要三个参数:
EventEmitter.addListener(eventName, handler, handlerContext)
在 React 组件中,您可能希望使用该上下文参数,以便处理程序可以是类方法而不是内联函数,并且仍然保留
this == component instance
。例如:
componentDidMount() {
someEmitter.addListener('awesome', this.handleAwesomeEvents, this);
// the generalist suggests the alternative:
someEmitter.addListener('awesome', this.handleAwesomeEvents.bind(this));
}
handleAwesomeEvents = (event) => {
let awesomeness = event.awesomeRating;
// if you don't provide context in didMount,
// "this" will not refer to the component,
// and this next line will throw
this.setState({ awesomeness });
};
仅供参考:我通过查看臭名昭著的订阅混合的绝对不神奇的实现得到了这一点。 Google 搜索结果基本上是 Ramsay 的基于 mixin 的单一演示的回声室。
附注至于将此发射器暴露给另一个组件,我可能会让拥有的组件提供一个用于接收发射器引用的函数,然后创建发射器的组件将有条件地使用发射器执行该道具。
// owner's render method:
<ThingThatEmits
onEmitterReady={(emitter) => this.thingEmitter = emitter}
/>
// inside ThingThatEmits:
componentDidMount() {
this.emitter = new EventEmitter();
if(typeof this.props.onEmitterReady === 'function') {
this.props.onEmitterReady(this.emitter);
}
}
这可能是一个很晚的答案,但我只是将其发布给任何可能觉得有用的人。
截至撰写本答案时(2020 年 7 月),React Native 自
0.60.0+
版本以来已经发生了很大变化,您可以使用 EventEmitter
的实例,或静态调用 DeviceEventEmitter
方法。
这是一个使用
EventEmitter
的示例:
import { EventEmitter } from 'events';
const newEvent = new EventEmitter();
// then you can use: "emit", "on", "once", and "off"
newEvent.on('example.event', () => {
// ...
});
另一个使用
DeviceEventEmitter
的示例:
import { DeviceEventEmitter } from 'react-native';
// then you can directly use: "emit", "addListener", and "removeAllListeners"
DeviceEventEmitter.emit('example.event', ['foo', 'bar', 'baz']);
希望这对于那些仍在寻找在 React Native 中实现自定义事件的方法的人来说很方便。
我能够通过 react-mixin 找到解决方法。不确定它是否正确,但无需任何修改即可运行。关键是在类定义后面添加
reactMixin(DetailView.prototype, Subscribable.Mixin);
。
继续使用 EventEmitter 和 Subscribable 的示例:
'use strict';
var reactMixin = require('react-mixin');
var React = require('react-native');
var EventEmitter = require('EventEmitter');
var Subscribable = require('Subscribable');
var {
AppRegistry,
StyleSheet,
Text,
View,
NavigatorIOS
} = React;
class MainView extends Component {
constructor(props){
super(props);
this.EventEmitter = new EventEmitter();
}
somethingHappenedFunction(){
this.EventEmitter.emit("update_event", { message: "hello from up here"});
}
//rest of the class
}
class DetailView extends Component {
componentDidMount(){
this.addListenerOn(this.props.events, 'update_event', this.miscFunction);
}
miscFunction(args) {
console.log("message: %s", args.message);
}
//rest of the class
}
reactMixin(DetailView.prototype, Subscribable.Mixin);
使用react-native 0.69.0我这样解决了它:
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
const emitter = new EventEmitter();
emitter.addListener('event name', (...args) => console.log('emitted with', args));
emitter.emit('event name', { message: 'Foo' });
在我看来,我认为不需要第三方库,在下面,您可以找到适用于任何 JavaScript/TypeScript 环境(如 Web、React Native 或 Node.js)的自定义解决方案:
const isFunction = (arg: any): boolean => typeof arg === 'function';
const isString = (arg: any): boolean => typeof arg === 'string';
type CallbackType = (data: unknown) => void;
type AddEventListenerReturnType = () => boolean;
type ListenersType = {
count: number;
refs: Record<string, {name: string; callback: CallbackType}>;
};
class EventRegister {
private static _Listeners: ListenersType = {
count: 0,
refs: {},
};
public addEventListener(
eventName: string,
callback: CallbackType,
): AddEventListenerReturnType {
if (isString(eventName) && isFunction(callback)) {
EventRegister._Listeners.count++;
const eventId = 'el' + EventRegister._Listeners.count;
EventRegister._Listeners.refs[eventId] = {
name: eventName,
callback,
};
return this.removeEventListener.bind(this, eventId);
}
throw new Error('Event name must be a string, Callback must be a function');
}
public removeEventListener(eventId: string): boolean {
if (isString(eventId)) {
return delete EventRegister._Listeners.refs[eventId];
}
throw new Error('Event ID must be a string');
}
public removeAllListeners(): boolean {
let removeError = false;
Object.keys(EventRegister._Listeners.refs).forEach((_id) => {
const removed = delete EventRegister._Listeners.refs[_id];
removeError = !removeError ? !removed : removeError;
});
return !removeError;
}
public emitEvent(eventName: string, data: unknown): void {
Object.keys(EventRegister._Listeners.refs).forEach((_id) => {
if (
EventRegister._Listeners.refs[_id] &&
eventName === EventRegister._Listeners.refs[_id].name
) {
EventRegister._Listeners.refs[_id].callback(data);
}
});
}
}
const EventEmitter = new EventRegister();
export {EventEmitter};