Taro: Apollo GraphQL

Created on 14 Sep 2018  ·  26Comments  ·  Source: NervJS/taro

怎么样把Apollo GraphQL给整合进来?官方能不能给个思路
或许应该有个@taro-apollo...
https://www.apollographql.com/docs/react/essentials/get-started.html

good first issue

Most helpful comment

@janpo 拿走别客气

仿照react-apollo 1.x版做的
client是个apollo client 你可以放个静态变量或者import

```

function optionsEqual(op1, op2) {
if (_.isEmpty(op1) && _.isEmpty(op2)) {
return true;
}
if (_.isEmpty(op1) || _.isEmpty(op2)) {
return false;
}

return op1.query === op2.query && _.isEqual(op1.variables, op2.variables);

}

export function withQuery(config = {}) {
const {
query: configQuery,
variables: configVariables,
} = config;

const evalQuery = (props, state) => {
    const query = _.isFunction(configQuery) ? configQuery(props, state) : configQuery;
    if (!query) {
        throw new Error("null query!!");
    }
    return query;
};

const evalVariables = (props, state) => {
    return _.isFunction(configVariables) ? configVariables(props, state) : configVariables;
};

const shouldSkip = (props, state) => {
    const query = evalQuery(props, state);
    if (!query) {
        return true;
    }
    const queryNeedsVariable = !!_.get(query, "definitions.0.variableDefinitions.0");
    return queryNeedsVariable && !evalVariables(props, state);
};

return Component => class extends Component {

    constructor() {
        super(...arguments);
        this._queryWatcher = null;
        this._querySubscription = null;
    }

    componentDidMount() {
        if (super.componentDidMount) {
            super.componentDidMount(...arguments);
        }
        this._watchOrUpdateQuery(this.props, this.state);
    }

    componentDidUpdate() {
        if (super.componentDidUpdate) {
            super.componentDidUpdate(...arguments);
        }
        this._watchOrUpdateQuery(this.props, this.state);
    }

    componentWillUnmount() {
        if (super.componentWillUnmount) {
            super.componentWillUnmount(...arguments);
        }

        if (this._querySubscription) {
            this._querySubscription.unsubscribe();
        }

        delete this._querySubscription;
        delete this._queryWatcher;

    }
    _watchOrUpdateQuery = (props, state) => {
        if (shouldSkip(props, state)) {
            return;
        }

        const options = {
            query: evalQuery(props, state),
            variables: evalVariables(props, state),
        };

        if (optionsEqual(options, this.prevOptions)) {
            return;
        }
        this.prevOptions = { ...options };
        if (this._queryWatcher) {
            this._queryWatcher.setOptions(options);
        } else {
            this._queryWatcher = client.watchQuery(options);
            this._querySubscription = this._queryWatcher.subscribe({
                next: this._updateResult,
                error: this._updateResult,
            });
        }
        this._updateResult();
    }

    _updateResult = () => {
        if (!this._queryWatcher) {
            return;
        }
        const result = this._queryWatcher.currentResult();
        // const r = _.get(result, "data.requests", []);
        // console.log(_.map(r, "id"));

        this.prevProps = _.assign({}, this.props);
        _.assign(this.props, result);
        this._unsafeCallUpdate = true;
        this.setState({}, function () {
            delete this._unsafeCallUpdate;
        });
    }

};

}```

All 26 comments

@janpo Apollo GraphQL 这个库我们还没使用过,暂时给不了思路,你看看能不能为 Taro 增加这个支持呢,非常欢迎~

@janpo 拿走别客气

仿照react-apollo 1.x版做的
client是个apollo client 你可以放个静态变量或者import

```

function optionsEqual(op1, op2) {
if (_.isEmpty(op1) && _.isEmpty(op2)) {
return true;
}
if (_.isEmpty(op1) || _.isEmpty(op2)) {
return false;
}

return op1.query === op2.query && _.isEqual(op1.variables, op2.variables);

}

export function withQuery(config = {}) {
const {
query: configQuery,
variables: configVariables,
} = config;

const evalQuery = (props, state) => {
    const query = _.isFunction(configQuery) ? configQuery(props, state) : configQuery;
    if (!query) {
        throw new Error("null query!!");
    }
    return query;
};

const evalVariables = (props, state) => {
    return _.isFunction(configVariables) ? configVariables(props, state) : configVariables;
};

const shouldSkip = (props, state) => {
    const query = evalQuery(props, state);
    if (!query) {
        return true;
    }
    const queryNeedsVariable = !!_.get(query, "definitions.0.variableDefinitions.0");
    return queryNeedsVariable && !evalVariables(props, state);
};

return Component => class extends Component {

    constructor() {
        super(...arguments);
        this._queryWatcher = null;
        this._querySubscription = null;
    }

    componentDidMount() {
        if (super.componentDidMount) {
            super.componentDidMount(...arguments);
        }
        this._watchOrUpdateQuery(this.props, this.state);
    }

    componentDidUpdate() {
        if (super.componentDidUpdate) {
            super.componentDidUpdate(...arguments);
        }
        this._watchOrUpdateQuery(this.props, this.state);
    }

    componentWillUnmount() {
        if (super.componentWillUnmount) {
            super.componentWillUnmount(...arguments);
        }

        if (this._querySubscription) {
            this._querySubscription.unsubscribe();
        }

        delete this._querySubscription;
        delete this._queryWatcher;

    }
    _watchOrUpdateQuery = (props, state) => {
        if (shouldSkip(props, state)) {
            return;
        }

        const options = {
            query: evalQuery(props, state),
            variables: evalVariables(props, state),
        };

        if (optionsEqual(options, this.prevOptions)) {
            return;
        }
        this.prevOptions = { ...options };
        if (this._queryWatcher) {
            this._queryWatcher.setOptions(options);
        } else {
            this._queryWatcher = client.watchQuery(options);
            this._querySubscription = this._queryWatcher.subscribe({
                next: this._updateResult,
                error: this._updateResult,
            });
        }
        this._updateResult();
    }

    _updateResult = () => {
        if (!this._queryWatcher) {
            return;
        }
        const result = this._queryWatcher.currentResult();
        // const r = _.get(result, "data.requests", []);
        // console.log(_.map(r, "id"));

        this.prevProps = _.assign({}, this.props);
        _.assign(this.props, result);
        this._unsafeCallUpdate = true;
        this.setState({}, function () {
            delete this._unsafeCallUpdate;
        });
    }

};

}```

