您的位置:首页 > Web前端 > React

Mutations #Facebook Relay文档翻译#

2015-09-25 15:46 671 查看
原文地址

上一篇 Ready State

Relay文档翻译目录

Up until this point we have only interacted with the GraphQL endpoint to perform queries that fetch data. In this guide, you will learn how to use Relay to perform mutations – operations that consist of writes to the data store followed by a fetch of any changed fields.

到目前为止,我们只是学习了GraphQL端,知道了如何查询并获取数据。在这篇文章中,你将学习到如何使用Relay去处理mutations,用于保持任何区域变动与写入data store之间的一致性。

A complete example

Before taking a deep dive into the mutations API, let’s look at a complete example. Here, we subclass
Relay.Mutation
to create a custom mutation that we can use to like a story.

在深入到mutations API之前,让我们看一个完整的例子。我们继承
Relay.Mutation
创建一个自己的mutation类,用于对喜欢的故事点赞。

class LikeStoryMutation extends Relay.Mutation {
// This method should return a GraphQL operation that represents
// the mutation to be performed. This presumes that the server
// implements a mutation type named ‘likeStory’.
// 该方法返回GraphQL的运行结果,代表着mutation的执行。前提是服务器端实现了一个名叫‘likeStory’的mutation type
getMutation() {
return Relay.QL`mutation {likeStory}`;
}
// Use this method to prepare the variables that will be used as
// input to the mutation. Our ‘likeStory’ mutation takes exactly
// one variable as input – the ID of the story to like.
// 该方法用返回一些变量,作为mutation的输入。这里的'likeStory' mutation只需要一个输入参数,就是要点赞的storey ID
getVariables() {
return {storyID: this.props.story.id};
}
// Use this method to design a ‘fat query’ – one that represents every
// field in your data model that could change as a result of this mutation.
// 该方法定义一个'fat query',用于表示你数据模型中每一个可能受该mutation变化影响的项
// Liking a story could affect the likers count, the sentence that
// summarizes who has liked a story, and the fact that the viewer likes the
// story or not.
// 对一个story点赞可能影响到点赞数、显示的句子和用户是否点赞的标示
// Relay will intersect this query with a ‘tracked query’
// that represents the data that your application actually uses, and
// instruct the server to include only those fields in its response.
// Relay会自动跟踪这些查询,恰当的选取查询的一部分,并且只向服务器请求刚好需要的数据。
getFatQuery() {
return Relay.QL`
fragment on LikeStoryPayload {
story {
likers {
count,
},
likeSentence,
viewerDoesLike,
},
}
`;
}
// These configurations advise Relay on how to handle the LikeStoryPayload
// returned by the server. Here, we tell Relay to use the payload to
// change the fields of a record it already has in the store. The
// key-value pairs of ‘fieldIDs’ associate field names in the payload
// with the ID of the record that we want updated.
// 这个配置告诉Relay如何处理服务器返回的LikeStoryPayLoad。这里,我们告诉Relay用这个payload去修改一条已经存在于store中的记录。
// 键值对中,键'fieldIDs'关联到playload中的field名字,通过ID确认是我们要更新的那条记录
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
story: this.props.story.id,
},
}];
}
// This mutation has a hard dependency on the story's ID. We specify this
// dependency declaratively here as a GraphQL query fragment. Relay will
// use this fragment to ensure that the story's ID is available wherever
// this mutation is used.
// 这个mutation是强依赖于story ID的。我们通过GraphQL query fragment显示的声明该依赖。
// Relay将通过该fragment确保mutation使用的story ID是有效的。
static fragments = {
story: () => Relay.QL`
fragment on Story {
id,
}
`,
};
}


Here’s an example of this mutation in use by a
LikeButton
component:

下面是一个
LikeButton
component使用了上述mutation的例子

class LikeButton extends React.Component {
_handleLike = () => {
// To perform a mutation, pass an instance of one to `Relay.Store.update`
Relay.Store.update(new LikeStoryMutation({story: this.props.story}));
}
render() {
return (
<div>
{this.props.story.viewerDoesLike
? 'You like this'
: <button onClick={this._handleLike}>Like this</button>
}
</div>
);
}
}

module.exports = Relay.createContainer(LikeButton, {
fragments: {
// You can compose a mutation's query fragments like you would those
// of any other RelayContainer. This ensures that the data depended
// upon by the mutation will be fetched and ready for use.
// 你可以像其他RelayContainer一样,构建mutation query fragments。这确保了依赖于该mutaion的数据会被及时的获取
story: () => Relay.QL`
fragment on Story {
viewerDoesLike,
${LikeStoryMutation.getFragment('story')},
}
`,
},
});


