【问题标题】:How to update the Redux store after Apollo GraphQL query returnsApollo GraphQL 查询返回后如何更新 Redux 存储
【发布时间】:2017-07-30 08:40:07
【问题描述】:

我正在使用 react apollo 提供的 graphql HOC 获取数据列表。例如:

const fetchList = graphql(
  dataListQuery, {
    options: ({ listId }) => ({
      variables: {
        listId,
      },
    }),
    props: ({ data: { loading, dataList } }) => {
      return {
        loading,
        list: dataList,
      };
    }
  }
);

我在受控单选按钮组中显示列表,默认情况下我需要选择其中一项。选中项的id 保存在 Redux 存储中。

那么,问题是查询成功返回后如何更新Redux store(即设置selectedItem)?

我想到的一些选项:

选项 1

我应该在我的 Redux reducer 中监听 APOLLO_QUERY_RESULT 操作吗?但这有点尴尬,因为如果之前已经运行过查询,我需要同时收听APOLLO_QUERY_RESULTAPOLLO_QUERY_RESULT_CLIENT。而且operationName 属性只出现在APOLLO_QUERY_RESULT 动作中,而不出现在APOLLO_QUERY_RESULT_CLIENT 动作中。所以我需要剖析每一个APOLLO_QUERY_RESULT_CLIENT 动作才能知道它来自哪里。难道没有一种简单直接的方法来识别查询结果操作吗?

选项 2

我是否应该在 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);
      }
    }
  })
);

选项 3

我是否应该直接使用 Apollo 客户端,将其注入 withApollo,然后使用 client.query(...).then(result => { /* some logic */ selectItem(...)}) 调度我的操作。但是我会失去 react-apollo 集成的所有好处,所以不是一个真正的选择。

选项 4

查询返回后我是否应该完全不更新 Redux 存储?因为我也可以只实现一个选择器,如果它已设置,则返回 selectedItem,如果没有,它会尝试通过浏览商店的 apollo 部分来派生它。

我没有一个选择让我满意。那么,我该怎么做呢?

【问题讨论】:

  • 我现在也遇到了类似的问题,你最后用的是哪个选项?
  • 我目前正在使用选项 2。
  • 但是选项二总是有效吗?我的印象是 componentWillReceiveProps 仅在道具更改时运行,而不一定在第一次渲染时运行。所以如果你的 props 没有发生变化,这个生命周期方法就不会运行,你的 action 也不会被调度。不过,也许我误解了该生命周期方法的语义。
  • @AdamDonahue React 文档明确指出:请注意,即使 props 没有更改,React 也可能会调用此方法,因此如果您只想处理,请确保比较当前值和下一个值变化。当父组件导致您的组件重新渲染时,可能会发生这种情况。 Docs
  • @TheWebweiser 我想你误解了。我的意思是,如果您的初始道具集永远不会改变,那么 componentWillReceiveProps 可能不会运行。这至少是我对这个生命周期方法文档的以下部分的解释:“React 在挂载期间不会使用初始道具调用 componentWillReceiveProps。它仅在某些组件的道具可能更新时才调用此方法。”那么,似乎很清楚,上面的选项 2 是不完整的。或者可以,除非您以某种方式强制更改道具。

标签: reactjs apollostack react-apollo apollo-client


【解决方案1】:

我过去遇到过类似的问题,并选择了类似于选项 2 的内容。 如果你同时拥有自己的 redux 存储和 apollo 自己的内部存储,那么在它们之间同步状态就会成为问题。

如果您使用的是 apollo,我建议您摆脱自己的 redux 商店。如果同时依赖gql server和一些rest server,在逻辑上和物理上分离数据。

一旦您决定使用 apollo 作为您的“数据源”,调度只是突变,而获取状态只是查询。您还可以使用查询进行过滤、排序等

【讨论】:

    【解决方案2】:

    在我看来,最好的方法是创建 选项 2hoc 的稍微修改和可组合的版本,其使用方式类似于 graphql特设的。这是我想到的一个示例用法:

    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)
    }
    

    即使我还没有尝试过这个伪代码,它没有测试甚至没有完成,它背后的想法非常简单易懂。希望它会帮助某人

    【讨论】:

      【解决方案3】:

      我使用 hoc,它比选项 2 稍好一些。我在撰写结束时使用 withLoader Hoc。

      const enhance = compose(
          connect(),
          graphql(dataListQuery, {
            options: ({ listId }) => ({
              variables: {
                listId,
              },
            }),
            props: ({ data: { loading, dataList } }) => ({
              isLoading:loading,
              isData:!!dataList,
              dataList
             }),
          }
        ),
      withLoader
      )(Component)
      

      WithLoader hoc 渲染组件基于两个 Props isData 和 isLoading。如果 isData 为 true,则它呈现 Wrapped Component,否则呈现加载器。

          function withLoader(WrappedComponent) {
              class comp extends React.PureComponent {
                 render(){
                    return this.props.isData?<WrappedComponent {...this.props}/>:<Loading/>
                 }
              }
          }
      

      我在 Component 的 componentWillMount 方法中设置了 dataList 的第一项。在我们获得由 withLoader hoc 确保的 dataList 之前,该组件不会挂载。

      【讨论】:

        【解决方案4】:

        我会监听 componentDidUpdate 中的变化,并在它们发生时调度一个将在 Redux 存储中设置 selectedItem 的操作

        componentDidUpdate(prevProps, prevState) {
        
            if (this.props.data !== prevProps.data) {
                dispatch some action that set whatever you need to set
            }
        }
        

        【讨论】:

          【解决方案5】:

          应该有足够的使用'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,
                  }
                },
              }
            ),
          );
          

          【讨论】:

            【解决方案6】:

            我会做类似于选项2的事情,但将生命周期方法放入实际的组件中。这样生命周期中的业务逻辑将与继承自 Container 的 props 分离。

            所以是这样的:

            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)
            

            【讨论】:

            • 这似乎只是一个品味问题,但实际上并非如此:我个人喜欢尽可能将我的表示组件实现为纯函数。而且我的组件不会也不应该关心选择了哪个特定项目。此外,预选项目的逻辑在应用程序的不同部分可能会有所不同,其中相同的表示组件用于单个选择列表。
            • @TheWebweiser 我也喜欢将组件作为纯函数。我发布的答案是基于这样的假设,即预选逻辑在您的应用程序中是相同的。逻辑放在组件中,因为它是组件行为的一部分。但是如果你打算为其定制逻辑,那么如果 selectedItem redux 状态将被其他组件使用,我会选择选项 2,否则我会选择选项 4。
            猜你喜欢
            • 2020-05-11
            • 2017-12-27
            • 2018-11-02
            • 2023-03-13
            • 2020-11-02
            • 2017-12-09
            • 2021-03-24
            • 2018-07-15
            • 2021-05-28
            相关资源
            最近更新 更多