React 中的事件驱动方法?

gli*_*a93 10 event-driven reactjs react-native react-hooks

我想在一个组件中“触发一个事件”,让其他组件“订阅”该事件并在 React 中做一些工作。

例如,这是一个典型的 React 项目。

我有一个模型,从服务器获取数据,并且使用该数据呈现多个组件。

interface Model {
   id: number;
   value: number;
}

const [data, setData] = useState<Model[]>([]);
useEffect(() => {
   fetchDataFromServer().then((resp) => setData(resp.data));
}, []);

<Root>
   <TopTab>
     <Text>Model with large value count:  {data.filter(m => m.value > 5).length}</Text>
   </TobTab>
   <Content>
      <View>
         {data.map(itemData: model, index: number) => (
            <Item key={itemData.id} itemData={itemData} />
         )}
      </View>
   </Content>
   <BottomTab data={data} />
</Root>
Run Code Online (Sandbox Code Playgroud)

在一个子组件中,可以编辑和保存模型。

const [editItem, setEditItem] = useState<Model|null>(null);
<Root>
   <TopTab>
     <Text>Model with large value count:  {data.filter(m => m.value > 5).length}</Text>
   </TobTab>
   <ListScreen>
      {data.map(itemData: model, index: number) => (
          <Item 
             key={itemData.id} 
             itemData={itemData} 
             onClick={() => setEditItem(itemData)}
          />
      )}
   </ListScreen>
   {!!editItem && (
       <EditScreen itemData={editItem} />
   )}
   <BottomTab data={data} />
</Root>
Run Code Online (Sandbox Code Playgroud)

让我们假设它是 EditScreen:

const [model, setModel] = useState(props.itemData);

<Input 
   value={model.value}
   onChange={(value) => setModel({...model, Number(value)})}
/>
<Button 
   onClick={() => {
       callSaveApi(model).then((resp) => {
           setModel(resp.data);
           // let other components know that this model is updated
       })
   }}
/>
Run Code Online (Sandbox Code Playgroud)

应用程序必须让TopTabBottomTabListScreen组件更新数据

  • 无需再次从服务器调用 API(因为 EditScreen.updateData 已经从服务器获取更新的数据)和
  • 不将updateData函数作为 props传递(因为在大多数实际情况下,组件结构太复杂,无法将所有函数作为 props 传递)

为了有效地解决上述问题,我想用一个参数(更改模型)触发一个事件(例如“模型更新”),并让其他组件订阅该事件并更改它们的数据,例如:

// in EditScreen
updateData().then(resp => {
   const newModel = resp.data;
   setModel(newModel);
   Event.emit("model-updated", newModel);
});

// in any other components
useEffect(() => {
   // subscribe model change event
   Event.on("model-updated", (newModel) => {
      doSomething(newModel);
   });
   // unsubscribe events on destroy
   return () => {
     Event.off("model-updated");
   }
}, []);

// in another component
useEffect(() => {
   // subscribe model change event
   Event.on("model-updated", (newModel) => {
      doSomethingDifferent(newModel);
   });
   // unsubscribe events on destroy
   return () => {
     Event.off("model-updated");
   }
}, []);

Run Code Online (Sandbox Code Playgroud)

是否可以使用 React 钩子?

如何在 React 钩子中实现事件驱动的方法?

Muh*_*man 12

不可能有事件发射器的替代品,因为 React hooks 和使用上下文依赖于 dom 树深度并且范围有限。

将 EventEmitter 与 React(或 React Native)一起使用是否被认为是一个好的实践?

A:是的,当 dom 树深处有组件时,这是一个很好的方法

我正在 React 中寻找事件驱动的方法。我现在对我的解决方案很满意,但是我可以使用 React hooks 实现同样的效果吗?

答:如果您指的是组件状态,那么钩子将无法帮助您在组件之间共享它。组件状态是组件的本地状态。如果你的状态存在于上下文中,那么 useContext 钩子会很有帮助。对于 useContext,我们必须使用 和 实现完整的上下文 API,MyContext.Provider并且MyContext.Consumer必须包装在高阶 ( HOC ) 组件 Ref中

