web-dev-qa-db-ja.com

反応機能コンポーネントを使用し、デコレータを使用せずにmobxを使用する

私はMobXをreactの機能コンポーネントで動作させようとしています。デコレータを使わずにこれをやりたいです。 create-react-appを使用してアプリをセットアップし、依存関係としてMobXとMobX-reactを追加しました。ただし、機能コンポーネント内でオブザーバブルが機能しているようには見えません。

import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';

const Test = () => {
    extendObservable(this, {
        button: false
    });

    const handleB1 = () => {
        this.button = false;
    }

    const handleB2 = () => {
        this.button = true;
    }

    const getButton2 = () => {
        console.log('button2');
        return (
            <button type="button" onClick={handleB2}>Button 2</button>
        );
    };

    const getButton1 = () => {
        console.log('button1');
        return (
            <button type="button" onClick={handleB1}>Button 1</button>
        );
    };

    return (
        <div>
            {this.button ? getButton1() : getButton2()}
        </div>
    )
};

export default observer(Test);

ボタンをクリックすると、オブザーバブルが変更されたためにコンポーネントが再レンダリングされると思いますが、エラーが発生します。

×
Error: [mobx] Invariant failed: Side effects like changing state are not 
allowed at this point. Are you trying to modify state from, for example, the render 
function of a React component? Tried to modify: [email protected]

機能コンポーネントの一部として、または以前は次のように、オブザーバブルを宣言しようとしました。

const buttonState = () => {
    extendObservable(this, {
        button: false
    });
}

しかし、どちらの場合も、コンポーネントを再レンダリングすることができなかったか、オブザーバブルが実際に正しく設定されているかどうかがわかりませんでした。

このようなクラスとしてすべてを書くと、完璧に機能します

import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';

class Test extends React.Component {
    constructor(props) {
        super();
        extendObservable(this, {
            button: false
        });
    }

    handleB1 = () => {
        this.button = false;
    }

    handleB2 = () => {
        this.button = true;
    }

    getButton2 = () => {
        console.log('button2');
        return (
            <button type="button" onClick={this.handleB2}>Button 2</button>
        );
    };

    getButton1 = () => {
        console.log('button1');
        return (
            <button type="button" onClick={this.handleB1}>Button 1</button>
        );
    };

    render = () => {
        return  (
        <div>
            {this.button ? this.getButton1() : this.getButton2()}
        </div>
        )
    }
};

export default observer(Test);
5
marcus_deh

Reactでは、機能コンポーネントは永続的ではありません。それらは上から下に実行され、JSXを戻し、すすぎ、繰り返します。

let i = 0;

const FunctionalComp = (props) => {
  const foo = props.foo.toUpperCase();
  return <span>Rendered {i++} times. {foo}</span>;
}

この機能コンポーネントが実行するのは、値fooを同期的に作成してから、スパンを返すことだけです。このコンポーネントの親が再レンダリングすると、このコンポーネントはまったく同じように動作しますが、新しい値が含まれる可能性があります。

それは他に何もすることができません、そしてそれがそれが強力である理由です。それが機能コンポーネントと呼ぶことができる理由です:それは提供された値にのみ依存するため、アプリケーションの他の部分の方向を変える副作用を引き起こさないため、そして同じ引数が与えられると、この関数は残りの永遠に同じ結果を生成します。

予測可能=強力。

これで、クラスコンポーネントは永続的な状態を保持します。状態、メソッド、プロパティを構築して初期化し、JSXをレンダリングして返します。クラス(オブジェクト)はまだメモリに存在するため、そのクラスのすべての値とメソッドも存在します。

クラスコンポーネントのメソッドはそれほど予測できません。

class Foo {
  name = 'tommy';

  getUpperName() {
    return this.name.toUpperCase();
  }

  setName(name) {
    this.name = name;
  }
}

Foo.getUpperNameは、同じ引数で使用されるたびに同じ結果を生成するわけではありません(ヒント:引数を受け入れず、結果を決定するために周囲のコンテキストに依存します)。したがって、アプリケーションの他の部分Foo.nameを変更し、本質的にFoo.getUpperNameの結果を制御する可能性があります。

クラスはそれ自体の状態を更新して、それ自体とすべての子コンポーネントにJSXリターンを再計算させることができます。

単純な矢印関数では、戻った後、存在するのは、生成する戻り値と関数ステートメント(宣言)だけです。間には何もありません。

とはいえ、機能コンポーネントにはthisがバインドされていません。 (これは関数型プログラミングではノーノーです。)状態はありません。

したがって、機能コンポーネント内のthisで何もできず、再レンダリングするたびにそれらの各値が再インスタンス化されるため、監視可能な値を保持できません。

上記の例では、thisTestを参照していても、次のようになります。

  1. テストでは、観測可能な値buttonfalseとして作成します。
  2. テストではボタンがレンダリングされ、クリックします。
  3. テストでは、観測可能な値buttonfalseとして作成します。
  4. テストではボタンがレンダリングされ、クリックします。
  5. テストでは、観測可能な値buttonfalseとして作成します。
  6. テストではボタンがレンダリングされ、クリックします。

などなど。

MobXでは、オブザーバブルは永続的なデータ構造上に存在し、UIマークアップを返すレンダリング関数に渡される必要があります。

const store = observable({
  name: 'tommy'
});

const changeName = () => store.name = store.name.split('').reverse().join('');

const Foo = observer((props) => {
  return (
    <button onClick={changeName}>{store.name}'s button</button>
  )
});

FoochangeNameも純粋ではないため、これは優れたパターンではありません。少なくとも、このコードは機能します。

あなたはそのようなことをする必要があります:

const store = () => {
  const self = {};

  self.actions = {
    setName: action((name) =>  self.name = name);
  }

  return extendObservable(self, { name: 'tommy' });
}

const App = (props) => {
    return <span><Foo store={store} /></span>
}

const Foo = observer((props) => {
  return (
    <button onClick={props.store.actions.setName}>
      {store.name}'s button
    </button>
  )
})

繰り返しになりますが、これは理想的な実装ではありませんが、機能します。私は仕事をしていて、彼らが私に支払ったものに戻らなければなりません。 ;)

6