Reactアプリケーションでサービスを利用する


177

私は、サービス/ファクトリーにロジックを抽出し、コントローラーでそれらを使用できる、角度のある世界から来ています。

Reactアプリケーションで同じことをどのように達成できるかを理解しようとしています。

ユーザーのパスワード入力を検証するコンポーネントがあるとします(それは強みです)。それはロジックがかなり複雑であるため、それ自体をコンポーネントに記述したくありません。

このロジックはどこに記述すればよいですか?フラックスを使用している場合、店で?または、より良いオプションはありますか?


あなたはパッケージを使用して、彼らがどのようにそれをやっているかを見ることができます-npmjs.com/package/react-password-strength-meter
James111

11
パスワードの強度はほんの一例です。より一般的なベストプラクティスを探しています
Dennis Nerush

あなたはそれをサーバー側でしなければならないかもしれませんか?
James111

2
いいえ。コンポーネントに直接含めることはできないクライアント側のロジックのみ。パスワード強度チェッカーは一例に過ぎません
Dennis Nerush

4
そのような関数がたくさんある場合は、それらをヘルパーファイルに保存して、使用するためにコンポーネントファイルに要求するだけです。それがそのコンポーネントにのみ関連する単一の関数である場合、複雑度に関係なくおそらくそこに存在するはずです。
Jesse Kernaghan

回答:


61

最初の答えは、現在のコンテナー対プレゼンターのパラダイムを反映していません。

パスワードの検証など、何かをする必要がある場合は、それを実行する関数がおそらくあります。その関数を小道具として再利用可能なビューに渡します。

コンテナ

したがって、正しい方法は、その機能をプロパティとして持つValidatorContainerを記述し、フォームをラップして、適切な小道具を子に渡すことです。ビューに関しては、バリデーターコンテナーがビューをラップし、ビューがコンテナーロジックを消費します。

検証はすべてコンテナのプロパティで行うことができますが、サードパーティのバリデータまたは任意の単純な検証サービスを使用している場合は、サービスをコンテナコンポーネントのプロパティとして使用し、コンテナのメソッドで使用できます。私はこれを安静なコンポーネントのために行いました、そしてそれは非常にうまく機能します。

プロバイダー

もう少し設定が必要な場合は、プロバイダー/コンシューマーモデルを使用できます。プロバイダーは、上位のアプリケーションオブジェクト(マウントしたオブジェクト)の近くまたはその下のどこかにラップし、それ自体の一部、または最上位のレイヤーで構成されたプロパティをコンテキストAPIに提供する高レベルのコンポーネントです。次に、コンテナ要素を設定してコンテキストを消費します。

親/子のコンテキスト関係は互いに近接している必要はありません。子が何らかの方法で降りてくる必要があるだけです。ReduxストアとReact Routerはこのように機能します。私はそれを使用して、残りのコンテナーにルートレストフルコンテキストを提供しました(自分で提供しない場合)。

(注:コンテキストAPIはドキュメントで実験的とマークされていますが、何を使用しているかを考えると、これはもうそうではないと思います)。

//An example of a Provider component, takes a preconfigured restful.js
//object and makes it available anywhere in the application
export default class RestfulProvider extends React.Component {
	constructor(props){
		super(props);

		if(!("restful" in props)){
			throw Error("Restful service must be provided");
		}
	}

	getChildContext(){
		return {
			api: this.props.restful
		};
	}

	render() {
		return this.props.children;
	}
}

RestfulProvider.childContextTypes = {
	api: React.PropTypes.object
};

ミドルウェア

私が試していないが使用されていると思われるもう1つの方法は、ミドルウェアをReduxと組み合わせて使用​​することです。サービスオブジェクトは、アプリケーションの外部で定義するか、少なくともreduxストアより高く定義します。ストアの作成時に、サービスをミドルウェアに注入すると、ミドルウェアがサービスに影響を与えるアクションを処理します。

このようにして、restful.jsオブジェクトをミドルウェアに注入し、コンテナーメソッドを独立したアクションに置き換えることができます。フォームビューレイヤーにアクションを提供するコンテナーコンポーネントはまだ必要ですが、connect()とmapDispatchToPropsで対応できます。

新しいv4 react-router-reduxはこのメソッドを使用して、たとえば履歴の状態に影響を与えます。

//Example middleware from react-router-redux
//History is our service here and actions change it.

import { CALL_HISTORY_METHOD } from './actions'

/**
 * This middleware captures CALL_HISTORY_METHOD actions to redirect to the
 * provided history object. This will prevent these actions from reaching your
 * reducer or any middleware that comes after this one.
 */
