26、构建 Relay React 应用

构建 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 进行数据管理和交互。

本研究基于扩展卡尔曼滤波(EKF)方法,构建了一套用于航天器姿态与轨道协同控制的仿真系统。该系统采用参数化编程设计,具备清晰的逻辑结构和详细的代码注释,便于用户根据具体需求调整参数。所提供的案例数据可直接在MATLAB环境中运行,无需额外预处理步骤,适用于计算机科学、电子信息工程及数学等相关专业学生的课程设计、综合实践或毕业课题。 在航天工程实践中,精确的姿态与轨道控制是保障深空探测、卫星组网及空间设施建设等任务成功实施的基础。扩展卡尔曼滤波作为一种适用于非线性动态系统的状态估计算法,能够有效处理系统模型中的不确定性与测量噪声,因此在航天器耦合控制领域具有重要应用价值。本研究实现的系统通过模块化设计,支持用户针对不同航天器平台或任务场景进行灵活配置,例如卫星轨道维持、飞行器交会对接或地外天体定点着陆等控制问题。 为提升系统的易用性与教学适用性,代码中关键算法步骤均附有说明性注释,有助于用户理解滤波器的初始化、状态预测、观测更新等核心流程。同时,系统兼容多个MATLAB版本(包括2014a、2019b及2024b),可适应不同的软件环境。通过实际操作该仿真系统,学生不仅能够深化对航天动力学与控制理论的认识,还可培养工程编程能力与实际问题分析技能,为后续从事相关技术研究或工程开发奠定基础。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值