所以事件发射器是最好的。

在React Native中,您可以使用react-native-event-listeners

yarn add react-native-event-listeners
Run Code Online (Sandbox Code Playgroud)

发送器组件

import { EventRegister } from 'react-native-event-listeners'

const Sender = (props) => (
    <TouchableHighlight
        onPress={() => {
            EventRegister.emit('myCustomEvent', 'it works!!!')
        })
    ><Text>Send Event</Text></TouchableHighlight>
)
Run Code Online (Sandbox Code Playgroud)

接收器组件

class Receiver extends PureComponent {
    constructor(props) {
        super(props)
        
        this.state = {
            data: 'no data',
        }
    }
    
    componentWillMount() {
        this.listener = EventRegister.addEventListener('myCustomEvent', (data) => {
            this.setState({
                data,
            })
        })
    }
    
    componentWillUnmount() {
        EventRegister.removeEventListener(this.listener)
    }
    
    render() {
        return <Text>{this.state.data}</Text>
    }
}
Run Code Online (Sandbox Code Playgroud)


Joe*_*don 9

不知道为什么 EventEmitter 被否决了,但这是我的看法:

当谈到状态管理时,我相信使用基于 Flux 的方法通常是最佳选择(Context/Redux 和朋友都很棒)。也就是说,我真的不明白为什么基于事件的方法会带来任何问题 - JS基于事件的,而 React 毕竟只是一个库,甚至不是一个框架,而且我不明白为什么我们会被迫这样做遵守其指导方针。

如果您的 UI 需要了解应用程序的一般状态并对其做出反应,请使用减速器,更新您的存储,然后使用 Context/Redux/Flux/无论什么 - 如果您只需要对特定事件做出反应,请使用 EventEmitter。

使用 EventEmitter 将允许您在 React 和其他库之间进行通信,例如画布(如果您没有使用 React Three Fiber,我敢说您可以尝试在没有事件的情况下与 ThreeJS/WebGL 进行交流),而无需使用所有样板。在很多情况下,使用 Context 是一场噩梦,我们不应该感到受到 React API 的限制。

如果它适合您,并且具有可扩展性,那就去做吧。

编辑:这是一个使用 eventemitter3 的示例:

./发射器.ts

import EventEmitter from 'eventemitter3';

const eventEmitter = new EventEmitter();

const Emitter = {
  on: (event, fn) => eventEmitter.on(event, fn),
  once: (event, fn) => eventEmitter.once(event, fn),
  off: (event, fn) => eventEmitter.off(event, fn),
  emit: (event, payload) => eventEmitter.emit(event, payload)
}

Object.freeze(Emitter);

export default Emitter;
Run Code Online (Sandbox Code Playgroud)

./some-component.ts

import Emitter from '.emitter';

export const SomeComponent = () => {
  useEffect(() => {
    // you can also use `.once()` to only trigger it ... once
    Emitter.on('SOME_EVENT', () => do what you want here)
    return () => {
      Emitter.off('SOME_EVENT')
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

从那里,您可以在任何您想要的地方触发事件,订阅它们,并对其采取行动,传递一些数据,做任何您真正想做的事情。

  • 全局事件总线模式感觉就像作弊——当然。但为什么作弊的感觉这么好呢?这是因为流动模式就像宗教正统观念一样,而你是一个离经叛道的人;你很美丽,就像你本来的样子。但说实话,在 Figma 这样的应用程序中,您最终需要与不共享状态的组件进行通信。如果感觉不错,就去做吧。(嘘..感觉很好)https://www.pluralsight.com/guides/how-to-communicate- Between-independent-components-in-reactjs (3认同)
  • 简单明了。比 Redux 和 Context 容易得多。我不知道为什么不在所有地方都使用这种方法。只是想知道为什么创建一个新对象并冻结它。导出 eventEmitter 也可以正常工作。 (2认同)