export default function routerMiddleware(history) {
  return () => next => action => {
    if (action.type !== CALL_HISTORY_METHOD) {
      return next(action)
    }

    const { payload: { method, args } } = action
    history[method](...args)
  }
}


素晴らしい答えの仲間、あなたは私が頭のないことをするのを止めました8)KUDOS !!
csomakk

コンテナの使用例は何ですか?
センセイ

私はそれを推奨していませんが、サービスロケーターパス(Angularに似たもの)を下る場合は、サービスを解決する(以前に登録した)ある種の「インジェクター/コンテナー」プロバイダーを追加できます。
eddiewould 2018年

Reactフックが助けになります。フックを使用すると、クラスを作成せずに再利用可能なロジックを作成できます。reactjs.org/docs/...
ラジャ・マリク

102

Angularサービスが、コンテキストに依存しない一連のメソッドを提供する単なるオブジェクトであることを理解すると、問題は非常に単純になります。より複雑に見えるのは、Angular DIメカニズムだけです。DIはインスタンスの作成と保守を担当しますが、実際には必要ないため便利です。

axiosという名前の人気のあるAJAXライブラリ(おそらく聞いたことがあるでしょう)を考えてみます。

import axios from "axios";
axios.post(...);

サービスとして動作しませんか?これは、特定のロジックを担当する一連のメソッドを提供し、メインコードから独立しています。

あなたの事例は、入力を検証するための分離されたメソッドのセットを作成することでした(例:パスワードの強度のチェック)。これらのメソッドをコンポーネントの内部に置くことを提案する人もいますが、これは明らかに私にとってアンチパターンです。検証にXHRバックエンド呼び出しの作成と処理、または複雑な計算の実行が含まれる場合はどうなりますか?このロジックをマウスクリックハンドラーやその他のUI固有のものと組み合わせますか?ナンセンス。コンテナー/ HOCアプローチと同じです。値に数字があるかどうかをチェックするメソッドを追加するためだけにコンポーネントをラップしていますか?いい加減にして。

「ValidationService.js」という名前の新しいファイルを作成し、次のように整理します。

const ValidationService = {
    firstValidationMethod: function(value) {
        //inspect the value
    },

    secondValidationMethod: function(value) {
        //inspect the value
    }
};

export default ValidationService;

次に、コンポーネントで:

import ValidationService from "./services/ValidationService.js";

...

//inside the component
yourInputChangeHandler(event) {

    if(!ValidationService.firstValidationMethod(event.target.value) {
        //show a validation warning
        return false;
    }
    //proceed
}

どこからでもご利用いただけます。検証ルールが変更された場合は、ValidationService.jsファイルのみに集中する必要があります。

他のサービスに依存する、より複雑なサービスが必要になる場合があります。この場合、サービスファイルは静的オブジェクトの代わりにクラスコンストラクターを返す可能性があるため、コンポーネントでオブジェクトのインスタンスを自分で作成できます。また、アプリケーション全体で使用されているサービスオブジェクトのインスタンスが常に1つだけであることを確認するために、単純なシングルトンの実装を検討することもできます。


3
これは私もそうする方法です。この回答は、最も摩擦の少ない方法であると感じているため、この回答の投票数が非常に少ないことにかなり驚いています。サービスが他のサービスに依存している場合は、モジュールを介して他のサービスをインポートします。さらに、モジュールは本質的にシングルトンであるため、「単純なシングルトンとして実装する」ための追加の作業は実際には必要ありません。その動作は無料で得られます:)
Mickey Puri

6
+1-関数を提供するサービスのみを使用している場合の適切な回答。ただし、Angularのサービスは一度定義されたクラスであるため、単に機能を提供するだけではなく、より多くの機能を提供します。たとえば、オブジェクトをサービスクラスパラメータとしてキャッシュできます。
Nino Filiu

6
これは本当の答えではなく、上記以上の複雑な応答でなければなりません
user1807334

1
これは「反応的」でないことを除いて、良い答えです。DOMは、サービス内の変数の変更時に更新されません。
デファクト

9
依存関係注入についてはどうですか?なんらかの方法で注入しない限り、コンポーネントでサービスをモックすることは不可能です。おそらく、各サービスをフィールドとして持つトップレベルの「コンテナー」グローバルオブジェクトがあると、これを回避できます。次に、テストで、モックしたいサービスのモックでコンテナーフィールドをオーバーライドできます。
menehune23

34

いくつかのフォーマットロジックを複数のコンポーネント間で共有する必要があり、Angular開発者も当然ながらサービスに傾倒していました。

別のファイルに入れてロジックを共有しました