In this particular e
4000
xample, the only field that the
LikeButton
cares about is
viewerDoesLike
. That field will form part of the tracked query that Relay will intersect with the fat query of
LikeStoryMutation
to determine what fields to request as part of the server’s response payload for the mutation. Another component elsewhere in the application might be interested in the likers count, or the like sentence. Since those fields will automatically be added to Relay’s tracked query, the
LikeButton
need not worry about requesting them explicitly.

在这个例子中,
LikeButton
唯一关心的就是
viewerDoesLike
这个field。它是组成track query的一部分,Relay会用它与
LikeStoryMutation
的fat query取交集,来确定该mutation触发的服务器端返回哪些payload的field。

Mutation props

Any props that we pass to the constructor of a mutation will become available to its instance methods as
this.props
. Like in components used within Relay containers, props for which a corresponding fragment has been defined will be populated by Relay with query data:

任何创建mutation时构造器中传入的props,都将可以在实例的任意方法中通过
this.props
访问到。像component和Relay container的关系一样,props和定义的fragment之间用Relay query data来填充。

class LikeStoryMutation extends Relay.Mutation {
static fragments = {
story: () => Relay.QL`
fragment on Story {
id,
viewerDoesLike,
}
`,
};
getMutation() {
// Here, viewerDoesLike is guaranteed to be available.
// We can use it to make this mutation polymorphic.
// 这里 viewerDoesLike 被保证是有效的,我们可以使mutation有多态特性
return this.props.story.viewerDoesLike
? Relay.QL`mutation {unlikeStory}`
: Relay.QL`mutation {likeStory}`;
}
/* ... */
}


Fragment variables

Like can be done with Relay containers, we can prepare variables for use by our mutation’s fragment builders, based on the previous variables and the runtime environment.

与我们在 Relay containers中学到的一样,我们可以在mutation fragment中使用一些保存状态的变量,或是基于之前的或是运行时产生的。

class RentMovieMutation extends Relay.Mutation {
static initialVariables = {
format: 'hd',
lang: 'en-CA',
};
static prepareVariables = (prevVariables) => {
var overrideVariables = {};
if (navigator.language) {
overrideVariables.lang = navigator.language;
}
var formatPreference = localStorage.getItem('formatPreference');
if (formatPreference) {
overrideVariables.format = formatPreference;
}
return {...prevVariables, overrideVariables};
};
static fragments = {
// Now we can use the variables we've prepared to fetch movies
// appropriate for the viewer's locale and preferences
// 我们可以使用准备的变量为根据位置与偏好为用户获取电影数据
movie: () => Relay.QL`
fragment on Movie {
posterImage(lang: $lang) { url },
trailerVideo(format: $format, lang: $lang) { url },
}
`,
};
}


The fat query

Changing one thing in a system can have a ripple effect that causes other things to change in turn. Imagine a mutation that we can use to accept a friend request. This can have wide implications:

在系统中改变一个东西,可能会引起一连串的变化。想象一下一个用来接受加好友申请的变化,将对下面各项也产生影响:

both people’s friend count will increment

an edge representing the new friend will be added to the viewer’s
friends
connection

an edge representing the viewer will be added to the new friend’s
friends
connection

the viewer’s friendship status with the requester will change

两个用户的好友数都将增加

在接受申请的用户的朋友关系中将增加一位

在发起申请的用户的朋友关系中也增加一位

这两个用户之间的关系状态也随之变化

Design a fat query that covers every possible field that could change:

设计fat query用于每一个可能变化的field:

class AcceptFriendRequestMutation extends Relay.Mutation {
getFatQuery() {
// This presumes that the server-side implementation of this mutation
// returns a payload of type `AcceptFriendRequestPayload` that exposes
// `friendEdge`, `friendRequester`, and `viewer` fields.
// 这里假设该mutation的服务器端实现中可返回类型为`AcceptFriendRequestPayload`的payload,并且暴露了field:`friendEdge`, `friendRequester` 和 `viewer`。
return Relay.QL`
fragment on AcceptFriendRequestPayload {
friendEdge,
friendRequester {
friends,
friendshipStatusWithViewer,
},
viewer {
friends,
},
}
`;
}
}


This fat query looks like any other GraphQL query, with one important distinction. We know some of these fields to be non-scalar (like
friendEdge
and
friends
) but notice that we have not named any of their children by way of a subquery. In this way, we indicate to Relay that anything under those non-scalar fields may change as a result of this mutation.

