构建 Relay React 应用
1. 引言
本文将引导你构建一个基于 Relay 和 GraphQL 的 React Native 待办事项应用。通过这个示例,你将了解如何在 GraphQL 架构中处理数据,以及如何在 React Native 应用中使用 Relay 进行数据查询和变更。
2. TodoMVC 与 Relay
原本计划扩展之前的 Neckbeard News 应用,但最终选择了 Relay 的 TodoMVC 示例(https://github.com/relayjs/relay-examples),因为它简洁且功能强大。我们将实现一个 React Native 版本的待办事项应用,该应用将使用与 Web UI 相同的 GraphQL 后端,这意味着 Web 和原生应用可以共享相同的架构。
3. GraphQL 架构
3.1 架构概述
GraphQL 架构是 GraphQL 后端服务器和前端 Relay 组件使用的词汇表。GraphQL 类型系统允许架构描述可用的数据,以及在查询请求到来时如何将数据组合在一起。这使得整个方法具有可扩展性,因为 GraphQL 运行时会处理数据的组合,我们只需提供告诉 GraphQL 数据位置的函数,例如数据库或远程服务端点。
3.2 架构代码示例
import {
GraphQLBoolean,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
cursorForObjectInConnection,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
nodeDefinitions,
toGlobalId,
} from 'graphql-relay';
import {
Todo,
User,
addTodo,
changeTodoStatus,
getTodo,
getTodos,
getUser,
getViewer,
markAllTodos,
removeCompletedTodos,
removeTodo,
renameTodo,
} from './database';
const { nodeInterface, nodeField } = nodeDefinitions(
(globalId) => {
const { type, id } = fromGlobalId(globalId);
if (type === 'Todo') {
return getTodo(id);
} else if (type === 'User') {
return getUser(id);
}
return null;
},
(obj) => {
if (obj instanceof Todo) {
return GraphQLTodo;
} else if (obj instanceof User) {
return GraphQLUser;
}
return null;
}
);
const GraphQLTodo = new GraphQLObjectType({
name: 'Todo',
fields: {
id: globalIdField('Todo'),
text: {
type: GraphQLString,
resolve: ({ text }) => text,
},
complete: {
type: GraphQLBoolean,
resolve: ({ complete }) => complete,
},
},
interfaces: [nodeInterface],
});
const {
connectionType: TodosConnection,
edgeType: GraphQLTodoEdge,
} = connectionDefinitions({
name: 'Todo',
nodeType: GraphQLTodo,
});
const GraphQLUser = new GraphQLObjectType({
name: 'User',
fields: {
id: globalIdField('User'),
todos: {
type: TodosConnection,
args: {
status: {
type: GraphQLString,
defaultValue: 'any',
},
...connectionArgs,
},
resolve: (obj, { status, ...args }) =>
connectionFromArray(getTodos(status), args),
},
totalCount: {
type: GraphQLInt,
resolve: () => getTodos().length,
},
completedCount: {
type: GraphQLInt,
resolve: () => getTodos('completed').length,
},
},
interfaces: [nodeInterface],
});
const Root = new GraphQLObjectType({
name: 'Root',
fields: {
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
node: nodeField,
},
});
3.3 代码解释
-
导入部分
:从
graphql库导入基本的 GraphQL 类型,从graphql-relay库导入一些辅助函数,以及从自定义的数据库模块导入相关数据和操作函数。 -
自定义 GraphQL 类型
:定义了
GraphQLTodo、GraphQLUser和Root类型。每个类型都有相应的字段和解析函数,用于告诉 GraphQL 运行时如何获取数据。 - 添加待办事项的变更 :以下是添加待办事项的变更代码示例:
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({ localTodoId }) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(
getTodos(),
Todo
),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({ text }) => {
const localTodoId = addTodo(text);
return { localTodoId };
},
});
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addTodo: GraphQLAddTodoMutation,
...
},
});
export const schema = new GraphQLSchema({
query: Root,
mutation: Mutation,
});
3.4 变更解释
所有变更都有一个
mutateAndGetPayload()
方法,用于实际调用外部服务来更改数据。返回的有效负载可以是更改的实体,也可以包含作为副作用更改的数据。
outputFields
用于将信息返回给 Relay,以便 Relay 根据变更的副作用正确更新组件。
4. 启动 Relay
4.1 配置 Relay
在 React Native 应用中,我们需要告诉 Relay 在哪里找到 GraphQL 后端。以下是入口文件的代码示例:
import React from 'react';
import { AppRegistry } from 'react-native';
import Relay, {
DefaultNetworkLayer,
RootContainer,
} from 'react-relay';
import viewerQueries from './queries/ViewerQueries';
import TodoApp from './TodoApp';
Relay.injectNetworkLayer(
new DefaultNetworkLayer('http://localhost:8080')
);
AppRegistry.registerComponent(
'TodoRelayMobile',
() => () => (
<RootContainer
Component={TodoApp}
route={{
name: 'viewer',
params: {},
queries: viewerQueries,
}}
/>
)
);
4.2 查看器查询模块
import Relay from 'react-relay';
export default {
viewer: () => Relay.QL`query { viewer }`,
};
这意味着如果
TodoApp
组件需要数据,其父组件知道
viewer
查询。这个查询包含了应用可用的所有数据,组件所需的任何数据都可以在这个查询中找到。
5. 添加待办事项
5.1 待办事项应用组件
import React, { Component, PropTypes } from 'react';
import {
View,
TextInput,
} from 'react-native';
import Relay from 'react-relay';
import styles from './styles';
import AddTodoMutation from './mutations/AddTodoMutation';
import TodoList from './TodoList';
export class TodoRelayMobile extends Component {
static propTypes = {
viewer: PropTypes.any.isRequired,
relay: PropTypes.shape({
commitUpdate: PropTypes.func.isRequired,
}),
}
state = {
text: '',
}
onSubmitEditing = ({ nativeEvent: { text } }) => {
this.props.relay.commitUpdate(
new AddTodoMutation({
text,
viewer: this.props.viewer,
})
);
this.setState({ text: '' });
}
onChangeText = text => this.setState({ text })
render() {
return (
<View style={styles.container}>
<TextInput
style={styles.textInput}
placeholder="What needs to be done?"
onSubmitEditing={this.onSubmitEditing}
onChangeText={this.onChangeText}
value={this.state.text}
/>
<TodoList viewer={this.props.viewer} />
</View>
);
}
}
export default Relay.createContainer(TodoRelayMobile, {
fragments: {
viewer: variables => Relay.QL`
fragment on User {
totalCount,
${AddTodoMutation.getFragment('viewer')},
${TodoList.getFragment('viewer', ...variables)},
}
`,
},
});
5.2 代码解释
-
状态管理
:通过
state管理用户输入的待办事项文本。 -
提交编辑事件
:当用户按下回车键提交待办事项时,调用
AddTodoMutation发送变更请求到 GraphQL 后端,并清空输入框。 -
Relay 容器
:将
TodoRelayMobile组件转换为 Relay 容器,声明了数据依赖,包括AddTodoMutation和TodoList的数据片段。
6. 渲染待办事项列表
6.1 待办事项列表组件
import React, { PropTypes } from 'react';
import Relay from 'react-relay';
import { View } from 'react-native';
import Todo from './Todo';
const TodoList = ({ viewer }) => (
<View>
{viewer.todos.edges.map(edge => (
<Todo
key={edge.node.id}
todo={edge.node}
viewer={viewer}
/>
))}
</View>
);
TodoList.propTypes = {
viewer: PropTypes.any.isRequired,
};
export default Relay.createContainer(TodoList, {
initialVariables: {
status: null,
},
prepareVariables() {
return {
status: 'any',
};
},
fragments: {
viewer: () => Relay.QL`
fragment on User {
todos(
status: $status,
first: 2147483647 # max GraphQLInt
) {
edges {
node {
id,
${Todo.getFragment('todo')},
},
},
},
${Todo.getFragment('viewer')},
}
`,
},
});
6.2 代码解释
-
渲染待办事项
:通过
viewer.todos.edges遍历待办事项列表,并为每个待办事项渲染Todo组件。 -
Relay 容器
:声明了初始变量和查询变量,以及所需的数据片段。通过
fragments属性编写 GraphQL 查询,告诉后端需要哪些数据。
以下是整个流程的 mermaid 流程图:
graph LR
A[启动应用] --> B[配置 Relay]
B --> C[查询数据]
C --> D[渲染界面]
D --> E[用户输入待办事项]
E --> F[提交变更请求]
F --> G[更新数据]
G --> D[重新渲染界面]
通过以上步骤,我们完成了一个基本的 Relay React Native 待办事项应用的构建,包括 GraphQL 架构的定义、Relay 的启动、待办事项的添加和列表的渲染。在后续部分,我们将继续介绍如何完成待办事项的标记和状态变更。
7. 完成待办事项
7.1 待办事项组件
import React, { Component, PropTypes } from 'react';
import Relay from 'react-relay';
import {
Text,
View,
Switch,
} from 'react-native';
import styles from './styles';
import ChangeTodoStatusMutation from
'./mutations/ChangeTodoStatusMutation';
const completeStyleMap = new Map([
[true, { textDecorationLine: 'line-through' }],
[false, {}],
]);
class Todo extends Component {
static propTypes = {
relay: PropTypes.any.isRequired,
viewer: PropTypes.any.isRequired,
todo: PropTypes.shape({
text: PropTypes.string.isRequired,
complete: PropTypes.bool.isRequired,
}),
}
onValueChange = complete =>
this.props.relay.commitUpdate(
new ChangeTodoStatusMutation({
complete,
todo: this.props.todo,
viewer: this.props.viewer,
})
)
render() {
const {
props: {
todo: {
text,
complete,
},
},
onValueChange,
} = this;
return (
<View style={styles.todoItem}>
<Switch
value={complete}
onValueChange={onValueChange}
/>
<Text style={completeStyleMap.get(complete)}>
{text}
</Text>
</View>
);
}
}
export default Relay.createContainer(Todo, {
fragments: {
todo: () => Relay.QL`
fragment on Todo {
complete,
id,
text,
${ChangeTodoStatusMutation.getFragment('todo')},
}
`,
viewer: () => Relay.QL`
fragment on User {
${ChangeTodoStatusMutation.getFragment('viewer')},
}
`,
},
});
7.2 代码解释
-
状态切换
:使用
Switch组件让用户切换待办事项的完成状态。当用户切换开关时,调用ChangeTodoStatusMutation发送变更请求到 GraphQL 后端。 -
样式处理
:根据待办事项的完成状态,使用
completeStyleMap为文本添加不同的样式,已完成的待办事项文本会显示为删除线。 -
Relay 容器
:声明了
todo和viewer的数据片段,这些片段在TodoList组件的todos查询中使用,确保组件的数据依赖得到满足。
8. 总结
8.1 核心要点回顾
-
GraphQL 架构
:定义了应用使用的数据类型和查询方式,通过
resolve函数告诉 GraphQL 运行时如何获取数据。 -
Relay 集成
:在 React Native 应用中启动 Relay,通过
RootContainer和查询模块建立数据连接。 - 待办事项操作 :实现了待办事项的添加、列表渲染和状态变更功能,通过 Relay 容器声明数据依赖,使用变更操作更新数据。
8.2 优势总结
该应用使用与 Web 版本相同的 GraphQL 架构,使得开发 Web 和原生 React 应用更加便捷。同时,Relay 帮助我们管理数据依赖和更新,提高了开发效率和代码的可维护性。
以下是各组件及其功能的表格总结:
| 组件名称 | 功能 |
| — | — |
|
TodoRelayMobile
| 包含输入框,用于添加待办事项,通过
AddTodoMutation
发送变更请求 |
|
TodoList
| 渲染待办事项列表,通过
viewer.todos.edges
遍历列表 |
|
Todo
| 渲染单个待办事项,提供状态切换功能,通过
ChangeTodoStatusMutation
变更状态 |
8.3 未来展望
React 作为一种渲染抽象,随着新的渲染目标和处理大规模状态的技术不断涌现,将会有更多的 React 库和技术出现。开发者可以基于此应用进一步扩展功能,如添加更多的待办事项操作、优化界面设计等,以适应不同的业务需求。
通过以上步骤,我们完成了一个完整的 Relay React Native 待办事项应用的构建,涵盖了从 GraphQL 架构设计到组件实现的各个方面。希望这个示例能帮助你更好地理解如何在 React Native 应用中使用 Relay 和 GraphQL 进行数据管理和交互。
超级会员免费看
348

被折叠的 条评论
为什么被折叠?