周末我发个包出来

@janpo
你还需要这个 http link

const httpLink = new HttpLink({
    uri: SERVER_URL + "/graphql",
    fetch: (url, { body, method }) =>
        Taro.request({
            url,
            header: createHeaders(),
            method,
            data: body,
            dataType: "text",
        }).then(response => ({
            text: () => Promise.resolve(response.data),
        })),
});

@kdong007 发个包出来吧,thx

@janpo 你先copy到自己project里用着

定义一个fetch函数,用于微信小程序即可

import ApolloClient from 'apollo-boost';
import Taro, { ENV_TYPE } from '@tarojs/taro';

//apollo-client默认支持fetch api,但微信小程序不支持fetch,所以需改造Taro.request,等同于wx.request
const fetch = (url, { body: data, ...fetchOptions }) => {
  //Taro.request默认会对res做JSON.parse,但apollo-http-link需要text,也要做一次JSON.parse
  //所以要让微信返回text,需做如下配置:dataType: 'txt', responseType: 'text'
  //dataType    String  否   json    如果设为json,会尝试对返回的数据做一次 JSON.parse    
  //responseType    String  否   text    设置响应的数据类型。合法值:text、arraybuffer
  return Taro.request({ url, data, ...fetchOptions, dataType: 'txt', responseType: 'text' })
    .then((res) => {
      res.text = () => Promise.resolve(res.data)
      return res
    })
}

const uri = `http://localhost:8080/graphql`;
const graphqlClient = new ApolloClient(Taro.getEnv() === ENV_TYPE.WEB ? { uri } : { uri, fetch });

加了fetch之后,其它apollo功能,我用下来都是正常的,依赖包如下

        "apollo-boost": "^0.1.12",
        "graphql": "^0.13.2",
        "graphql-tag": "^2.9.2",

@neoscript99 谢谢! 如果能把react-apollo到taro就完美了,不知道你怎么想?

我觉得把graphql查询和react控件揉在一起不是一种好的编程模式,
我目前是通过redux的action调用graphql,react控件只和state发生关系。

https://gist.github.com/neoscript99/e2a469088209fd8d4197b5b6bd739673

这是我用来做graphql查询的工具类,包含了通用的增删改查功能,可以提供给redux的action调用,里面的domain最终对应一张数据库表。
但这个类,仅适合我自己的后台,你有兴趣参考下。@janpo

@neoscript99 你确定你理解了graphql的核心么 graphql不是单纯resolver查询chain这些入门的东西 而是type based caching

多种resolver之间的互相cache 以及mutation之后相关的自动刷新之前query相关的UI组件等 这些才是graphql存在的意义

例如你有一个user下面有帖子posts 按照更新时间排序,每个Post有 content/lastUpdate

产品上假设有三页 个人题列表 / 帖子详细列表 / 修改帖子内容

修改帖子后由于帖子内容和更新时间的更新 导致帖子列表重新排序以及帖子详情刷新

你不绑定组件你怎么做跨页刷新? 全局事件么? 然后各种 on off 然后在event listerner遗漏中无限debug

而真正的graphql 是这样的
个人列表类似于这样

user{
  id
  post{
    id
    content
    lastUpdate
  }
}

帖子详情类似于这样

post{
   id
   content
   lastUpdate
   xxx 
   xxx
}

而修改页面mutation 是类似于这样的

mutation xxx{
  editPost(xxx){
    id
    content
    lastUpdate
    user{
      id
      posts{
        id
      }
    }
  }
}

一个mutation chain 获取所有更新后需要刷新的每个节点 cache自动更新 触发所有相关UI组件刷洗

apollo client官方的教程你可能需要多研究一下

@neoscript99 apollo cache你可以想象成一个server data的redux 从服务器数据到网络状态到前端状态 全部是一体化state based. this is how react should be

