私はangularの世界から来ています。そこではロジックをサービス/工場に抽出し、コントローラーでそれらを使用できます。
Reactアプリケーションで同じことを実現する方法を理解しようとしています。
ユーザーのパスワード入力を検証するコンポーネントがあるとしましょう(強度です)。ロジックは非常に複雑であるため、コンポーネント自体に記述したくありません。
このロジックはどこに書くべきですか?フラックスを使用している場合、店で?または、より良いオプションがありますか?
最初の答えは、現在の Container vs Presenter パラダイムを反映していません。
パスワードの検証など、何かを行う必要がある場合は、おそらくそれを行う機能があります。その関数を小道具として再利用可能なビューに渡します。
したがって、それを行う正しい方法は、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
};
私が試したことはないが、使用されていると見られるさらなる方法は、ミドルウェアを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)
}
}
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つだけであることを確認するために、単純なシングルトンの実装を検討することもできます。
Reactの目的は、論理的に結合する必要のあるものをより適切に結合することです。複雑な「パスワードの検証」メソッドを設計している場合、どこで結合する必要がありますか?
ユーザーが新しいパスワードを入力する必要があるたびに使用する必要があります。これは、登録画面、「パスワードを忘れた」画面、管理者の「別のユーザーのパスワードをリセットする」画面などに表示されます。
しかし、いずれの場合でも、常にテキスト入力フィールドに関連付けられます。それはそれが結合されるべき場所です。
入力フィールドと関連する検証ロジックのみで構成される非常に小さなReactコンポーネントを作成します。パスワード入力を必要とする可能性のあるすべてのフォーム内にそのコンポーネントを入力します。
基本的に、ロジックのサービス/ファクトリーと同じ結果ですが、入力に直接結合しています。そのため、検証入力を探す場所を関数に伝える必要がなくなりました。検証入力は永続的に結合されているためです。
いくつかのフォーマットロジックを複数のコンポーネント間で共有する必要があり、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);
}
また、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>
以下に簡単な例を示します。
同じ状況:複数の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の経験。ロジックの性質を考慮してください。
また、再利用のために HOCs に達するものもありますが、私にとっては、ほとんどすべてのユースケースをカバーしています。また、懸念を分離してUI中心に保つために、 ducks を使用して状態管理をスケーリングすることを検討してください。
私もAngularから来て、Reactを試しています。今のところ、推奨される(?)方法の1つは High-Order Components を使用しているようです。
高次コンポーネント(HOC)は、コンポーネントロジックを再利用するためのReactの高度な手法です。 HOCはReact APIの一部ではありません。それらは、Reactの構成的性質から生まれたパターンです。
input
とtextarea
があり、同じ検証ロジックを適用したいとします。
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'));
シンプルな デモ を作成しました。
Edit:別の demo は、propsを使用して関数の配列を渡し、複数の検証関数で構成されるロジックを共有できるようにしますHOC
sのような:
<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />
サービスは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コンポーネントに入れることもできます。この場合は、反応コンポーネントの機能として...
class Greeting extends React.Component {
getName() {
return "Alireza Dezfoolian";
}
render() {
return <h1>Hello, {this.getName()}</h1>;
}
}
4)物事を処理する別の方法として、Reduxを使用できます。それはあなたのための一時的なストアなので、あなたがReactアプリケーションにそれを持っているなら、多くのであなたを助けることができます使用するゲッターセッター関数...状態を追跡し、コンポーネント間で共有できる大きなストアのようなものであるため、使用するゲッターセッタースタッフの多くの苦痛を取り除くことができますサービス...
DRYコードを実行し、コードを再利用可能かつ読み取り可能にするために使用する必要があるものを繰り返さないことは常に良いことですが、Angularの方法でReact appに従うことを試みないでください。項目4で述べたように、Reduxを使用するとサービスの必要性が減り、アイテム1などの再利用可能なヘルパー関数での使用を制限します...
または、クラス継承「http」をReactコンポーネントに挿入できます
propsオブジェクト経由。
更新:
ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
次のようにReact Component ReactAppを編集するだけです:
class ReactApp extends React.Component {
state = {
data: ''
}
render(){
return (
<div>
{this.props.data.getData()}
</div>
)
}
}
私はあなたと同じブーツにいます。あなたが言及する場合、入力検証UIコンポーネントをReactコンポーネントとして実装します。
検証ロジックの実装自体は(絶対に)結合されるべきではないことに同意します。したがって、私はそれを別のJSモジュールに入れます。
つまり、結合すべきでないロジックの場合は、別個のファイルでJSモジュール/クラスを使用し、require/importを使用してコンポーネントを「サービス」から分離します。
これにより、2つの独立した依存関係の注入と単体テストが可能になります。