如何将操作传达给 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,感谢您的有趣建议。我可以看到它如何解决我的问题,因为我将查看seekTocurrentTime 的组合,而不是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 中的组件

React 如何将状态传递给另一个组件

如何在 React 中将不同的道具从父组件传递给多个子组件?

如何将道具从调用组件传递给 HOC - React + Typescript

React Navigation 如何将 props 传递给 TabNavigator 无状态功能组件