一些 React-Relay 探索中的 Debug经验

#1

思考路线:

fragment on NameOfRelationshipListType { } 里的层级关系和名字是 NameOfRelationshipListType 的 GraphQLObjectType 里定义的层级关系是否一致。做法是把 ${xxx.getFragment(‘xxxx’)} 都用 graphQL 展开,看看这个 json 里的每一项是否和 schema 里定义的每一项层次相对应。

检查 schema 里定义了的数据到底有没有暴露给前端,还有前端是不是只顾着写怎么使用数据,却忘了定义怎么获取数据。做法是从 export var Schema = new GraphQLSchema 这儿的根请求里的 field 开始检查,自己有没有把所需的数据端点暴露出来。还有在 routes 里的 export default class extends Relay.Route 里有没有和根请求对接好。

看看数据库返回的 json 的格式是不是 schema 里定义的一模一样,没有多一层 {data: whatSchemaReallyWants } 这样的 key: object ,也没有少一个 key: value 对。做法是给向数据库请求数据的部分写单元测试。

检查想请求的数据所用到的变量名和 schema 里定义的变量名是不是一致。做法是先吃点提神的中药,然后关闭搜索里的大小写精确匹配,然后搜索你想请求的数据所用的变量名,用肉眼看看是不是拼错了、大小写用错了。

检查自己是不是压力太大了,导致认知能力下降,如果是就休息一段时间。做法是找个朋友扯扯淡,约个异性出去玩,几项工作轮轮换,找家餐馆吃个饭。

首先最浅显的一点,schema 里 solve 的都得是返回 Promise 的函数,从Facebook 用 solve 这个词我们就能看出,Relay 是天生得和 Promise 搭对的。
事实上,不用 Promise 啥数据都获取不到。

export default Relay.createContainer(App, {
  fragments: {
    plantList: () => Relay.QL`
        fragment on NameOfPlantListType {
            ${Features.getFragment('plantList')},
        }
    `,
  },
});

报错:

-- GraphQL Validation Error -- App.js --

