还在用 Redux,要不要试试 GraphQL 和 Apollo?

img
https://twitter.com/seldo/status/950794461235130368

题注:若是喜欢咱们的文章别忘了点击关注阿里南京技术专刊呦~ 本文转载自 阿里南京技术专刊-知乎,欢迎大牛小牛投递阿里南京前端/后端开发等职位,详见 阿里南京诚邀前端小伙伴加入~前端

前段时间刷 Twitter 的时候看到大 V 纷纷提到 Apollo,预测它将在 2018 年崛起。正巧碰上有使用 GraphQL 的机会,在大概翻了下 Apollo 的文档以后,我下定决心在新的前端项目里尝试下抛开已经熟悉的 Redux,彻底使用 Apollo 来写数据层。一个月后的如今,我必须出来好好赞美下这位“太阳神”了。react

GraphQL

转眼已经 2018 年了,GraphQL 已再也不是个新鲜的名词了。15 年短暂的掀起一波讨论以后,彷佛也没有听到多少它的声音了。然而 Github 在这几年里慢慢成熟,Github 也将新版 api 彻底用 GraphQL 实现。在这里我就不展开讨论 GraphQL 的自己了,它让先后端之间的数据获取变得更加简单。redux

Redux

提到前端数据管理,最早想到的就是 Redux,我想不少人都体验过对 Redux 从陌生到熟悉的各个阶段,大体应该是这样的:后端

img

  • 开始:Facebook 设计的 Flux 架构,很厉害的样子,你们都在用那我也用吧
  • 半年:数据管理变的清晰些,终于不用在组件里来回混乱的 setState 了
  • 一年:我就是个 CRUD 工程师,写个千篇一概的列表,表单页用 redux 真是折腾,多些了多少代码啊
  • 一年半:看了 redux-action,redux-promise,dva, mirror ...,根据团队的业务场景定制了最合适的中间件和插件。代码又变的简洁啦
  • 两年:该折腾的都折腾过了。有点累了,可是也离不开了。

为何累了呢?由于 Flux 的单向数据流对你来讲已经再也不新鲜了。大部分时候,store 里存放的都是从后端请求来的数据,对于它们而言,怎么样作 dispatch 和 reduce 其实并非关键,反却是怎么设计 store 值得考虑。api

当 Redux 赶上业务需求

让咱们直接以一个真实的场景做为例子吧:promise

img

这是一个很常见的评论列表,拿到需求后咱们就开始写咱们的 <Comments /> 组件了,在 Redux 的范式下,咱们不免要按照这个逻辑来写:缓存

  1. CommentsdidMount 里,dispatch 一个获取数据的 action,在这个 fetch action 内发送请求。为了作 loading,咱们极可能要再 dispatch 一个 action 去通知 redux 咱们发起了一个请求。
  2. 若是请求顺利成功了,咱们 dispatch 一个请求数据成功的 action,而后在 reducer 内处理并更新数据。
  3. Comments 内咱们收到了 props 传来的数据,正式开始渲染

咱们大量的工做花费在了如何获取数据上。而咱们面临的挑战又是什么呢?看几个产品经理们可能会提的需求bash

  • 用户建立或修改评论,要能马上在列表中看到更新;

简单,从新请求一遍整个列表接口就行了!通常而言确实足够了,不过要求高的产品可能会要求你作”乐观“更新来让体验更好。这也没什么问题,加个 reducer 就是。数据结构

  • 当鼠标 hover 在用户头像上的时候,要弹出用户的详细数据(我的简介,联系方式...)

首先你会想,后端大哥能不能把这些字段都帮我加在评论的接口数据里,他坚决果断的拒绝了你,拿出一个 commonUser 的接口让你本身去调。细一想用户数据量不小,评论里也有大量的相同用户,不放在列表里也确实合理。心一横,干脆把前端这里的数据结构所有 normalize 化,按用户 id 为 key 用哈希表来存放数据。也就一个下午,你获得了一个很是完美的解决方案。架构

面对这样的场景,咱们写了太多的 命令式 代码,咱们一步步的描述了怎么去获取评论数据,在获得评论数据后再提取出全部的用户 id,去重后再次请求获取全部的用户数据,等等。咱们还须要考虑缓 normalize, 缓存,乐观更新等等细节上的问题。而这些,偏偏是 redux 帮不了咱们的。因而咱们会基于 Redux 封装更强大的库和框架,但真正 focus 在数据获取上的好像还真没看到很是合适的。

Declarative(声明式) vs Imperative(命令式)

那么在 Apollo 的世界里是什么样的呢?

import { graphql } from 'react-apollo';

const CommentsQuery = gql`
    query Comments() {
        comments {
            id
            content
            creator {
                id
                name
            }
        }
    }
`;

export default graphql(CommentsQuery)(Comments);
复制代码