function format(input) {
    //convert input to output
    return output;
}

module.exports = {
    format: format
};

そしてそれをモジュールとしてインポートしました

import formatter from '../services/formatter.service';

//then in component

    render() {

        return formatter.format(this.props.data);
    }

8
これはさえに言及した文書反応する良いアイデアです:reactjs.org/docs/composition-vs-inheritance.html あなたはコンポーネント間の非UI機能を再利用したい場合は、我々は別のJavaScriptモジュールにそれを抽出することをお勧めします。コンポーネントはそれをインポートし、拡張せずにその関数、オブジェクト、またはクラスを使用できます。
user3426603 2017年

これが実際に意味のある唯一の答えです。
Artem Novikov

33

Reactの目的は、論理的に結合されるべきものをより適切に結合することであることに留意してください。複雑な「パスワードの検証」方法を設計している場合、どこで組み合わせる必要がありますか?

ユーザーが新しいパスワードを入力する必要があるたびに、それを使用する必要があります。これは、登録画面、「パスワードを忘れた」画面、管理者の「別のユーザーのパスワードをリセットする」画面などに表示されます。

しかし、これらのいずれの場合でも、常に何らかのテキスト入力フィールドに関連付けられます。これが結合される場所です。

入力フィールドと関連する検証ロジックのみで構成される非常に小さなReactコンポーネントを作成します。パスワード入力を必要とする可能性のあるすべてのフォーム内にそのコンポーネントを入力します。

これは基本的には、ロジックのサービス/ファクトリーを持つのと同じ結果ですが、入力に直接結合しています。したがって、この関数は永続的に結び付けられているため、検証入力を探す場所を関数に指示する必要はありません。


11
ロジックとUIを組み合わせることが悪い習慣です。ロジックを変更するために、コンポーネントに触れる必要があります
Dennis Nerush

14
Reactは根本的にあなたがしているその仮定に挑戦します。従来のMVCアーキテクチャとはまったく対照的です。 このビデオは、その理由を説明するのにかなり役立ちます(関連セクションは約2分で始まります)。
Jake Roby

8
同じ検証ロジックをテキスト領域要素にも適用する必要がある場合はどうなりますか?ロジックは引き続き共有ファイルに抽出する必要があります。私は箱から出して反応ライブラリからの同等物があるとは思いません。Angularサービスはインジェクタブルであり、Angularフレームワークは、Angularが管理する依存関係のインスタンスを可能にする依存関係注入設計パターンの上に構築されています。サービスが挿入されると、通常、提供されたスコープにシングルトンが存在します。Reactで同じサービスを使用するには、サードパーティのDIライブラリをアプリケーションに導入する必要があります。
Downhillski

15
@gravityplanx Reactを使って楽しんでいます。これは角度パターンではなく、ソフトウェア設計パターンです。他の良いところから好きなものを借りている間、心を開いておくのが好きです。
ダウンヒル

1
@MickeyPuri ES6モジュールは、依存性注入とは異なります。
スポック

12

また、Angular.jsエリアから来ましたが、React.jsのサービスとファクトリーはより単純です。

私のようなプレーンな関数やクラス、コールバックスタイル、イベントMobxを使用できます:)

// Here we have Service class > dont forget that in JS class is Function
class HttpService {
  constructor() {
    this.data = "Hello data from HttpService";
    this.getData = this.getData.bind(this);
  }

  getData() {
    return this.data;
  }
}


// Making Instance of class > it's object now
const http = new HttpService();


// Here is React Class extended By React
class ReactApp extends React.Component {
  state = {
    data: ""
  };

  componentDidMount() {
    const data = http.getData();

    this.setState({
      data: data
    });
  }

  render() {
    return <div>{this.state.data}</div>;
  }
}