Error: Unknown type "NameOfPlantListType".
File:  /home/onetwo/plantRecg/js/components/App.js
Source:
>
> fragment App on NameOfPlantListType {
>     

把 fragment on NameOfPlantListType 修改为:

fragment on Query {
     ${Features.getFragment('plantList')},

就好了,因为 schema 结构库里是这样定义的:

var queryType = new GraphQLObjectType({
 name: 'Query',
  fields: () => ({
    node: nodeField,
    // Add your own root fields here
    plantList: {
      type: PlantListType,
      resolve: () => getPlantList(),
    },

plantList 是在名为Query的类型的 field 里,所以使用的时候必须要注意这些对应关系,报错说 Unknown type 其实就是层次写错了,数据请求和端点定义没有对齐,解析 graphQL 的时候找不到对象而已。

还有 relay 只允许

  fragments: {
    plantList: () => Relay.QL`
      fragment on NameOfPlantListType {
        plantArray {
            ${ButtonCloud.getFragment('aPlant')},
        },
    }
    `,
  },

而不能

  fragments: {
    plantList: () => Relay.QL`
      fragment on NameOfPlantListType {
        plantArray {
            fragment on NameOfPlantType {
                uuid,
                chineseName,
            },
        },
    }
    `,
  },

传入数组的话,一般是用子组件来接收,然后用 ${xxx.getFragment(‘oooo’)} 。
不过也可以

  fragments: {
    plantList: () => Relay.QL`
      fragment on NameOfPlantListType {
        plantArray {
                uuid,
                chineseName,
        },
    }
    `,
  },

mutation 记得数据一定要有特异的 id ,之前我数据上传都搞定了,就是没法自动用新提交的数据更新视图,后来发现原来是数据库操作那边,没有给 relay 返回一个特异的 id ,返回的是和另一个 mutation 相同的 id,所以不报错,但是就是功能无法实现。

function getPlant() {
    return runCypher({
        query: 'MATCH (p:PLANT) RETURN p.uuid AS id, p.chineseName AS text',
    }).then(results => {
        return new Promise(function (resolve, reject) {
            let forPlant = {
                plantArray: [],
                id: '42',
            }
            if(results) {
                forPlant.plantArray = results;
                resolve(forPlant);
            }
            resolve(forPlant);
        })
    })
}
function getFeature() {
    return runCypher({
        query: 'MATCH (p:FEATURE) RETURN p.uuid AS id, p.chineseName AS text',
    }).then(results => {
        return new Promise(function (resolve, reject) {
            let forFeature = {
                featureArray: [],
                id: 42',
            }

看上面两个数据的 id 是一样的,所以能向数据库添加数据,但是返回的东西不被 relay 接受,所以不自动更新视图。

mutation 中可能出现的 bug 在于它 field 比较多,比较容易记混,或出现拼写错误。

比如

Error: Cannot query field "relationshipListFromMutationOutputFields" on "NameOfMutationOfLetPlantArrayHasFeatureasdfasdfPayload".
File:  /home/onetwo/Relay-Neo4j-example/js/components/TaoTao.js
Source:
>
>                       relationshipListFromMutationOutputFields { relationshipArray },
>    ^^^

说是不能在 “relationshipListFromMutationOutputFields” 这个 field 里请求,其实隐含的意思一般是你拼写错误了。

var MutationOfLetPlantArrayHasFeature = mutationWithClientMutationId({
    name: 'NameOfMutationOfLetPlantArrayHasFeatureasdfasdf',
    inputFields: {
        plantUUIDArray: { type: new GraphQLList(GraphQLString) },
        featureUUID: { type: new GraphQLNonNull(GraphQLString) },
    },
    outputFields: {
        relatiomshipListFromMutationOutputFields: {
            type: RelationshipListType,
            resolve: getRelationShip,
        },
    },

里是 relatiomshipListFromMutationOutputFields

而 React-Relay 前端那边写的是 relationshipListFromMutationOutputFields
Relay 找不到就说不让你查询,其实真正错误是打错了字。

    getFatQuery() { // 下面这东西on 的Object,居然就是 schema 里定义的Mutation的名字后面加个Payload
    return Relay.QL`
        fragment on NameOfMutationOfLetPlantArrayHasFeatureasdfasdfPayload {
            relationshipListFromMutationOutputFields { relationshipArray },
        }
    `;
POST http://test.poselevel.com/graphql 400 (Bad Request)
blabla 。。。
fetchWithRetries(): Still no successful response after 3 retries, giving up.

则一般是 React-Relay 前端那边的请求和 schema 里的层级结构没有对应好。
没对应好它不说你没对应好,而说请求错误 400 ,让你感觉事情很严重。虽然这问题的确是出在请求构造错误上,但只要把 fragment 用 graphQL 语法展开来自己看看层级结构,一般就 OK 了。

比如有一个mutation 在 schema 里定义有

    outputFields: {
        relationshipListFromMutationOutputFields: {
            type: RelationshipListType,
            resolve: getRelationShip,
        },
    },

那么如果想当然地按照以前的经验,或是看别人的代码(比如说我自己以前写的教程)都把 mutation 的请求写在外头,自己也写在外头

            fragment on NameOfTaoTaoType {
                forRelationship {
                    ${RelationshipPanel.getFragment('relationshipFromRelay')},
                },
                ${LetPlantArrayHasFeatureMutation.getFragment('relationshipListWithID')},
            }

那就会出错,因为把它用 graplQL 语法展开分析,其实是

fragment on NameOfTaoTaoType {
     forRelationship {   
          ...relationshipFromRelay,
     }
     ...relationshipListWithID
  }

  fragment relationshipFromRelay on NameOfRelationshipListType{
            relationshipArray {
                plant {
                    id,
                    text,
                },
                id,
                feature {
                    id,
                    text,
                },
            },
  }

  fragment relationshipListWithID on NameOfRelationshipListType{
            relationshipArray {
                plant {
                    id,
                    text,
                },
                id,
                feature {
                    id,
                    text,
                },
            },
  }

然而仔细想想,schema 里定义的 NameOfTaoTaoType 其实是

var TaoTaoType = new GraphQLObjectType({
    name: 'NameOfTaoTaoType',
    fields: () => ({
        forPlant: {type: PlantListType},
        forFeature: {type: FeatureListType},
        forRelationship: {type: RelationshipListType},
    }),
});

如果上面那个写法是对的话,schema 里应该是

var TaoTaoType = new GraphQLObjectType({
    name: 'NameOfTaoTaoType',
    fields: () => ({
        forPlant: {type: PlantListType},
        forFeature: {type: FeatureListType},
        forRelationship: {type: RelationshipListType},
        relationshipArray: {type: plantIdFeatureType},
    }),
});

其中 plantIdFeatureType 得包含了

                plant {
                    id,
                    text,
                },
                id,
                feature {
                    id,
                    text,
                },

的定义才行,然而并不。

所以我们得把请求修改成

        guanHaiTingTao: () => Relay.QL`
            fragment on NameOfTaoTaoType {
                forRelationship {
                    ${RelationshipPanel.getFragment('relationshipFromRelay')},
                    ${LetPlantArrayHasFeatureMutation.getFragment('relationshipListWithID')},
                },
            }

哎,这就对了,再也不会 400 。

Warning: RelayMutation: Expected data for fragment relationshipListWithID to be supplied to LetPlantArrayHasFeatureMutation as a prop. Pass an explicit null if this is intentional.

说明 relationshipListWithID 请求失败了,这个变量现在是 undefined 。把它赋值给 LetPlantArrayHasFeatureMutation 就会这样报错。
为什么它是 undefined 呢?既然不是报错 400 就说明请求和数据端点是对齐了的,如果确定数据库获取数据的时候也没有问题,就很可能是因为 RelayRoute 请求配置这边没有定义关于这个请求的内容,甚至在 schema 那边的 root field 里也忘了暴露出这个数据端点。

先这样吧,国内用 Relay 的人也不多,不过我也先发上来以警后人,Relay 的错误提示极度不友好,恍若神谕,又似喜怒无常的君王,如果不了解 graphQL 等基础,贸然 debug ,就会不知所措,被水淹没。

1 Like