React中的事件驱动方法



我想"引发事件";在一个组件中;订阅";并在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>

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

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>

假设它是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
})
}}
/>

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

  • 没有再次从服务器调用API(因为EditScreen.updateData已经从服务器获取了更新的数据),并且
  • 不将updateData函数作为道具传递(因为在大多数实际情况下,组件结构过于复杂,无法将所有函数作为道具进行传递)

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

// 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");
}
}, []);

是否可以使用React挂钩?

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

不能有事件发射器的替代方案,因为React挂钩和使用上下文依赖于dom树深度,范围有限。

将EventEmitter与React(或React Native)一起使用是否被认为是一种良好的做法?

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

我正在React中寻求事件驱动的方法。我现在对我的解决方案很满意,但我能用React钩子实现同样的效果吗?

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

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

在react native中,您可以使用react nativeeventlisteners包

yarn add react-native-event-listeners

发送组件

import { EventRegister } from 'react-native-event-listeners'
const Sender = (props) => (
<TouchableHighlight
onPress={() => {
EventRegister.emit('myCustomEvent', 'it works!!!')
})
><Text>Send Event</Text></TouchableHighlight>
)

接收器组件

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>
}
}

不确定EventEmitter被否决的原因,但我的看法是:

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

如果你的UI需要了解你的应用程序的一般状态并对其做出反应,请使用reducers,更新你的商店,然后使用Context/Redux/Flux/whatever-如果你只是需要对特定事件做出反应,则使用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;

/某些组件.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')
}
})
}

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

我们也遇到了类似的问题,并从useSWR中获得了灵感。

以下是我们实现的简化版本:

const events = [];
const callbacks = {};
function useForceUpdate() {
const [, setState] = useState(null);
return useCallback(() => setState({}), []);
}
function useEvents() {
const forceUpdate = useForceUpdate();
const runCallbacks = (callbackList, data) => {
if (callbackList) {
callbackList.forEach(cb => cb(data));
forceUpdate();
}

}
const dispatch = (event, data) => {
events.push({ event, data, created: Date.now() });
runCallbacks(callbacks[event], data);
}
const on = (event, cb) => {
if (callbacks[event]) {
callbacks[event].push(cb);
} else {
callbacks[event] = [cb];
}
// Return a cleanup function to unbind event
return () => callbacks[event] = callbacks[event].filter(i => i !== cb);
}
return { dispatch, on, events };
}

在我们做的一个组件中:

const { dispatch, on, events } = useEvents();
useEffect(() => on('MyEvent', (data) => { ...do something...}));

这很好地工作有几个原因:

  1. 与窗口Event系统不同,事件数据可以是任何类型的对象。这节省了stringify有效载荷的开销。这也意味着不可能与任何内置的浏览器事件发生冲突
  2. 全局缓存(借用SWR的思想)意味着我们可以在任何需要的地方只使用useEvents,而不必传递事件列表&在组件树下分派/订阅函数,或者乱用react上下文
  3. 将事件保存到本地存储或回放/倒带是很简单的

我们遇到的一个难题是,每次调度事件时都要使用forceUpdate,这意味着接收事件列表的每个组件都会重新呈现,即使它们没有订阅该特定事件。这是一个复杂观点中的问题。我们正在积极寻找解决方案。。。

您可以使用useContext在App.js中创建use上下文,然后在您的子组件中,您可以使用其中的值,并在上下文更新后立即更新上下文,它将更新其他子组件中使用的值,无需传递道具。

您可以通过任何React的全局状态管理来实现这一点。

在您的商店中,为您的活动订阅设置一个useEffect,为每个活动设置一个reducer。

如果您有两个数据源,订阅和查询,则使用查询初始化状态值,然后侦听订阅。

像这样的

const reducer = (state, action) => {
switch(action.type) {
case 'SUBSCRIBE':
return action.payload
default:
return state
}
}

假设您使用的是https://github.com/dai-shi/use-reducer-async

const asyncActions = {
QUERY: ({ dispatch }) => async(action) => {
const data = await fetch(...)
dispatch({ type: 'query', payload: data })
}
}

您也可以在Redux 中使用中间件

const [state, dispatch] = useReducer(reducer, initialValues, asyncActions)
useEffect(() => {
dispatch({ type: 'QUERY' })
Event.on((data) => {
dispatch({ type: 'SUBSCRIBE', payload: data })
})
return () => Event.off()
}, [])
return <Provider value={state}>{children}</Provider>

相关内容

  • 没有找到相关文章

最新更新