ReactDOM.render(<ReactApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  
  <div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

</body>
</html>

ここに簡単な例があります:


React.jsは、UIコンポーネントをレンダリングおよび整理するためのUIライブラリです。追加の機能を追加するのに役立つサービスに関しては、関数、関数オブジェクト、またはクラスのコレクションを作成する必要があります。クラスは非常に便利だと思いましたが、Reac.jsのスコープ外にあるアドバンテージ機能を追加するためのヘルパーを作成するためにも使用できる機能スタイルで遊んでいることを知っています。
ジュラジ

これを実装しました。クラスにしてエクスポートする方法は非常にエレガントです。
GavinBelson

10

同じ状況:複数のAngularプロジェクトを実行してReactに移行した後、DIを介してサービスを提供する単純な方法がないと、欠けているように見えます(サービスの詳細はさておき)。

コンテキストとES7デコレーターを使用して、近づくことができます。

https://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/

これらの人はそれをさらに一歩/別の方向に進んだようです:

http://blog.wolksoftware.com/dependency-injection-in-react-powered-inversifyjs

まだ穀物に反対するように感じています。主要なReactプロジェクトに着手してから6か月後にこの回答を再検討します。

編集:6か月後、Reactの経験がさらに増えました。ロジックの性質を考慮してください。

  1. UIに(のみ)関連付けられていますか?それをコンポーネント(受け入れられた回答)に移動します。
  2. 状態管理に(のみ)関連付けられていますか?サンクに移動します。
  3. 両方に縛られていますか?別のファイルに移動し、セレクターとサンクでコンポーネントを使用します。

一部は再利用のためにHOCに到達しますが、私にとっては上記のほとんどすべてのユースケースをカバーしています。また、アヒルを使用して状態管理をスケーリングし、懸念を個別に保ち、UI中心の状態を維持することも検討してください。


Imho ES6モジュールシステムを使用してDIを介してサービスを提供する簡単な方法あると思います
ミッキープリ

1
@ MickeyPuri、ES6モジュールDIには、Angular DIの階層的な性質は含まれません。親(DOM内)は、子コンポーネントに提供されるサービスをインスタンス化およびオーバーライドします。Imho ES6モジュールDIは、DOMコンポーネント階層に基づくのではなく、NinjectやStructuremapなどのバックエンドDIシステムにより近い位置にあります。しかし、私はそれについてあなたの考えを聞きたいのですが。
カローラ

6

私もAngular出身で、Reactを試しています。現在、推奨される(?)方法の1つは高次コンポーネントを使用しているようです。

高次コンポーネント(HOC)は、コンポーネントロジックを再利用するためのReactの高度な手法です。HOC自体はReact APIの一部ではありません。これらは、Reactの構成的な性質から生まれたパターンです。

あなたが持っていると言うinputtextareaと同様に同じ検証ロジックを適用します:

const Input = (props) => (
  <input type="text"
    style={props.style}
    onChange={props.onChange} />
)
const TextArea = (props) => (
  <textarea rows="3"
    style={props.style}
    onChange={props.onChange} >
  </textarea>
)

次に、ラップされたコンポーネントの検証とスタイル設定を行うHOCを記述します。

function withValidator(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props)

      this.validateAndStyle = this.validateAndStyle.bind(this)
      this.state = {
        style: {}
      }
    }

    validateAndStyle(e) {
      const value = e.target.value
      const valid = value && value.length > 3 // shared logic here
      const style = valid ? {} : { border: '2px solid red' }
      console.log(value, valid)
      this.setState({
        style: style
      })
    }

    render() {
      return <WrappedComponent
        onChange={this.validateAndStyle}
        style={this.state.style}
        {...this.props} />
    }
  }
}

これらのHOCは同じ検証動作を共有します。

const InputWithValidator = withValidator(Input)
const TextAreaWithValidator = withValidator(TextArea)

render((
  <div>
    <InputWithValidator />
    <TextAreaWithValidator />
  </div>
), document.getElementById('root'));

簡単なデモを作成しました。

編集:別のデモでは、propsを使用して関数の配列を渡しているため、次HOCのような複数の検証関数で構成されるロジックを共有できます。

<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />

Edit2:React 16.8以降では、ロジックを共有するもう1つの優れた方法であるフックという新機能が提供されています。

const Input = (props) => {
  const inputValidation = useInputValidation()

  return (
    <input type="text"
    {...inputValidation} />
  )
}

function useInputValidation() {
  const [value, setValue] = useState('')
  const [style, setStyle] = useState({})

  function handleChange(e) {
    const value = e.target.value
    setValue(value)
    const valid = value && value.length > 3 // shared logic here
    const style = valid ? {} : { border: '2px solid red' }
    console.log(value, valid)
    setStyle(style)
  }

  return {
    value,
    style,
    onChange: handleChange
  }
}

https://stackblitz.com/edit/react-shared-validation-logic-using-hook?file=index.js


ありがとうございました。私はこのソリューションから本当に学びました。複数のバリデーターが必要な場合はどうなりますか?たとえば、3文字のバリデーターに加えて、数字が入力されていないことを確認する別のバリ​​データーが必要な場合はどうすればよいでしょうか。バリデータを作成できますか?
ユーセフシェリフ

1
@YoussefSherif複数の検証関数を準備し、それらをの小道具として渡すことができHOCます。別のデモの編集を参照してください。
ボブ

HOCは基本的にコンテナコンポーネントですか?
センセイ

