5

私のシナリオは非常に具体的ですが、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'/>;
    }
});

このコードのバグは、同じ場所を 2 回シークしようとした場合です。動画プレーヤーが継続的に再生されていると想像してください。プログレスバーをクリックしてシークします。マウスを動かさず、数秒待ちます。プログレスバーの前と同じ場所をもう一度クリックします。の値はdata.seekTo変更されていないためwindow.player.seek、2 回目は呼び出されません。

これを解決するためのいくつかの可能性を検討しましたが、どれがより正しいかはわかりません。入力が要求されました...


seekTo1:使用後リセット

単純にリセットする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);
    }
});


window.player3:自分の行動の中で対話する

考えてみると、呼び出しは実際にはアクションなので、これは正しいと思います。唯一の奇妙な点は、アクション内での操作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(技術的負債) に保存し続けています。明らかに、私の 3 番目の解決策はその事実を利用することですが、これは技術的に悪いことです。とにかく、誰かがそれを指摘する前に、理解し、注意してください。


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);
    }
});
4

1 に答える 1

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);
  }
}
于 2016-01-11T20:02:59.810 に答える