Node.js EventEmitter
前情提要
會接觸並使用它是為了完成這個功能...
class CreateServiceFormFieldList extends PureComponent<Props, State> {
static sharedEmitter = sharedEmitter;
static ARRANGE_START = 'E/ARRANGE_START';
static ARRANGE_END = 'E/ARRANGE_END';
static ARRANGE_HOVERED = 'E/ARRANGE_HOVERED';
originY: ?number
constructor(props) {
super(props);
this.state = {
isDragging: false,
isDragOn: false,
};
this.dragElement = React.createRef();
this.onMouseMoveListener = ({ clientY }) => this.onMouseMove(clientY);
this.onMouseUpListener = () => this.onMouseUp();
this.onMouseEnterListener = () => this.onMouseEnter();
this.onMouseLeaveListener = () => this.onMouseLeave();
this.onArrangeStartListener = () => {
const elem = this.dragElement.current.parentNode;
if (elem) {
elem.addEventListener('mouseenter', this.onMouseEnterListener);
elem.addEventListener('mouseleave', this.onMouseLeaveListener);
}
};
this.onArrangeEndListener = () => {
const elem = this.dragElement.current.parentNode;
if (elem) {
elem.removeEventListener('mouseenter', this.onMouseEnterListener);
elem.removeEventListener('mouseleave', this.onMouseLeaveListener);
}
const { isDragOn } = this.state;
if (isDragOn) {
this.setState({
isDragOn: false,
});
}
};
this.onArrangeHovered = (index) => { this.hoveredRank = index; };
this.hoveredRank = -1;
}
componentDidMount() {
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_START,
this.onArrangeStartListener,
);
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_END,
this.onArrangeEndListener,
);
}
componentWillUnmount() {
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_START,
this.onArrangeStartListener,
);
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_END,
this.onArrangeEndListener,
);
}
onMouseEnter() {
const { index } = this.props;
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_HOVERED,
index,
);
this.setState({
isDragOn: true,
});
}
onMouseLeave() {
this.setState({
isDragOn: false,
});
}
onMouseMove(nowY) {
const elem = this.dragElement.current;
if (elem) {
elem.style.top = `${nowY - this.originY}px`;
}
}
onMouseUp() {
const elem = this.dragElement.current;
const {
index,
replaceFieldArrayValue,
} = this.props;
if (elem) {
elem.style.top = '0px';
}
this.originY = 0;
window.removeEventListener('mousemove', this.onMouseMoveListener, false);
window.removeEventListener('mouseup', this.onMouseUpListener, false);
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_HOVERED,
this.onArrangeHovered,
);
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_END,
);
const fieldValues = selector(store.getState(), 'formInfo');
const draggingValue = fieldValues[index];
if (~this.hoveredRank) {
if (fieldValues) {
const removedList = [
...fieldValues.slice(0, index),
...fieldValues.slice(index + 1),
];
replaceFieldArrayValue([
...removedList.slice(0, this.hoveredRank),
draggingValue,
...removedList.slice(this.hoveredRank),
]);
}
}
this.setState({
isDragging: false,
});
this.hoveredRank = -1;
}
getHeightValue() {
const elem = this.dragElement.current;
if (elem) {
return elem.clientHeight;
}
return null;
}
startArrange(originY) {
this.setState({
isDragging: true,
});
this.originY = originY;
window.addEventListener('mousemove', this.onMouseMoveListener, false);
window.addEventListener('mouseup', this.onMouseUpListener, false);
const elem = this.dragElement.current;
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_HOVERED,
this.onArrangeHovered,
);
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_START,
elem,
);
}
render() {
const {
field,
fields,
index,
} = this.props;
const {
isDragging,
isDragOn,
} = this.state;
return (
<div
style={[
styles.placement,
isDragging && styles.placementHolded,
isDragging && {
height: `${this.getHeightValue()}px`,
},
]}>
<div
ref={this.dragElement}
style={[styles.mainWrapper, isDragging && styles.wrapperDragging]}>
{createFormItem(field, fields.get(index).type)}
<div style={styles.subFeatureWrapper}>
<ItemTypeSelectBlock
formItem={field} />
<AdditionFeatureBlock
formItem={field}
removeFieldItem={() => fields.remove(index)}
copyFieldItem={value => fields.insert(index + 1, value)} />
</div>
<div style={styles.btnWrapper}>
<button
onMouseDown={({ clientY }) => this.startArrange(clientY)}
style={styles.arrangeBtn}
type="button" />
</div>
</div>
{isDragOn ? (
<div style={styles.isDragOnLine} />
) : null}
</div>
);
}
}
一臉懵逼
EventEmitter概念邏輯
Emitter instance
EventEmitter
Hub
Emit
'A'
on('A')
on('A')
on('B')
自訂事件觸發
回頭看看dragging feature
class CreateServiceFormFieldList extends PureComponent<Props, State> {
static sharedEmitter = sharedEmitter;
static ARRANGE_START = 'E/ARRANGE_START';
static ARRANGE_END = 'E/ARRANGE_END';
static ARRANGE_HOVERED = 'E/ARRANGE_HOVERED';
originY: ?number
constructor(props) {
super(props);
this.state = {
isDragging: false,
isDragOn: false,
};
this.dragElement = React.createRef();
this.onMouseMoveListener = ({ clientY }) => this.onMouseMove(clientY);
this.onMouseUpListener = () => this.onMouseUp();
this.onMouseEnterListener = () => this.onMouseEnter();
this.onMouseLeaveListener = () => this.onMouseLeave();
this.onArrangeStartListener = () => {
const elem = this.dragElement.current.parentNode;
if (elem) {
elem.addEventListener('mouseenter', this.onMouseEnterListener);
elem.addEventListener('mouseleave', this.onMouseLeaveListener);
}
};
this.onArrangeEndListener = () => {
const elem = this.dragElement.current.parentNode;
if (elem) {
elem.removeEventListener('mouseenter', this.onMouseEnterListener);
elem.removeEventListener('mouseleave', this.onMouseLeaveListener);
}
const { isDragOn } = this.state;
if (isDragOn) {
this.setState({
isDragOn: false,
});
}
};
this.onArrangeHovered = (index) => { this.hoveredRank = index; };
this.hoveredRank = -1;
}
componentDidMount() {
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_START,
this.onArrangeStartListener,
);
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_END,
this.onArrangeEndListener,
);
}
componentWillUnmount() {
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_START,
this.onArrangeStartListener,
);
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_END,
this.onArrangeEndListener,
);
}
onMouseEnter() {
const { index } = this.props;
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_HOVERED,
index,
);
this.setState({
isDragOn: true,
});
}
onMouseLeave() {
this.setState({
isDragOn: false,
});
}
onMouseMove(nowY) {
const elem = this.dragElement.current;
if (elem) {
elem.style.top = `${nowY - this.originY}px`;
}
}
onMouseUp() {
const elem = this.dragElement.current;
const {
index,
replaceFieldArrayValue,
} = this.props;
if (elem) {
elem.style.top = '0px';
}
this.originY = 0;
window.removeEventListener('mousemove', this.onMouseMoveListener, false);
window.removeEventListener('mouseup', this.onMouseUpListener, false);
CreateServiceFormFieldList.sharedEmitter.removeListener(
CreateServiceFormFieldList.ARRANGE_HOVERED,
this.onArrangeHovered,
);
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_END,
);
const fieldValues = selector(store.getState(), 'formInfo');
const draggingValue = fieldValues[index];
if (~this.hoveredRank) {
if (fieldValues) {
const removedList = [
...fieldValues.slice(0, index),
...fieldValues.slice(index + 1),
];
replaceFieldArrayValue([
...removedList.slice(0, this.hoveredRank),
draggingValue,
...removedList.slice(this.hoveredRank),
]);
}
}
this.setState({
isDragging: false,
});
this.hoveredRank = -1;
}
getHeightValue() {
const elem = this.dragElement.current;
if (elem) {
return elem.clientHeight;
}
return null;
}
startArrange(originY) {
this.setState({
isDragging: true,
});
this.originY = originY;
window.addEventListener('mousemove', this.onMouseMoveListener, false);
window.addEventListener('mouseup', this.onMouseUpListener, false);
const elem = this.dragElement.current;
CreateServiceFormFieldList.sharedEmitter.on(
CreateServiceFormFieldList.ARRANGE_HOVERED,
this.onArrangeHovered,
);
CreateServiceFormFieldList.sharedEmitter.emit(
CreateServiceFormFieldList.ARRANGE_START,
elem,
);
}
render() {
const {
field,
fields,
index,
} = this.props;
const {
isDragging,
isDragOn,
} = this.state;
return (
<div
style={[
styles.placement,
isDragging && styles.placementHolded,
isDragging && {
height: `${this.getHeightValue()}px`,
},
]}>
<div
ref={this.dragElement}
style={[styles.mainWrapper, isDragging && styles.wrapperDragging]}>
{createFormItem(field, fields.get(index).type)}
<div style={styles.subFeatureWrapper}>
<ItemTypeSelectBlock
formItem={field} />
<AdditionFeatureBlock
formItem={field}
removeFieldItem={() => fields.remove(index)}
copyFieldItem={value => fields.insert(index + 1, value)} />
</div>
<div style={styles.btnWrapper}>
<button
onMouseDown={({ clientY }) => this.startArrange(clientY)}
style={styles.arrangeBtn}
type="button" />
</div>
</div>
{isDragOn ? (
<div style={styles.isDragOnLine} />
) : null}
</div>
);
}
}
前傳
Publish/Subscribe Pattern
情境題
題目:做出複數個元件,並在使用者觸發其中一個時,其餘元件能接收到觸發元件資料
function PanelA({
panelState,
setPanelState,
}) {
useEffect(() => {
console.log('PanelA notice state changed to panel', panelState);
}, [panelState]);
return (
<button onClick={() => setPanelState('A')}>
panelA
</button>
);
}
function PanelB({
panelState,
setPanelState,
}) {
useEffect(() => {
console.log('PanelB notice state changed to panel', panelState);
}, [panelState]);
return (
<button onClick={() => setPanelState('B')}>
panelB
</button>
);
}
function PanelC({
panelState,
setPanelState,
}) {
useEffect(() => {
console.log('PanelC notice state changed to panel', panelState);
}, [panelState]);
return (
<button onClick={() => setPanelState('C')}>
panelC
</button>
);
}
function PanelInterface() {
const [panelState, setPanelState] = useState(null);
return (
<div style={styles.interface}>
<PanelA
panelState={panelState}
setPanelState={setPanelState} />
<PanelB
panelState={panelState}
setPanelState={setPanelState} />
<PanelC
panelState={panelState}
setPanelState={setPanelState} />
</div>
);
}
基本款
問題
- 層級不同造成非必較的props傳遞
- 依賴關係錯誤建立非必要的耦合性
Flux-redux
Turn into an interface
升級版
const sharedEmitter = new EventEmitter();
sharedEmitter.setMaxListeners(30);
const PANEL_STATE_CHANGED = 'E/PANEL_STATE_CHANGED';
function PanelA() {
useEffect(() => {
function stateChangedRecall(panelId) {
console.log('PanelA noticed state changed to', panelId);
}
sharedEmitter.on(PANEL_STATE_CHANGED, stateChangedRecall);
return () => {
sharedEmitter.removeListener(PANEL_STATE_CHANGED, stateChangedRecall);
};
});
return (
<button onClick={() => {
sharedEmitter.emit(PANEL_STATE_CHANGED, 'PanelA');
}}>
PanelA
</button>
);
}
// PanelB, PanelC, ... and so on
function PanelInterface() {
return (
<div style={styles.interface}>
<PanelA />
<PanelB />
<PanelC />
</div>
);
}
升級版
Parent
Instance
ElementA
ElementB
Call state change
Current State
Call state change
Current State
children layer
parent layer
Parent
Instance
ElementA
ElementB
EventEmitter
Trigger event
& send message
children layer
parent layer
Event Manager Interface
event / message filter
Call Subscriber
& get message
再來回頭看看Pub/Sub的定義介紹
針對特定的事件驅動建立抽象介面,藉此達成模組的解耦
中間人
穴大家
deck
By ian Lai
deck
- 348