Fat query与普通的GraphQL query长的很像,但是有一点重要的区别。我们知道有一些非标量的field(如friendEdge和friends),请注意我们没用subquery的形式声明他们的子节点。表示我们告诉Relay,这些非标量field下面的任何东西都将因该变动而改变。

Note

When designing a fat query, consider all of the data that might change as a result of the mutation – not just the data currently in use by your application. We don’t need to worry about overfetching; this query is never executed without first intersecting it with a ‘tracked query’ of the data our application actually needs. If we omit fields in the fat query, we might observe data inconsistencies in the future when we add views with new data dependencies, or add new data dependencies to existing views.

当我们去设计自己的fat query时,请认为所有的数据都将因该mutation而改变,不仅仅是你当前应用正在使用的这些。我们不必担心数据取多了。在fat query与tracked query取交集确定我们应用实际所需的数据之前,该查询是不会被执行的。如果我们在fat query中省略一些fields,我们在将来可能会看到不一致的现象,如当我们添加视图并带有新的数据依赖时,如给已经存在的视图添加新的数据依赖。

Mutator configuration

We need to give Relay instructions on how to use the response payload from each mutation to update the client-side store. We do this by configuring the mutation with one or more of the following mutation types:

我们需要给Relay指令,告诉它如何使用改变触发的返回payload去更新客户端store。我们通过下面的一些方式配置:

FIELDS_CHANGE

Any field in the payload that can be correlated by DataID with one or more records in the client-side store will be merged with the record(s) in the store.

任何playload中的filed对应DataID的客户端store中的记录,将与store中的记录合并。

Arguments

fieldIDs: {[fieldName: string]: DataID | Array<DataID>}


A map between a
fieldName
in the response and one or more DataIDs in the store.

返回的fieldName与store中一个或多个DataID的映射。

Example

class RenameDocumentMutation extends Relay.Mutation {
// This mutation declares a dependency on a document's ID
// 这个mutation声明了一个与文档ID的依赖
static fragments = {
document: () => Relay.QL`fragment on Document { id }`,
};
// We know that only the document's name can change as a result
// of this mutation, and specify it here in the fat query.
// 这个改动只能改变文档的name,在fat query中明确指出。
getFatQuery() {
return Relay.QL`
fragment on RenameDocumentMutationPayload { updatedDocument { name } }
`;
}
getVariables() {
return {id: this.props.document.id, newName: this.props.newName};
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
// Correlate the `updatedDocument` field in the response
// with the DataID of the record we would like updated.
// 将返回中updatedDocument field与我们想要更新的记录 DataID关联
fieldIDs: {updatedDocument: this.props.document.id},
}];
}
/* ... */
}


NODE_DELETE

Given a parent, a connection, and one or more DataIDs in the response payload, Relay will remove the node(s) from the connection and delete the associated record(s) from the store.

在返回的payload中给出一个parent,一个connection和 一个或多个DataID,Relay将删除connection中的node,删除store中关联的记录。

Arguments

parentName: string


The field name in the response that represents the parent of the connection

返回中的field name表示connection的parent。

parentID: string


The DataID of the parent node that contains the connection

包含connection的parent node的DataID

connectionName: string


The field name in the response that represents the connection

返回中的field name表示connection

deletedIDFieldName: DataID | Array<DataID>


One or more DataIDs corresponding to nodes to remove from the connection and delete from the store

从connection和store中删除一个或多个DataIDs对应的node。

Example

