closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

GraphQL进阶篇: 挥手Redux不是梦 #25

Open closertb opened 5 years ago

closertb commented 5 years ago

写于: 2017-08-05
同学,GraphQL了解一下:基础篇
同学,GraphQL了解一下:实践篇 首先,需要澄清,这有点标题党,像Redux, Mobx,Flux这种状态管理库,在日常的开发中的地位还是难以撼动的,但是我们可以试着去了解ApolloClent,它在做本地状态管理所应用的思想,ApolloClient官方有一片文章:The future of state management。如果对GraphQL还不是很了解的同学,可以看一下开头的两篇文章。作为自己今年下半年学习的重点,如果仅仅去了解好像有点半途而废的感觉,所以我选择如果学,请深钻的道路。 文章所引用的源码地址

实践篇的最后,我在最后一段抛出graphql怎么与现在的redux做集成,而MagicPig同学在评论里告诉我ApolloClent其实可以不依赖第三方库,自己做状态管理。当时自己入门不深,也是一脸懵逼,后面受其指点,在ApolloClent官网转悠,发现还有很多宝藏可以挖掘。

用ApolloClent代替Redux

在Redux的官方教程中,曾用一个TodoList来介绍Redux的状态管理,看下图: image 这上面的演示,如果你不是一个react新手,应该不会太陌生。在react应用中,加入redux,实现本地添加list条目与条目状态切换,以及列表的过滤条件切换,如果关于它的实现还不是很了解,可以到Redux官网重新温习一次。 ApolloClent的Local state management章节,为了说明怎样用ApoloClient管理应用的本地状态(Learn how to store your local data in Apollo Client),官方提供了一个示例,应用其state功能以及grapql本地查询语法,实现了一个拥有同样功能的TodoList,CodeSandBox源码地址,不过官方提供的这个在线演示,好像是少了些东西,我并没有完全跑成功,我把东西down下来,改把,改把,在本地还是跑成功了,想了解的,可以通过上方的地址下载。

基础知识梳理

在实践篇中创建一个client实例代码是这样的:

import { ApolloProvider } from 'react-apollo';
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
  uri: 'http://localhost:8080/graphql',  // 服务端接口
  batchInterval: 10,
  opts: {
    credentials: 'cross-origin', // App端单独跑了一个服务,所以涉及到跨域;
  },
});

上面的代码,就是建立了一个远程的Graphql操作服务,而在这里,我们需要加入本地的状态管理,代码变成了这样:

import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { withClientState } from 'apollo-link-state';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { resolvers, typeDefs, defaults } from '../client/index';
const cache = new InMemoryCache();
const client = new ApolloClient({
  cache, // 本地数据存储
  link: withClientState({ resolvers, defaults, cache, typeDefs }).concat(
    new HttpLink({
      uri: 'http://localhost:4001/graphql',
      batchInterval: 10,
      opts: {
        credentials: 'cross-origin',
      },
    })
  ),
});

首先,ApolloClient 这个对象引入的NPM包变了,以前是从apollo-boost引入的,现在是从apollo-client引入的。其次这里加入的本地状态管理,是用withClientState创建了一个link对象,传入了四个参数(resolvers, defaults, cache, typeDefs),cachce很简单,就是上面new InMemoryCache()创建的本地存储,这里简单说明一下resolvers, defaults, typeDefs。

基本定义

首先需要知道的,ApolloClient所建立的状态管理思想与Redux的操作思路基本一致。只是实现上。ApolloClient的本地状态管理,是用Graphql那一套来做的,即query, root, resolver, schema这些概念,建立一套本地的Query(query, mutation, subscrition)。

const GET_STATUS = gql { readStatus @client } ; // 每次页面渲染前,从cache中读取status的值 const BookList = () => (

{({ data: { readStatus } }) => { return (
); }}

);

每次页面渲染前,从cache中读取status的值,然后将其作为props传递到TabBar与Content组件。  
```js
/** TabBar.js **/
import { Mutation } from 'react-apollo';
import { Tabs } from 'antd';
import gql from 'graphql-tag';

const TabPane = Tabs.TabPane;
const ReadStatus = [{
  label: '总书单',
  value: '',
}, {
  label: '已读',
  value: 'read',
}, {
  label: '期望读',
  value: 'wish',
}, {
  label: '正在读',
  value: 'reading',
}];
const ChangeStatus = gql`
  mutation ChangeStatus($status: String){
    changeStatus(status: $status) @client
  }
`;
export default class TabBar extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { status } = this.props;
    return (
      <Mutation mutation={ChangeStatus} >
        {changeStatus => (
          <Tabs defaultActiveKey={status} onChange={(value) => { changeStatus({ variables: { status: value } }); }}>
            {ReadStatus.map(({ label, value }) =>
              <TabPane tab={label} key={value} />)}
          </Tabs>
        )}
      </Mutation>
    );
  }
}

tabBar组件根据拿到的status,渲染tab的选中状态,同时给Tabs增加了相应的点击事件,来触发cache中readStatus值的变更。

/** ContentHoc.js **/
import { Query } from 'react-apollo';
import { Table } from 'antd';
import gql from 'graphql-tag';

const columns = [{
  title: '序号',
  dataIndex: 'book_id',
  key: 'id',
}, {
  title: '书名',
  dataIndex: 'title',
  key: 'title',
}, {
  title: 'url',
  dataIndex: 'image',
  key: 'image',
}];
export const BOOKS_QUERY = gql`
  query($status: String){
    collections(status: $status) {
      total
      collections {
        book_id
        title
        image
      }
    }
  }
`;

export default class BookList extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { status } = this.props;

    return (
      <Query query={BOOKS_QUERY} variables={{ status }}>
        {({ loading, error, data }) => {
          if (loading) {
            return <div className="loading">Loading...</div>;
          }
          if (error) {
            return <div className="loading error">error</div>;
          }
          const { collections: lists, total } = data.collections;
          const tableProps = {
            dataSource: lists,
            columns,
            rowKey: 'book_id',
          };
          return (
            <div>
              <p className="total">总共有<span>{total}</span>本图书</p>
              <Table {...tableProps} />
            </div>
          );
        }}
      </Query>
    );
  }
}  

这一部分应该是与我们使用Redux区别最大的部分,传统的Redux用法会将list的获取与保存放置在容器组件中,然后通过props传递到展示组件。而在这里,利用了apolloClient提供的Query组件,来做以前容器组件干的活。然后以前我们需要在请求的过程中捕获错误或请求状态,而在这里,Query组件提供了一系列的属性(loading,error),可以直接使用,无需自身维护。 另外,为了调试方便,apolloClient还提供了像React-developer-Tool一样的调试工具(需要梯子):Apollo Client Devtools

使用总结

通过一个实践,自我感觉其实不管使用Redux还是apolloClient,我们都采用了相同的思路,只是具体的实现方式有差别,或则说Redux与apolloClient用两种不同的手段达到了同一种效果:Redux的dispatch Type 与 apolloClient的query @client查询。另外,就上面这种简单纯粹的中后台系统,使用apolloClient就已足够,不需要再加入Redux家族来帮忙处理。这个月被借调去支撑另一个团队,学习的步伐好像又要放慢了。哎。。。。。。