私は、サービス/ファクトリーにロジックを抽出し、コントローラーでそれらを使用できる、角度のある世界から来ています。
Reactアプリケーションで同じことをどのように達成できるかを理解しようとしています。
ユーザーのパスワード入力を検証するコンポーネントがあるとします(それは強みです)。それはロジックがかなり複雑であるため、それ自体をコンポーネントに記述したくありません。
このロジックはどこに記述すればよいですか?フラックスを使用している場合、店で?または、より良いオプションはありますか?
私は、サービス/ファクトリーにロジックを抽出し、コントローラーでそれらを使用できる、角度のある世界から来ています。
Reactアプリケーションで同じことをどのように達成できるかを理解しようとしています。
ユーザーのパスワード入力を検証するコンポーネントがあるとします(それは強みです)。それはロジックがかなり複雑であるため、それ自体をコンポーネントに記述したくありません。
このロジックはどこに記述すればよいですか?フラックスを使用している場合、店で?または、より良いオプションはありますか?
回答:
最初の答えは、現在のコンテナー対プレゼンターのパラダイムを反映していません。
パスワードの検証など、何かをする必要がある場合は、それを実行する関数がおそらくあります。その関数を小道具として再利用可能なビューに渡します。
したがって、正しい方法は、その機能をプロパティとして持つ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)
}
}
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つだけであることを確認するために、単純なシングルトンの実装を検討することもできます。
いくつかのフォーマットロジックを複数のコンポーネント間で共有する必要があり、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);
}
Reactの目的は、論理的に結合されるべきものをより適切に結合することであることに留意してください。複雑な「パスワードの検証」方法を設計している場合、どこで組み合わせる必要がありますか?
ユーザーが新しいパスワードを入力する必要があるたびに、それを使用する必要があります。これは、登録画面、「パスワードを忘れた」画面、管理者の「別のユーザーのパスワードをリセットする」画面などに表示されます。
しかし、これらのいずれの場合でも、常に何らかのテキスト入力フィールドに関連付けられます。これが結合される場所です。
入力フィールドと関連する検証ロジックのみで構成される非常に小さなReactコンポーネントを作成します。パスワード入力を必要とする可能性のあるすべてのフォーム内にそのコンポーネントを入力します。
これは基本的には、ロジックのサービス/ファクトリーを持つのと同じ結果ですが、入力に直接結合しています。したがって、この関数は永続的に結び付けられているため、検証入力を探す場所を関数に指示する必要はありません。
また、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の経験がさらに増えました。ロジックの性質を考慮してください。
一部は再利用のためにHOCに到達しますが、私にとっては上記のほとんどすべてのユースケースをカバーしています。また、アヒルを使用して状態管理をスケーリングし、懸念を個別に保ち、UI中心の状態を維持することも検討してください。
私もAngular出身で、Reactを試しています。現在、推奨される(?)方法の1つは高次コンポーネントを使用しているようです。
高次コンポーネント(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'));
簡単なデモを作成しました。
編集:別のデモでは、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
HOC
ます。別のデモの編集を参照してください。
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のようないくつかの再利用可能なヘルパー関数での使用を制限します...
または、クラス継承「http」をReactコンポーネントに注入できます
小道具オブジェクトを介して。
更新:
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>
)
}
}
私が遭遇した再利用可能なロジックで最もよく使用されるパターンは、フックを作成するか、utilsファイルを作成することです。それはあなたが達成したいことに依存します。
hooks/useForm.js
フォームデータを検証する場合と同様に、useForm.jsという名前のカスタムフックを作成してフォームデータを提供すると、次の2つを含むオブジェクトが返されます。
Object: {
value,
error,
}
あなたは進歩するにつれて間違いなくそれからより多くのものを返すことができます。
utils/URL.js
別の例として、URLから情報を抽出したい場合は、関数を含むutilsファイルを作成し、必要な場所にインポートします。
export function getURLParam(p) {
...
}