はい、React docから:「HOCは入力コンポーネントを変更せず、継承を使用してその動作をコピーしないことに注意してください。むしろ、HOCは元のコンポーネントをコンテナコンポーネントにラップすることによって構成します。HOCは純粋です副作用なしの関数。」
ボブ2018年

1
要件はロジックを注入することでしたが、これを行うためにHOCが必要な理由はわかりません。あなたはHOCでそれを行うことができますが、それは過度に複雑に感じます。HOCについての私の理解は、追加および管理する必要があるいくつかの追加の状態、つまり純粋なロジック(ここではそうでした)ではない場合です。
ミッキープリ

4

Angular2 +でも、サービスはAngularに限定されません。

サービスはヘルパー関数の単なるコレクションです...

そして、それらを作成してアプリケーション全体で再利用する方法はたくさんあります...

1)以下のように、jsファイルからエクスポートされたすべての機能を分離できます。

export const firstFunction = () => {
   return "firstFunction";
}

export const secondFunction = () => {
   return "secondFunction";
}
//etc

2)関数のコレクションなどのファクトリメソッドを使用することもできます... ES6では、関数コンストラクタではなくクラスにすることができます。

class myService {

  constructor() {
    this._data = null;
  }

  setMyService(data) {
    this._data = data;
  }

  getMyService() {
    return this._data;
  }

}

この場合、新しいキーでインスタンスを作成する必要があります...

const myServiceInstance = new myService();

また、この場合、各インスタンスには独自のライフがあるため、共有する場合は注意してください。その場合、必要なインスタンスのみをエクスポートする必要があります...

3)関数とユーティリティを共有しない場合は、Reactコンポーネントに配置することもできます。この場合、reactコンポーネントの関数と同じように...

class Greeting extends React.Component {
  getName() {
    return "Alireza Dezfoolian";
  }

  render() {
    return <h1>Hello, {this.getName()}</h1>;
  }
}

4)Reduxを使用するなど、物事を処理する別の方法です。これは一時的なストアであるため、Reactアプリケーションにある場合、多くのゲッターセッター関数を使用できます。使用するを使用できるようになります。これは大きなストアのようなものです。状態を追跡し、コンポーネント間で共有できるため、サービスで使用するゲッターセッターに関する多くの苦痛を取り除くことができます...

DRYコードを実行し、コードを再利用可能かつ読み取り可能にするために使用する必要があるものを繰り返さないことは常に良いことですが、ReactアプリAngularの方法を実行しないでください。サービスと、アイテム1のようないくつかの再利用可能なヘルパー関数での使用を制限します...


確かに、あなたは私の個人的なウェブサイトでそれを見つけることができます。それは私のプロフィールページからリンクされています...
Alireza

「ReactでAngularの方法をフォローしない」.. ahem AngularはReduxの使用を促進し、ObservablesとRxJS / StoreのようなReduxのような状態管理を使用してストアをプレゼンテーションコンポーネントにストリーミングします。..あなたはAngularJSを意味しましたか?それは別のことです
スポック

1

私はあなたと同じブーツにいます。あなたが言及した場合、私は入力検証UIコンポーネントをReactコンポーネントとして実装します。

検証ロジックの実装自体は(絶対に)結合してはならないことに同意します。したがって、それを別のJSモジュールに入れます。

つまり、結合すべきではないロジックについては、別のファイルでJSモジュール/クラスを使用し、require / importを使用してコンポーネントを「サービス」から分離します。

これにより、依存関係の注入と2つの単体テストを個別に行うことができます。


1

または、クラス継承「http」をReactコンポーネントに注入できます

小道具オブジェクトを介して。

  1. 更新:

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
  2. React Component ReactAppを次のように編集するだけです。

    class ReactApp extends React.Component {
    
    state = {
    
        data: ''
    
    }
    
        render(){
    
        return (
            <div>
            {this.props.data.getData()}      
            </div>
    
        )
        }
    }

0

私が遭遇した再利用可能なロジックで最もよく使用されるパターンは、フックを作成するか、utilsファイルを作成することです。それはあなたが達成したいことに依存します。

hooks/useForm.js

フォームデータを検証する場合と同様に、useForm.jsという名前のカスタムフックを作成してフォームデータを提供すると、次の2つを含むオブジェクトが返されます。

Object: {
    value,
    error,
}

あなたは進歩するにつれて間違いなくそれからより多くのものを返すことができます。

utils/URL.js

別の例として、URLから情報を抽出したい場合は、関数を含むutilsファイルを作成し、必要な場所にインポートします。

 export function getURLParam(p) {
...
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.