咱们使用了 graphql(类比到 redux 中的 connect) 做为高阶组件将一条 GraphQL 的查询语句绑定到了 Comments 组件上,而后你全部的一切就准备就绪了。这么简单么?是的,咱们再也不须要描述怎么在 didMount 里发送请求,怎么处理请求来的数据。而是委托 Apollo 帮咱们处理这些全部事情,它会称职的帮咱们在须要的时候发送请求获取数据,而后将 data 映射到 Comments 的 props 中交给咱们。

img

不止于此,当咱们作更新操做的时候也会便捷许多。好比修改一条评论。咱们定义一个 graphql 的 mutation 操做:

// ...

const updateComment = gql`
    mutation UpdateComment($id: Int!, $content: String!) {
      UpdateComment(id: $id, content: $content) {
        id
        content
        gmtModified
      }
    }
`;

class Comments extends React.Component {
    // ...
    onUpdateComment(id, content) {
        this.props.updateComment(id, content);
    }

    // ...
}

export default graphql(updateComment)(graphql(CommentsQuery)(Comments));
复制代码

当咱们调用 updateComment 时,你就会神奇的发现,列表中的评论数据自动更新了。这是由于 apollo-client 把数据按照类型自动缓存在了 cache 中,GraphQL 节点返回的任何数据都会自动被用来更新缓存,在 UpdateComment 这个 mutation 中,咱们定义了它的返回值,一条类型为 Comment 的新修改评论,而且指定了须要接受的字段,contentgmtModified。这样,apollo-client 就会自动经过 id 和类型去更新缓存中的数据,从而从新渲染咱们的列表。

再看看剩下的需求,咱们须要在鼠标停留在用户头像时展开用户详情。这个需求下咱们不只仅须要定义咱们须要什么数据,还会关心“怎么”获取数据(在 hover 头像时发送请求)。Apollo 一样为咱们提供了 “命令式” 的支持。

class UserItem extends React.Component {
    // ...
    onHover() {
        const { client, id } = this.props;

        client.query({
           query: UserQuery,
           variables: { id }
        }).then(data => {
            this.setState({ fullUserInfo: data });
        });
    }
}

export default withApollo(UserItem);
复制代码

幸运的是这里咱们依然不须要本身考虑缓存的问题。得益于 Apollo 全局的数据缓存,当咱们查询过用户 A 以后,再次查询相同 id 的数据会直接命中缓存,apollo-client 会直接 resolve 缓存中的数据,并不发送请求。这时候问题来了,假设我就是想要每次都从新查询呢?

client.query({
   query: UserQuery,
   variables: { id },
   fetchPolicy: 'cache-and-network'
});
复制代码

Apollo 给咱们提供了不少策略来自定义缓存逻辑,好比默认的 cache-first (优先使用缓存),这里的 cache-and-network(先使用缓存,同时发请求更新),以及 cache-onlynetwork-only

这些就是 GraphQL 和 Apollo 很吸引个人一些地方。当你开始从 GraphQL 的角度来思考,你更多的关心的是你的业务组件须要什么数据,而不是怎么一步步的得到它。而剩下的大部分业务场景,均可以经过前端的数据类型推导和缓存自动解决掉。固然,篇幅有限,还有不少优雅的地方来不及说起,好比分页,直接操做缓存达到乐观更新,轮询查询,以及数据订阅等等。若是有机会的话咱们能够继续深刻探讨。

REST 和其余本地状态 ?

看到这里,你可能会以为 “GraphQL 很酷,Apollo 也很酷,可是个人后端是 REST,目前是与他们无缘了”。其实否则,从 Apollo Client 的 2.0 版本开始引入了 Apollo Link,理论上来讲咱们能够经过 GraphQL 从任何类型的数据源获取数据。

img

“经过 GraphQL“ 意味着咱们可使用书写 GraphQL 的查询语句来获取不管是 rest api 或是 client state 中的数据,这样 Apollo Client 能够替咱们管理应用中全部的数据,包括缓存和数据拼接。

const MIXED_QUERY = gql`
    query UserInfo() {
        // graphql endpoint
        currentUser {
            id
            name
        }
        // client state
        browserInfo @client {
            platform
        }
        // rest api
        messages @rest(route: '/user/messages') @type(type: '[Message]') {
            title
        }
    }
`;
复制代码

在这样一个 Query 查询中,咱们使用 GraphQL 的 directive 拼接了来自于 GraphQL,rest,client state 中的数据,将它们抽象在一块儿维护。与之相似的,咱们还能够封装相应的 mutation 实现。

尾巴

以上大概就是我这段时间使用 Apollo 和 GraphQL 的一些浅浅的实践。虽然接触的不深,但我能够感觉到 Thinking in GraphQL 为前端带来的更优雅的解决方式,和 Apollo Client 这样一个完整的前端数据层解决方案的高效。我相信在 2018 年,它们会迎来更大的增加,甚至有代替 redux 成为通用数据管理方案的可能。

Apollo 相关的社区也比较活跃,在 dev-blog.apollodata.com 上也常常发表一些颇有参考价值的文章,有兴趣能够随便看看~

相关文章
相关标签/搜索