class DestroyShipMutation extends Relay.Mutation {
// This mutation declares a dependency on an enemy ship's ID
// and the ID of the faction that ship belongs to.
static fragments = {
ship: () => Relay.QL`fragment on Ship { id, faction { id } }`,
};
// Destroying a ship will remove it from a faction's fleet, so we
// specify the faction's ships connection as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on DestroyShipMutationPayload {
destroyedShipID,
faction { ships },
}
`;
}
getConfigs() {
return [{
type: 'NODE_DELETE',
parentName: 'faction',
parentID: this.props.ship.faction.id,
connectionName: 'ships',
deletedIDFieldName: 'destroyedShipID',
}];
}
/* ... */
}


RANGE_ADD

Given a parent, a connection, and the name of the newly created edge in the response payload Relay will add the node to the store and attach it to the connection according to the range behavior specified.

在返回的payload中给出一个parent,一个connection和一个新创建的edge的name,Relay将把该node加入到store中,根据range的行为说明将它加到connection上

Arguments

parentName: string


The field name in the response that represents the parent of the connection

parentID: string


The DataID of the parent node that contains the connection

connectionName: string


The field name in the response that represents the connection

edgeName: string


The field name in the response that represents the newly created edge

rangeBehaviors: {[call: string]: GraphQLMutatorConstants.RANGE_OPERATIONS}


A map of GraphQL calls to the behavior we want Relay to exhibit when adding the new edge to connections under the influence of those calls. Behaviors can be one of
'append'
,
'prepend'
, or
'remove'
.

Example

class IntroduceShipMutation extends Relay.Mutation {
// This mutation declares a dependency on the faction
// into which this ship is to be introduced.
static fragments = {
faction: () => Relay.QL`fragment on Faction { id }`,
};
// Introducing a ship will add it to a faction's fleet, so we
// specify the faction's ships connection as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on IntroduceShipPayload {
faction { ships },
newShipEdge,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'faction',
parentID: this.props.faction.id,
connectionName: 'ships',
edgeName: 'newShipEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
'orderby(newest)': 'prepend',
},
}];
}
/* ... */
}


RANGE_DELETE

Given a parent, a connection, one or more DataIDs in the response payload, and a path between the parent and the connection, Relay will remove the node(s) from the connection but leave the associated record(s) in the store.

给出返回payload中的一个parent,一个connection和一个或多个DataID,以及parent和connection之间的一个path,Relay将从connection中删除node,但是会保留store中相关的record。

Arguments

parentName: string


The field name in the response that represents the parent of the connection

parentID: string


The DataID of the parent node that contains the connection

connectionName: string


The field name in the response that represents the connection

deletedIDFieldName: DataID | Array<DataID>


One or more DataIDs corresponding to nodes to remove from the connection

pathToConnection: Array<string>


Any array containing the field names between the parent and the connection, including the parent and the connection

Example

class RemoveTagMutation extends Relay.Mutation {
// This mutation declares a dependency on the
// todo from which this tag is being removed.
static fragments = {
todo: () => Relay.QL`fragment on Todo { id }`,
};
// Removing a tag from a todo will affect its tags connection
// so we specify it here as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on RemoveTagMutationPayload {
todo { tags },
removedTagIDs,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_DELETE',
parentName: 'todo',
parentID: this.props.todo.id,
connectionName: 'tags',
deletedIDFieldName: 'removedTagIDs',
}];
}
/* ... */
}


Optimistic updates

All of the mutations we’ve performed so far have waited on a response from the server before updating the client-side store. Relay offers us a chance to craft an optimistic response of the same shape based on what we expect the server’s response to be in the event of a successful mutation.

我们之前使用的mutations都是等服务器端返回了才去更新客户端的store。Relay给我们提供了机制,可以提前模拟一个假装服务器端已经返回所需数据的返回。

Let’s craft an optimistic response for the
LikeStoryMutation
example above:

我们以
LikeStoryMutation
来说明它,目的是用户点了赞之后,马上能看到效果,之后服务器端返回了,再同步上,如果没同步上,可能面临界面回滚。

class LikeStoryMutation extends Relay.Mutation {
/* ... */
// Here's the fat query from before
getFatQuery() {
return Relay.QL`
fragment on LikeStoryPayload {
story {
likers {
count,
},
likeSentence,
viewerDoesLike,
},
}
`;
}
// Let's craft an optimistic response that mimics the shape of the
// LikeStoryPayload, as well as the values we expect to receive.
getOptimisticResponse() {
return {
story: {
id: this.props.story.id,
likers: {
count: this.props.story.likers.count + (this.props.story.viewerDoesLike ? -1 : 1),
},
viewerDoesLike: !this.props.story.viewerDoesLike,
},
};
}
// To be able to increment the likers count, and flip the viewerDoesLike
// bit, we need to ensure that those pieces of data will be available to
// this mutation, in addition to the ID of the story.
static fragments = {
story: () => Relay.QL`
fragment on Story {
id,
likers { count },
viewerDoesLike,
}
`,
};
/* ... */
}


You don’t have to mimic the entire response payload. Here, we’ve punted on the like sentence, since it’s difficult to localize on the client side. When the server responds, Relay will treat its payload as the source of truth, but in the meantime, the optimistic response will be applied right away, allowing the people who use our product to enjoy instant feedback after having taken an action.

你并不用去模拟全部的返回payload。这里的like sentence就是不那么准的,因为很难在客户端确定。当服务器端返回时,Relay会以返回结果为标准。但是我们知道optimistic response的好处是立马生效,增强了用户体验。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息