如何将操作传达给 React 组件
Posted
技术标签:
【中文标题】如何将操作传达给 React 组件【英文标题】:How to communicate an action to a React component 【发布时间】:2015-11-27 05:00:43 【问题描述】:虽然我的场景非常具体,但我认为它说明了 Flux 中的一个更大的问题。组件应该是来自商店的数据的简单呈现,但是如果您的组件呈现有状态的第三方组件怎么办?如何在遵守 Flux 规则的同时与这个第三方组件交互?
所以,我有一个包含视频播放器的 React 应用程序(使用 clappr)。一个很好的例子是寻求。当我单击进度条上的某个位置时,我想寻找视频播放器。这是我现在拥有的(使用RefluxJS)。我试图将我的代码精简到最相关的部分。
var PlayerActions = Reflux.createActions([
'seek'
]);
var PlayerStore = Reflux.createStore(
listenables: [
PlayerActions
],
onSeek(seekTo)
this.data.seekTo = seekTo;
this.trigger(this.data);
);
var Player = React.createClass(
mixins: [Reflux.listenTo(PlayerStore, 'onStoreChange')],
onStoreChange(data)
if (data.seekTo !== this.state.seekTo)
window.player.seek(data.seekTo);
// apply state
this.setState(data);
componentDidMount()
// build a player
window.player = new Clappr.Player(
source: this.props.sourcePath,
chromeless: true,
useDvrControls: true,
parentId: '#player',
width: this.props.width
);
,
componentWillUnmount()
window.player.destroy();
window.player = null;
,
shouldComponentUpdate()
// if React realized we were manipulating DOM, it'd certainly freak out
return false;
,
render()
return <div id='player'/>;
);
我对这段代码的错误是当您尝试两次寻找同一个地方时。想象一下视频播放器连续播放。点击进度条进行搜索。不要移动鼠标,等待几秒钟。在与之前相同的位置再次单击进度条。 data.seekTo
的值没有改变,所以window.player.seek
没有被第二次调用。
我已经考虑了解决这个问题的几种可能性,但我不确定哪个更正确。输入请求...
1:seekTo
用完后重置
简单地重置seekTo
似乎是最简单的解决方案,尽管它肯定不会更优雅。最终,这感觉更像是一种创可贴。
这就像...一样简单
window.player.on('player_seek', PlayerActions.resetSeek);
2:创建一个更像传递的独立商店
基本上,我会听SeekStore
,但实际上,这将充当传递,使其更像是商店的动作。这个解决方案感觉像是对 Flux 的一种破解,但我认为它会起作用。
var PlayerActions = Reflux.createActions([
'seek'
]);
var SeekStore = Reflux.createStore(
listenables: [
PlayerActions
],
onSeek(seekTo)
this.trigger(seekTo);
);
var Player = React.createClass(
mixins: [Reflux.listenTo(SeekStore, 'onStoreChange')],
onStoreChange(seekTo)
window.player.seek(seekTo);
);
3:在我的操作中与window.player
互动
当我想到它时,感觉是正确的,因为调用window.player.seek
实际上是一个动作。唯一奇怪的一点是,我在动作中与window
进行交互感觉不对。不过,也许这只是一个不合理的想法。
var PlayerActions = Reflux.createActions(
seek: asyncResult: true
);
PlayerActions.seek.listen(seekTo =>
if (window.player)
try
window.player.seek(seekTo);
PlayerActions.seek.completed(err);
catch (err)
PlayerActions.seek.failed(err);
else
PlayerActions.seek.failed(new Error('player not initialized'));
);
顺便说一句,房间里还有一头我没有触及的大象。在我的所有示例中,播放器都存储为window.player
。 Clappr 在旧版本中自动执行此操作,但尽管它已被修复以与 Browserify 一起使用,但我们继续将其存储在 window
(技术债务)中。显然,我的第三个解决方案是利用这一事实,这在技术上是一件坏事。无论如何,在任何人指出之前,理解和注意。
4:通过dispatchEvent
搜索
我也知道调度自定义事件可以完成工作,但考虑到我有 Flux,这感觉有点不对劲。这感觉就像我要跳出 Flux 架构来完成工作。我应该能够做到并留在 Flux 操场内。
var PlayerActions = Reflux.createActions(
seek: asyncResult: true
);
PlayerActions.seek.listen(seekTo =>
try
let event = new window.CustomEvent('seekEvent', detail: seekTo);
window.dispatchEvent(event);
PlayerActions.seek.completed(err);
catch (err)
PlayerActions.seek.failed(err);
);
var Player = React.createClass(
componentDidMount()
window.addEventListener('seekEvent', this.onSeek);
,
componentWillUnmount()
window.removeEventListener('seekEvent', this.onSeek);
,
onSeek(e)
window.player.seek(e.detail);
);
【问题讨论】:
除非最后跳转到的位置应该是您应用程序状态的一部分,否则它不需要在商店中。此外,播放器看起来像是在没有 React 的情况下将自己注入到 DOM 中。这可能是个问题,因为 React 想要自己控制 dom。 @jnes,我们也使用shouldComponentUpdate() return false
,所以 React 不会在意玩家在这个区域操作 DOM。另外,关于seekTo
的好处不需要在商店里。你是对的。你会选择哪个选项?
杰夫 - 正如 jnes 指出的那样, seekto 真的不是状态的一部分,但 CurrentPosition 似乎是状态。您正在尝试“渲染”当前位置。您可以在播放器播放时更新当前位置,当您调用“seek”时,它会将 CurrentPosition 重置为所需位置。这允许将来进行其他整洁的事情,例如存储最后播放的位置,而对 UI 几乎没有更改。仅当 seekTo != CurrentPosition 时才需要更新搜索操作。我对 ReFlux 不熟悉,所以不知道如何在 ReFlux 中实现。\
@DanKaufman,感谢您的有趣建议。我可以看到它如何解决我的问题,因为我将查看seekTo
和currentTime
的组合,而不是seekTo
的下一个和上一个值。我遇到的一个问题是,我必须在currentTime
上收听更新——这是超级嘈杂的——并将每个周期与seekTo
进行比较。不过,这不是一个坏主意。
@JeffFairley - 是的,更新会很吵,但您也可以根据需要使更新变得尽可能精细(1 秒,5 秒)。此外,如果它只是更新状态而没有重新渲染任何内容,那么这是一个成本非常低的操作。
【参考方案1】:
5:保持播放位置在状态(由Dan Kaufman注明)
可以这样做:
handlePlay ()
this._interval = setInterval(() => this.setState(curPos: this.state.curPos + 1), 1000)
this.setState(playing: true) // might not be needed
handlePauserOrStop ()
clearInterval(this._interval)
this.setState(playing: false)
componentWillUnmount ()
clearInteral(this._interval)
onStoreChange (data)
const diff = Math.abs(data.seekTo - this.state.curPos)
if (diff > 2) // adjust 2 to your unit of time
window.player.seek(data.seekTo);
【讨论】:
以上是关于如何将操作传达给 React 组件的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 React 中的 CSS 模块将多个类动态传递给子组件
如何在 React 中将不同的道具从父组件传递给多个子组件?