@kdong007
type based caching没研究过,也就是说它能帮助你自动管理好state,确实实用。

@kdong007 已经在关注你的 https://github.com/kdong007/taro-apollo 期望更新!谢谢

@kdong007 taro-apollo近期会放出吗?持续关注中... 谢谢

@janpo 已经发出来了 https://github.com/kdong007/taro-apollo 欢迎来踩

Hello~

您的问题楼上已经提供了解决方案,如果没有更多的问题这个 issue 将在 15 天后被自动关闭。

如果您在这 15 天中更新更多信息自动关闭的流程会自动取消,如有其他问题也可以发起新的 Issue。

Good luck and happy coding~

@janpo 你先copy到自己project里用着

请问下有用过 apollo-link-rest 这个库吗? 在小程序中报错了, 需要一些headers信息。 如果用的 http link 在小程序中可用吗?

@kdong007 请问下有没有相关的小程序这边的处理方式? 使用 apollo-link-http的

@Janice1114 原生小程序么? 不用taro/wepy这些框架的那种

@Janice1114 原生小程序么? 不用taro/wepy这些框架的那种

@kdong007 taro的, 例如怎么基于 apollo-link-http 发送请求给中台获取数据,麻烦帮忙看下。 目前h5是没问题的, 小程序接入apollo 后,fetch请求的时候各种坑

@neoscript99 另外有相关的使用 apollo-link-http 这个相关demo吗? h5中调试ok,但是在小程序中发现有些异常,需要解决一下。 看下是怎么做兼容处理的?

@kdong007 请问下有没有 apollo-link-http 在小程序中使用的相关说明? 包括一些指令或者定制处理的,谢谢

@neoscript99 你确定你理解了graphql的核心么 graphql不是单纯resolver查询chain这些入门的东西 而是type based caching

多种resolver之间的互相cache 以及mutation之后相关的自动刷新之前query相关的UI组件等 这些才是graphql存在的意义

例如你有一个user下面有帖子posts 按照更新时间排序,每个Post有 content/lastUpdate

产品上假设有三页 个人题列表 / 帖子详细列表 / 修改帖子内容

修改帖子后由于帖子内容和更新时间的更新 导致帖子列表重新排序以及帖子详情刷新

你不绑定组件你怎么做跨页刷新? 全局事件么? 然后各种 on off 然后在event listerner遗漏中无限debug

而真正的graphql 是这样的
个人列表类似于这样

user{
  id
  post{
    id
    content
    lastUpdate
  }
}

帖子详情类似于这样

post{
   id
   content
   lastUpdate
   xxx 
   xxx
}

而修改页面mutation 是类似于这样的

mutation xxx{
  editPost(xxx){
    id
    content
    lastUpdate
    user{
      id
      posts{
        id
      }
    }
  }
}

一个mutation chain 获取所有更新后需要刷新的每个节点 cache自动更新 触发所有相关UI组件刷洗

apollo client官方的教程你可能需要多研究一下

跨页刷新那地方我不太理解,redux不是很简单可以做到吗,修改帖子内容后出发action改变store的状态,然后列表和详情会rerender更新。

@dpyzo0o redux是可以做到 但是你的工作量非常之大

apollo cache是个对graphql专用的redux

举例你有个数据User如下:

{
   id:xxx
   name:xxx
   age:xxx,
   address:{
      unitNumber:xxx
      streetName:xxx
      city:xxxx
   }
  meta:{
      groupA:{
         a1:xx
         a2:xxx
         a3:xxx
      }
      groupB:{
         b1:xx
         b2:xx
         b3:xx
      }
  }
}

你的产品在使用过程中会遇到User下的一个或者多个节点数据更新,比如可能是用户自己修改过的,也有可能是服务器上的控制的数据

假如你存一份你自己的User在redux中

首先你要下载自己的数据,那问题就是你第一次下载在哪里?主页启动?但如果你的程序中加入deep link 直接进入的分页,自己的数据还是空 你还要特殊处理

第二 每次更新数据后你都要同步到redux 你用什么方式同步?
更新数据后自己检查修改了哪些节点 然后各种 dispatch actionMyXXXUpdated()? 而且在你后期User数据节点变多的时候你会有无数个小的action
另一种做法 dispatch actionMyUserDataMayUpdate() 整个user丢进去? 那你所有跟User下节点connect的组件都可能出发刷新,你需要写一个非常聪明的merge以及connect的方式要非常小心

也就是说 graphql之后同步到redux的方式可以达到你要的目的,但是比较烦 难于管理

相反 假如你直接用react apollo绑定query, apollo cache知道你的这个组件下面需要具体哪些节点的数据,在别的其他query或者mutation时候,跟回来的数据(一般都是更新后的数据)会先进入apollo cache进行diff/merge,也就上面提到的工作,然后再检查现在的组件中哪些连接的数据节点有更新,然后通知他们刷新。



@kdong007 很详细的回答,谢谢~ apollo我还是刚接触,很多地方理解的还不够深入

Was this page helpful?
0 / 5 - 0 ratings