Reactapolloが提供するgraphql
HOCを使用してデータのリストを取得しています。例えば。:
_const fetchList = graphql(
dataListQuery, {
options: ({ listId }) => ({
variables: {
listId,
},
}),
props: ({ data: { loading, dataList } }) => {
return {
loading,
list: dataList,
};
}
}
);
_
制御されたラジオボタングループにリストを表示していますが、デフォルトで項目の1つを選択する必要があります。選択したアイテムのid
はReduxストアに保存されます。
だから、問題は、クエリが正常に戻った後にReduxストアを更新する(つまり、selectedItem
を設定する)方法です。
私の頭に浮かんだいくつかのオプション:
Reduxレデューサーで_APOLLO_QUERY_RESULT
_アクションをリッスンする必要がありますか?しかし、クエリが以前に実行されている場合は、_APOLLO_QUERY_RESULT
_と_APOLLO_QUERY_RESULT_CLIENT
_の両方をリッスンする必要があるため、これはちょっと厄介です。また、operationName
小道具は_APOLLO_QUERY_RESULT
_アクションにのみ存在し、_APOLLO_QUERY_RESULT_CLIENT
_アクションには存在しません。したがって、すべての_APOLLO_QUERY_RESULT_CLIENT
_アクションを分析して、それがどこから来たのかを知る必要があります。クエリ結果のアクションを識別する簡単で直接的な方法はありませんか?
componentWillReceiveProps
で_SELECT_LIST_ITEM
_のような別のアクションをディスパッチする必要があります(例: recompose を使用):
_const enhance = compose(
connect(
function mapStateToProps(state) {
return {
selectedItem: getSelectedItem(state),
};
}, {
selectItem, // action creator
}
),
graphql(
dataListQuery, {
options: ({ listId }) => ({
variables: {
listId,
},
}),
props: ({ data: { loading, dataList } }) => ({
loading,
items: dataList,
}),
}
),
lifecycle({
componentWillReceiveProps(nextProps) {
const {
loading,
items,
selectedItem,
selectItem,
} = nextProps;
if (!selectedItem && !loading && items && items.length) {
selectItem(items[items.length - 1].id);
}
}
})
);
_
ApolloクライアントをwithApollo
で挿入して直接使用し、次にclient.query(...).then(result => { /* some logic */ selectItem(...)})
でアクションをディスパッチする必要があります。しかし、そうすると、react-apollo統合のすべての利点が失われるため、実際にはオプションではありません。
クエリが戻った後、Reduxストアをまったく更新しないでください。設定されている場合はselectedItem
を返すセレクターを実装することもでき、設定されていない場合は、ストアのapollo
部分を参照して派生しようとするためです。
私の選択肢はどれも私を満足させません。だから、私はそれをどのように正しくしますか?
ComponentDidUpdateの変更をリッスンし、それらが発生したときに、reduxストアにselectedItemを設定するアクションをディスパッチします
componentDidUpdate(prevProps, prevState) {
if (this.props.data !== prevProps.data) {
dispatch some action that set whatever you need to set
}
}
オプション2と同様のことを行いますが、ライフサイクルメソッドを実際のコンポーネントに配置します。このようにして、ライフサイクルのビジネスロジックは、コンテナから継承された小道具から分離されます。
だからこのようなもの:
class yourComponent extends Component{
componentWillReceiveProps(nextProps) {
const {
loading,
items,
selectedItem,
selectItem,
} = nextProps;
if (!selectedItem && !loading && items && items.length) {
selectItem(items[items.length - 1].id);
}
}
render(){...}
}
// Connect redux and graphQL to the Component
const yourComponentWithGraphQL = graphql(...)(yourComponent);
export default connect(mapStateToProps, mapDispatchToProps)(yourComponentWithGraphQL)
オプション2の少し良いバージョンであるhocを使用します。composeの最後にwithLoaderHocを使用します。
const enhance = compose(
connect(),
graphql(dataListQuery, {
options: ({ listId }) => ({
variables: {
listId,
},
}),
props: ({ data: { loading, dataList } }) => ({
isLoading:loading,
isData:!!dataList,
dataList
}),
}
),
withLoader
)(Component)
2つのプロップisDataとisLoadingに基づくWithLoaderホックレンダリングコンポーネント。 isDataがtrueの場合、ラップされたコンポーネントをレンダリングします。それ以外の場合はローダーをレンダリングします。
function withLoader(WrappedComponent) {
class comp extends React.PureComponent {
render(){
return this.props.isData?<WrappedComponent {...this.props}/>:<Loading/>
}
}
}
ComponentのcomponentWillMountメソッドでdataListの最初の項目を設定しました。 withLoader hocによって保証されるdataListを取得するまで、コンポーネントはマウントされません。
私の意見では、取る最善のアプローチは、graphql
と同様に使用されるhoc of Option 2のわずかに変更された構成可能なバージョンを作成することです。その場限り。頭に浮かぶ使用例は次のとおりです。
export default compose(
connect(
state => ({ /* ... */ }),
dispatch => ({
someReduxAction: (payload) => dispatch({ /* ... */ }),
anotherReduxAction: (payload) => dispatch({ /* ... */ }),
}),
),
graphqlWithDone(someQuery, {
name: 'someQuery',
options: props => ({ /* ... */ }),
props: props => ({ /* ... */ }),
makeDone: props => dataFromQuery => props.someReduxAction(dataFromQuery)
}),
graphqlWithDone(anotherQuery, {
name: 'anotherQuery',
options: props => ({ /* ... */ }),
props: props => ({ /* ... */ }),
makeDone: props => dataFromQuery => props.anotherReduxAction(dataFromQuery)
})
)(SomeComponent)
そして、最も単純な実装は次のようになります。
const graphqlWithDone = (query, queryConfig) => (Wrapped) => {
const enhance = graphql(query, {
...queryConfig,
props: (props) => ({
queryData: { ...( props[queryConfig.name] || props.data ) },
queryProps: queryConfig.props(props),
})
})
class GraphQLWithDone extends Component {
state = {
isDataHandled: false
}
get wrappedProps () {
const resultProps = { ...this.props };
delete resultProps.queryData;
delete resultProps.queryProps;
return { ...resultProps, ...this.props.queryProps }
}
get shouldHandleLoadedData () {
return (
!this.props.queryData.error &&
!this.props.queryData.loading &&
!this.state.isDataHandled
)
}
componentDidUpdate() {
this.shouldHandleLoadedData &&
this.handleLoadedData(this.props.queryData);
}
handleLoadedData = (data) => {
if (!makeDone || !isFunction(makeDone)) return;
const done = makeDone(this.wrappedProps)
this.setState({ isDataHandled: true }, () => { done(data) })
}
render() {
return <Wrapped {...this.wrappedProps} />
}
}
return enhance(GraphQLWithDone)
}
私がこの擬似コードを試したことがないとしても、テストも終了もしていません。その背後にある考え方は非常に単純で理解しやすいものです。それが誰かを助けることを願っています
'props'を使用するのに十分なはずです。
const enhance = compose(
connect(
function mapStateToProps(state) {
return {
selectedItem: getSelectedItem(state),
};
}, {
selectItem, // action creator
}
),
graphql(
dataListQuery, {
options: ({ listId }) => ({
variables: {
listId,
},
}),
props: ({ data: { loading, dataList } }) => {
if (!loading && dataList && dataList.length) {
selectItem(dataList[dataList.length - 1].id);
}
return {
loading,
items: dataList,
}
},
}
),
);
私は過去に同様の問題に直面し、オプション2と同様のものを選択しました。独自のreduxストアとapollo独自の内部ストアの両方がある場合、それらの間の状態の同期が問題になります。
Apolloを使用している場合は、独自のreduxストアを削除することをお勧めします。 gqlサーバーといくつかのレストサーバーに同時に依存している場合は、データを論理的および物理的に分離します。
「データソース」としてapolloを使用することを決定すると、ディスパッチは単なる変更であり、状態の取得は単なるクエリです。クエリでフィルタリング、並べ替えなどを行うこともできます