前端工程师号称魔术师

In a recent post we explored how RethinkDB’s built-in reactivity is a perfect fit to write a chat app with Socket.io. In this article you will learn how to use GraphQL subscriptions instead, to access RethinkDB’s reactive nature in the frontend.

在最近的一篇文章中,我们探讨了RethinkDB的内置React性非常适合与Socket.io编写聊天应用程序。 在本文中,您将学习如何改为使用GraphQL订阅,以在前端访问RethinkDB的React性。

RethinkDB is a realtime document database. It is easy to use and schema-less, just like MongoDB. In addition, you can subscribe to queries and get notified when data changes, making it the perfect choice for realtime applications.

RethinkDB是一个实时文档数据库。 就像MongoDB一样,它易于使用且没有架构。 此外,您可以订阅查询并在数据更改时得到通知,这使其成为实时应用程序的理想选择。

You can also try the running app, or check out the code repository.

您也可以尝试运行的应用程序 ,或签出代码存储库 。

应用程序设置 (Application setup)

We will build a Node.js app, so you need to have node and npm installed. If you want to deploy your app to Heroku, you will also need a Heroku account, as well having their CLI installed. To run your app locally, you need to install and run a RethinkDB instance.

我们将构建一个Node.js应用程序,因此您需要安装nodenpm 。 如果要将应用程序部署到Heroku,则还需要一个Heroku帐户 ,并安装其CLI 。 要在本地运行您的应用,您需要安装并运行RethinkDB实例 。

We will use a simple Node.js server and a Vue.js frontend. Since the frontend needs to be build, we will create a Vue app with the Vue CLI:

我们将使用一个简单的Node.js服务器和一个Vue.js前端。 由于需要构建前端,因此我们将使用Vue CLI创建一个Vue应用程序:

$ vue create -d rethink-chat$ cd rethink-chat

This will create a Node project, create a Vue.js skeleton, and initialize a git repository.

这将创建一个Node项目,创建一个Vue.js框架,并初始化一个git仓库。

准备一个Heroku应用 (Prepare a Heroku app)

In order to deploy the application to Heroku we need to create a Heroku app:

为了将应用程序部署到Heroku,我们需要创建一个Heroku应用程序:

$ heroku create

We will also need a RethinkDB instance to store and subscribe to the chat messages sent between users. You can do this via the RethinkDB Cloud add-on as follows:

我们还将需要一个RethinkDB实例来存储和订阅用户之间发送的聊天消息。 您可以通过RethinkDB Cloud插件执行以下操作:

$ heroku addons:create rethinkdb

The RethinkDB Cloud add-on is currently in alpha. Request an invite for your Heroku account email.

RethinkDB Cloud附加组件当前处于Alpha状态。 请求邀请您的Heroku帐户电子邮件 。

构建服务器 (Building the server)

We will create our server in the server directory. So to start, lets create the directory and install the required dependencies:

我们将在server目录中创建server 。 首先,让我们创建目录并安装所需的依赖项:

$ mkdir server$ npm install rethinkdb apollo-server-express graphql morgan lorem-ipsum

Now, let us set up the Node.js server. Create an index.js file and add the following server skeleton. We use an Express.js server to serve the frontend and the Apollo GraphQL server to access and subscribe to chat messages.

现在,让我们设置Node.js服务器。 创建一个index.js文件,并添加以下服务器框架。 我们使用Express.js服务器来服务前端,使用Apollo GraphQL服务器来访问和订阅聊天消息。

// server/index.js// Setup Express server
const express = require("express");
const app = express();
const http = require("http").createServer(app);// Logging middleware
var morgan = require("morgan");
app.use(morgan("combined"));// Serve frontend
app.use(express.static("dist"));// Lazy RethinkDB connection
// ...// Setup Apollo (GraphQL) server
// ...// HTTP server (start listening)
const listenPort = process.env.PORT || "3000";
http.listen(listenPort, () => {console.log("listening on *:" + listenPort);
});

This skeleton serves a static frontend from the dist folder. This is where the compiled Vue.js app is located which we will create later. In addition our server needs to do three things:

此框架从dist文件夹提供静态前端。 这是编译的Vue.js应用程序所在的位置,我们稍后将创建它。 另外,我们的服务器需要做三件事:

  1. Handle connections to the RethinkDB database处理与RethinkDB数据库的连接
  2. Setup the Apollo server设置Apollo服务器
  3. Create a GraphQL schema including type definitions and resolvers创建一个包括类型定义和解析器的GraphQL模式

RethinkDB连接 (RethinkDB connection)

We manage our RethinkDB connection lazily, i.e., we only create the (re-)connection when it is actually needed. The connection parameters are parsed from environment variables, or the defaults are used.

我们懒惰地管理我们的RethinkDB连接,即,仅在实际需要时才创建(重新)连接。 连接参数是从环境变量解析的,或者使用默认值。

// server/index.js
// ...// Lazy RethinkDB connection
var r = require("rethinkdb");
let rdbConn = null;
const rdbConnect = async function () {try {const conn = await r.connect({host: process.env.RETHINKDB_HOST || "localhost",port: process.env.RETHINKDB_PORT || 28015,username: process.env.RETHINKDB_USERNAME || "admin",password: process.env.RETHINKDB_PASSWORD || "",db: process.env.RETHINKDB_NAME || "test",});// Handle closeconn.on("close", function (e) {console.log("RDB connection closed: ", e);rdbConn = null;});console.log("Connected to RethinkDB");rdbConn = conn;return conn;} catch (err) {throw err;}
};
const getRethinkDB = async function () {if (rdbConn != null) {return rdbConn;}return await rdbConnect();
};

On Heroku, the RethinkDB Cloud add-on will set the environment variables. For a locally running instance of RethinkDB, the defaults should work.

在Heroku上,RethinkDB Cloud插件将设置环境变量。 对于本地运行的RethinkDB实例,默认值应该起作用。

Apollo GraphQL服务器设置 (Apollo GraphQL server setup)

As mentioned earlier, the frontend is static. We do however need to access the data in a chat room. This will be handled by Apollo, the most used GraphQL server.

如前所述,前端是静态的。 但是,我们确实需要在聊天室中访问数据。 这将由最常用的GraphQL服务器Apollo处理。

// server/index.js
// ...// Setup Apollo (GraphQL) server
const { ApolloServer } = require("apollo-server-express");
const { typeDefs, resolvers } = require("./schema.js");
const graphqlServer = new ApolloServer({typeDefs,resolvers,context: async (arg) => {const conn = await getRethinkDB();return {conn: conn,};},
});
graphqlServer.applyMiddleware({ app });
graphqlServer.installSubscriptionHandlers(http);

This will create an Apollo server using the type definitions and resolves defined in our schema file (next section). We also connect to RethinkDB and pass the connection to our GraphQL context so it can be used in any incoming request.

这将使用类型定义创建Apollo服务器,并解析我们的模式文件(下一节)中定义的解析。 我们还连接到RethinkDB,并将连接传递到我们的GraphQL上下文,以便可以在任何传入请求中使用它。

创建一个GraphQL模式 (Create a GraphQL schema)

The main logic of the server resides in defining the GraphQL types and implementing their resolvers. We need to be able to preform three different actions, namely

服务器的主要逻辑在于定义GraphQL类型并实现其解析程序。 我们需要能够执行三种不同的操作,即

  • Query chat messages in a room查询聊天室中的聊天消息
  • Send a chat message to a room发送聊天消息到房间
  • Subscribe to new chat messages in a room订阅会议室中的新聊天消息

First, we create the GraphQL types. This consists of a Chat message type and the three mentioned actions, namely the chats query, the sendChat mutation, and the chatAdded subscription.

首先,我们创建GraphQL类型。 它由一个Chat消息类型和三个提到的操作组成,即chats查询, sendChat突变和chatAdded订阅。

// server/schema.js// GraphQL type definitions
const { gql } = require("apollo-server-express");
exports.typeDefs = gql`type Chat {user: Stringmsg: StringroomId: Stringts: Float}type Query {chats(room: String!): [Chat]}type Mutation {sendChat(user: String!, message: String!, room: String!): Chat}type Subscription {chatAdded(room: String!): Chat}
`;// GraphQL resolvers
// ...

Second, we need to resolve these actions, i.e., implement the code that they invoke. The query and the mutation are fairly straight-forward and are implemented as a simple RethinkDB query. The subscription however, requires an async iterator. This is basically a spell to turn the RethinkDB magic into GraphQL subscription magic. In more earthly terms, the async iterator wraps the RethinkDB change feed so we can subscribe to it via GraphQL.

其次,我们需要解决这些动作,即实现它们调用的代码。 该查询和变异非常简单,并且实现为简单的RethinkDB查询。 但是,订阅需要异步迭代器。 这基本上是一种将RethinkDB魔术变成GraphQL订阅魔术的咒语。 用更实际的术语来说,异步迭代器包装了RethinkDB更改提要,因此我们可以通过GraphQL对其进行订阅。

// server/schema.js// GraphQL type definitions
// ...// GraphQL resolvers
const r = require("rethinkdb");
exports.resolvers = {Subscription: {chatAdded: {async subscribe(parent, args, context, info) {return new RethinkIterator(r.table("chats").filter({ roomId: args.room }),context.conn,);},},},Mutation: {async sendChat(root, args, context) {const chatMsg = {user: args.user,roomId: args.room,msg: args.message,ts: Date.now(),};await r.table("chats").insert(chatMsg).run(context.conn);return chatMsg;},},Query: {async chats(parent, args, context, info) {const cursor = await r.table("chats").filter({ roomId: args.room }).orderBy(r.desc("ts")).run(context.conn);return await cursor.toArray();},},
};// Async iterator to access the RethinkDB change feed
const { $$asyncIterator } = require("iterall");
class RethinkIterator {constructor(query, conn) {this.cursor = query.changes().run(conn);}async next() {const val = await (await this.cursor).next();return { value: { chatAdded: val.new_val }, done: false };}async return() {await (await this.cursor).close();return { value: undefined, done: true };}async throw(error) {return Promise.reject(error);}[$$asyncIterator]() {return this;}
}

With the server set up, let’s move to the frontend.

设置好服务器后,让我们转到前端。

创建前端 (Creating the frontend)

We already created the Vue.js app skeleton we will use for the frontend. However, since our server implements a standard GraphQL backend, you might as well use React or any other frontend framework that supports GraphQL.

我们已经创建了将用于前端的Vue.js应用程序框架。 但是,由于我们的服务器实现了标准的GraphQL后端,因此您最好使用React或任何其他支持GraphQL的前端框架。

Our frontend will use two views, one for the home page and one for the chat room as well as a router to navigate between the two. For this lets add a router to the Vue skeleton and install all required dependencies. Adding a router to the Vue app will warn you about uncommited changes (proceed anyway) and ask if you want history mode (no).

我们的前端将使用两个视图,一个用于主页,一个用于聊天室,以及一个在这两个视图之间导航的路由器。 为此,让我们将路由器添加到Vue框架并安装所有必需的依赖项。 在Vue应用程序中添加路由器将警告您有关未提交的更改(无论如何进行),并询问您是否要使用历史记录模式(否)。

$ vue add router$ npm install apollo-client apollo-link-http apollo-link-ws apollo-cache-inmemory vue-apollo$ npm install sass sass-loader --save-dev

Our Vue app is located in the src folder and will be structured as follows: the entry point is in main.js and gets the GraphQL client configuration from graphql.js. Our main file also mounts App.vue which displays views selected by the router in router/index.js. Our app contains two views, views/Home.vue and views/ChatRoom.vue.

我们的Vue应用程序位于src文件夹中,其结构如下:入口点位于main.js并从graphql.js获取GraphQL客户端配置。 我们的主文件还挂载了App.vue ,它显示了路由器在router/index.js选择的视图。 我们的应用程序包含两个视图,即views/Home.vueviews/ChatRoom.vue

src├── main.js├── graphql.js├── App.vue├── router│   └── index.js└── views    ├── Home.vue    └── ChatRoom.vue

主应用和路由器 (Main app and router)

In a first step, let us modify the main app, home view, and router files that where initialized in the skeleton Vue app. In main.js we import the Apollo GraphQL client we will define further down and add it to our Vue app. In addition we will also create a random chat username for the user.

第一步,让我们修改在骨架Vue应用程序中初始化的主应用程序,主视图和路由器文件。 在main.js我们导入Apollo GraphQL客户端,我们将对其进行进一步定义,并将其添加到我们的Vue应用程序中。 此外,我们还将为用户创建一个随机的聊天用户名。

// src/main.jsimport Vue from "vue";
import App from "./App.vue";
import router from "./router";
import apolloProvider from "./graphql";Vue.config.productionTip = false;// Initialize random username
window.username = Math.random().toString(36).substring(2, 8);// Create and mount Vue app
new Vue({router,apolloProvider,render: (h) => h(App),
}).$mount("#app");

Our App.vue is even simpler than the skeleton, it just shows the router view and has some styling.

我们的App.vue比框架更简单,它仅显示路由器视图并具有某些样式。

<!-- src/App.vue --><template><div id="app"><router-view /></div>
</template><script>
export default {name: "App",
};
</script><style lang="scss">
// See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/App.vue
</style>

In our router/index.js we basically replace the "About" route with our "Room" route.

在我们的router/index.js我们基本上用“ Room”路由替换了“ About”路由。

// src/router/index.jsimport Vue from "vue";
import VueRouter from "vue-router";
import Home from "@/views/Home";
import ChatRoom from "@/views/ChatRoom";Vue.use(VueRouter);const routes = [{ path: "/", name: "Home", component: Home },{ path: "/:roomId", name: "Room", component: ChatRoom },
];const router = new VueRouter({routes,
});export default router;

In the home view we remove the HelloWorld component and add a form that allows us to join a room.

在主视图中,我们删除HelloWorld组件,并添加一个允许我们加入房间的表单。

<!-- src/views/Home.vue --><template><div class="main"><form v-on:submit.prevent="gotoRoom"><label>Username:<input v-model="user" type="text" /></label><label>Room:<input v-model="room" type="text" /></label><button>Join</button></form></div>
</template><script>
export default {name: "Home",data() {return {user: window.username,room: "lobby",};},methods: {gotoRoom() {window.username = this.user;this.$router.push({name: "Room",params: { roomId: this.room },});},},
};
</script><style scoped lang="scss">
// See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/views/Home.vue
</style>

Now that we stuffed the skeleton with the bits an pieces we need, let us tackle the real meat of the frontend, the GraphQL client and the chat room view.

现在,我们用所需的零碎填充了骨架,让我们处理前端,GraphQL客户端和聊天室视图的真实内容。

GraphQL客户端 (GraphQL client)

When our frontend loads we need to initiate the GraphQL client. In our example we use Apollo, the most used GraphQL client, which has good Vue.js integration with the vue-apollo package.

当前端加载时,我们需要启动GraphQL客户端。 在我们的示例中,我们使用最常用的GraphQL客户端Apollo,该客户端与vue-apollo软件包具有良好的Vue.js集成。

// src/graphql.jsimport Vue from "vue";
import VueApollo from "vue-apollo";
import ApolloClient from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { split } from "apollo-link";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";Vue.use(VueApollo);// HTTP connection to the API
const httpLink = createHttpLink({// For production you should use an absolute URL hereuri: `${window.location.origin}/graphql`,
});// Create the subscription websocket link
const wsLink = new WebSocketLink({uri: `wss://${window.location.host}/graphql`,options: {reconnect: true,},
});// Split link based on operation type
const link = split(({ query }) => {const definition = getMainDefinition(query);return (definition.kind === "OperationDefinition" &&definition.operation === "subscription");},wsLink, // Send subscription traffic to websocket linkhttpLink, // All other traffic to http link
);// Create apollo client/provider with our link
const apolloClient = new ApolloClient({cache: new InMemoryCache(),link: link,
});const apolloProvider = new VueApollo({defaultClient: apolloClient,
});export default apolloProvider;

Since we will use GraphQL subscriptions, our Apollo setup is a bit more complicated than usual. This is because normal GraphQL should be performed over HTTP but subscription updates will be pushed over a WebSocket.

由于我们将使用GraphQL订阅,因此我们的Apollo设置比平常要复杂一些。 这是因为正常的GraphQL应该通过HTTP执行,但是订阅更新将通过WebSocket推送。

聊天室视图 (The chat room view)

The final piece of the frontend will be the ChatRoom view. Here we actually get to use the GraphQL client we just initialized. This view basically shows a list with all the items in the chats variable and provides a form to send a chat message to the backend.

前端的最后一块将是ChatRoom视图。 在这里,我们实际上可以使用刚刚初始化的GraphQL客户端。 此视图基本上显示了一个列表,其中包含chats变量中的所有项目,并提供了一种将聊天消息发送到后端的表单。

<!-- src/views/ChatRoom.vue --><template><div class="chatroom"><ul id="chatlog"><li v-for="chat in chats" v-bind:key="chat.ts"><span class="timestamp">{{new Date(chat.ts).toLocaleString(undefined, {dateStyle: "short",timeStyle: "short",})}}</span><span class="user">{{ chat.user }}:</span><span class="msg">{{ chat.msg }}</span></li></ul><label id="username"> Username: {{ user }} </label><form v-on:submit.prevent="sendMessage"><input v-model="message" autocomplete="off" /><button>Send</button></form></div>
</template><script>
import gql from "graphql-tag";export default {name: "ChatRoom",data() {return {chats: [],message: "",user: window.username,handle: null,};},methods: {sendMessage() {const msg = this.message;this.$apollo.mutate({mutation: gql`mutation($user: String!, $msg: String!, $room: String!) {sendChat(user: $user, room: $room, message: $msg) {ts}}`,variables: {user: this.user,msg: msg,room: this.$route.params.roomId,},});this.message = "";},},apollo: {chats: {query: gql`query FetchChats($room: String!) {chats(room: $room) {msguserts}}`,variables() {return {room: this.$route.params.roomId,};},subscribeToMore: {document: gql`subscription name($room: String!) {chatAdded(room: $room) {msguserts}}`,variables() {return {room: this.$route.params.roomId,};},// Mutate the previous resultupdateQuery: (previousResult, { subscriptionData }) => {previousResult.chats.unshift(subscriptionData.data.chatAdded);},},},},
};
</script><style scoped lang="scss">
// See styles at https://github.com/mostlytyped/rethink-chat-graphql/blob/master/src/views/ChatRoom.vue
</style>

The sendMessage method is bound to the sendChat GraphQL mutation. As for the chats variable, the binding is a bit more involved. We bind it to the GraphQL chats query and in addition we use the chatAdded subscription to keep the variable up to date.

sendMessage方法绑定到sendChat GraphQL突变。 至于chats变量,绑定涉及更多。 我们将其绑定到GraphQL chats查询,此外,我们使用chatAdded订阅来使变量保持最新。

Now we have a working server and frontend. The last thing we need is to make sure the chats table actually exists in the RethinkDB database when we run the app.

现在我们有一个工作的服务器和前端。 我们需要做的最后一件事是在运行应用程序时确保chats表确实存在于RethinkDB数据库中。

数据库迁移 (Database migration)

The app does not work without a chats table. We thus need a database migration that adds the table.

没有chats表,该应用程序将无法运行。 因此,我们需要添加表的数据库迁移。

// server/migrate.jsvar r = require("rethinkdb");r.connect({host: process.env.RETHINKDB_HOST || "localhost",port: process.env.RETHINKDB_PORT || 28015,username: process.env.RETHINKDB_USERNAME || "admin",password: process.env.RETHINKDB_PASSWORD || "",db: process.env.RETHINKDB_NAME || "test",},function (err, conn) {if (err) throw err;r.tableList().run(conn, (err, cursor) => {if (err) throw err;cursor.toArray((err, tables) => {if (err) throw err;// Check if table existsif (!tables.includes("chats")) {// Table missing --> createconsole.log("Creating chats table");r.tableCreate("chats").run(conn, (err, _) => {if (err) throw err;console.log("Creating chats table -- done");conn.close();});} else {// Table exists --> exitconn.close();}});});},
);

This migration checks if the chats table exists, and if it is missing, it creates it.

此迁移检查chats表是否存在,如果缺少,则创建它。

一个简单的聊天机器人 (A simple chat bot)

As we saw, one of RethinkDBs great features is the baked in reactivity that allows us to subscribe to queries. This feature also comes in handy when creating a simple chat bot. The bot simply needs to subscribe to changes in the chats table and react to them whenever appropriate.

正如我们所看到的,RethinkDB的强大功能之一是响应式烘焙,它使我们能够订阅查询。 创建简单的聊天机器人时,此功能也很方便。 机器人只需要订阅chats表中的更改,并在适当的时候对它们做出React。

Our Lorem bot will reply with a random section of Lorem Ipsum whenever prompted with @lorem. The bot subscribes to the chats table and scans the beginning of the message. If it starts with@lorem, it will reply with a message in the same room.

每当有@lorem提示时,我们的Lorem机器人将随机回复Lorem Ipsum部分。 该漫游器订阅chats表并扫描消息的开头。 如果以@lorem ,它将在同一房间回复并显示一条消息。

// server/lorem-bot.jsconst LoremIpsum = require("lorem-ipsum").LoremIpsum;
const lorem = new LoremIpsum({sentencesPerParagraph: {max: 8,min: 4,},wordsPerSentence: {max: 16,min: 4,},
});// Run Lorem bot
const runBot = function (conn) {console.log("Lorem bot started");r.table("chats").changes().run(conn, (err, cursor) => {if (err) throw err;cursor.each((err, row) => {const msg = row.new_val.msg.trim().split(/\s+/);// Is the message directed at me?if (msg[0] === "@lorem") {let num = 10;if (msg.length >= 1) {num = parseInt(msg[1]) || num;}r.table("chats").insert({user: "lorem",msg: lorem.generateWords(num),roomId: row.new_val.roomId,ts: Date.now(),}).run(conn, function (err, res) {if (err) throw err;});}});});
};// Connect to RethinkDB
const r = require("rethinkdb");
const rdbConnect = async function () {try {const conn = await r.connect({host: process.env.RETHINKDB_HOST || "localhost",port: process.env.RETHINKDB_PORT || 28015,username: process.env.RETHINKDB_USERNAME || "admin",password: process.env.RETHINKDB_PASSWORD || "",db: process.env.RETHINKDB_NAME || "test",});// Handle closeconn.on("close", function (e) {console.log("RDB connection closed: ", e);setTimeout(rdbConnect, 10 * 1000); // reconnect in 10s});// Start the lorem botrunBot(conn);} catch (err) {throw err;}
};
rdbConnect();

将应用程序部署到Heroku (Deploy the application to Heroku)

To deploy our working application and bot to Heroku we need to create a Procfile. This file basically tells Heroku what processes to run.

要将我们的工作应用程序和机器人部署到Heroku,我们需要创建一个Procfile 。 该文件基本上告诉Heroku要运行哪些进程。

// Procfilerelease: node server/migrate.jsweb: node server/index.jslorem-bot: node server/lorem-bot.js

The release and web processes are recognized by Heroku as the command to run upon release and the main web app respectively. The lorem-bot process is just a worker process that could have any name.

Heroku将releaseweb进程识别为分别在发行版和主Web应用程序上运行的命令。 lorem-bot进程只是一个工作进程,可以使用任何名称。

Deploy the app to Heroku with

使用将应用程序部署到Heroku

$ git add .$ git commit -m 'Working rethink-chat app'$ git push heroku master

You will need to manually enable the lorem-bot process in your Heroku app. You can do so on the Resources tab.

您将需要在Heroku应用中手动启用lorem-bot进程。 您可以在“资源”选项卡上执行此操作。

结论 (Conclusion)

In less than 15 minutes we managed to create and deploy a chat application with a simple bot. This shows the power and ease of use of RethinkDB. The ability to subscribe to queries makes it easy to build a reactive app and can easily be integrated with GraphQL. Further, Heroku makes deployment a breeze, and with the RethinkDB Cloud add-on you will never have to do the tedious work of managing a database server yourself.

在不到15分钟的时间内,我们设法用一个简单的机器人创建并部署了一个聊天应用程序。 这显示了RethinkDB的强大功能和易用性。 订阅查询的能力使构建响应式应用程序变得容易,并且可以轻松地与GraphQL集成。 而且,Heroku使部署变得轻而易举,并且有了RethinkDB Cloud附加组件,您将不必亲自进行管理数据库服务器的繁琐工作。

Originally published at https://www.rethinkdb.cloud on September 3, 2020.

最初于 2020年9月3日 发布在 https://www.rethinkdb.cloud

普通英语JavaScript (JavaScript In Plain English)

Did you know that we have three publications and a YouTube channel? Find links to everything at plainenglish.io!

您知道我们有三个出版物和一个YouTube频道吗? 在plainenglish.io上找到所有内容的链接!

翻译自: https://medium.com/javascript-in-plain-english/bring-rethinkdbs-realtime-magic-to-the-frontend-with-graphql-1995cf20144f

前端工程师号称魔术师


http://www.taodudu.cc/news/show-5508841.html

相关文章:

  • jQuery的追加节点
  • jQueryDOM节点操作总结
  • 修改节点 html,jQuery节点更改
  • jquery在节点后后添加html,jquery如何添加节点?
  • JQuery获取节点的方法
  • jQuery siblings() 兄弟节点的方法
  • 【opencv+mfc】实现身份证上的身份证号识别
  • 华为云-身份证识别-OCR
  • webpack 打包配置
  • webpack4打包实战
  • Vue基础知识总结 9:vue webpack打包原理
  • 复印机的使用
  • 身份证复印件的正确使用方法
  • setInterval与setTimeout用法
  • 游戏在计算机丢失,Win10玩游戏报错“计算机丢失binkw32.dll”怎么办
  • 计算机缺失binkw32.dll,binkw32.dll 丢失的解决方法
  • 成为红警高手的基本要素(最后修订版)
  • 强制建筑+造小兵不花钱+炒兵赚钱.
  • IDEA - IDEA通过快捷键快速定位编译错误
  • IDEA快捷键定位编译错误
  • 红警快捷键一览表
  • alt键用法+微操+加攻击+防守技巧
  • QQ聊天气泡的动画动态效果实现
  • nbtstat获取计算机和MAC地址
  • 通过执行nbtstat命令获取MAC地址
  • 【89】nbtstat
  • win10查看对方计算机名,win10系统巧用nbtstat命令快速探测对方计算机名称的操作方法...
  • win10查找xp计算机名称,win10系统巧用nbtstat命令快速探测对方计算机名称的操作办法...
  • 不同设备拿到了相同的mac地址(arp和nbtstat)
  • 域名解析,反向域名解析nbtstat

前端工程师号称魔术师_使用graphql将rethinkdbs实时魔术带到前端相关推荐

  1. 前端工程师考核总结_最新前端工程师周工作总结

    前端工程师周工作总结 转眼一周的时间过去了,回想一下这一周做的工作,在保证效率的同时也仍然 存在着一些小的问题,现在总结如下. 第一,在工作的时候还是有点急于求成.这一周在写页面的时候遇到了一个小 问 ...

  2. hbuilder前端需要的插件_这两款HTML5开发工具,前端开发工程师用了直呼内行

    HTML5,是web前端开发者入门就需要学习的一门技术.而对于程序员来说,要想让开发工作实现效率最大化,就必然离不开一个趁手的开发工具.像这种世界级的技术,也自然是有很多很多的开发工具可以选择使用.今 ...

  3. 45万年薪web前端工程师,给你一条“不归路”-系统的web前端学习路线

    首先,对于HTML.CSS的学习内容,完全看视频就可以学,因为HTML.CSS没有任何的逻辑,而w3cschool是我们必须参考的标准,最重要的是 一遍一遍的敲,不要偷懒:然后参考大网站的官网去模仿, ...

  4. web前端入职配置_我如何从全职妈妈变成前端Web开发人员

    web前端入职配置 I thought about writing about my personal coding journey many times, but never had the cou ...

  5. 前端接收pdf文件_如何实现springmvc将返回的给前端的pdf文件放在浏览器里预览

    展开全部 1,在web路径下建立一个uploadFiles文件636f707962616964757a686964616f31333361316561夹. 2,在springMVC里映射PDF文件就像 ...

  6. 对前端来说token代表了什么_在线公开课 | 前端工程师如何突破瓶颈更好地变现自己...

    课程概要 此次课程的分享主题是"前端工程师如何突破瓶颈更好地变现提升自己".课程从以下三个方面入手,为大家详解一个前端工程师是如何一步步完善并提升自己的的. 前端工程师所应具备的能 ...

  7. 在线公开课 | 前端工程师如何突破瓶颈更好地变现自己

    在线公开课 | 前端工程师如何突破瓶颈更好地变现自己 原创: 京小云 京东云开发者社区  3天前 课程概要! 此次课程的分享主题是"前端工程师如何突破瓶颈更好地变现提升自己".课程 ...

  8. 前端工程师的出路在哪里? 未来前端的发展方向和岗位

    阿里妹导读:很多童鞋在上次的问卷调查里表示,希望多推出一些前端方向的内容.今天为大家分享一篇关于前端工程师职业发展的文章,抛砖引玉,期待与大家一起交流探讨. 我是成曰,目前在蚂蚁金服数据平台部任职前端 ...

  9. 前端工程师的未来在哪里?

    阿里妹导读:很多童鞋在上次的问卷调查里表示,希望多推出一些前端方向的内容.今天为大家分享一篇关于前端工程师职业发展的文章,抛砖引玉,期待与大家一起交流探讨. 文章作者,目前在蚂蚁金服数据平台部任职前端 ...

最新文章

  1. 一年的收益就是60% 熊市也能做到 股票花荣实战系统
  2. 95% CI, 置信区间 Confidence Interval
  3. 6月份Github上最热门的Java开源项目!
  4. jdbc显示mysql的数据_JDBC链接mysql插入数据后显示问号的原因及解决办法
  5. [BZOJ 3668UOJ #2][Noi2014]起床困难综合症(贪心)
  6. 优先级(HTML、CSS)
  7. 基于python的随机森林回归实现_python实现随机森林
  8. 异动处理中的发票类型应用(Complaint Processing)
  9. .Net读取Excel(包括Excel2007)
  10. Apache Hive 下载与安装
  11. 【AI应用】NVIDIA Tesla T4的详情参数
  12. MySQL关系一对多一对一多对多
  13. python 使用图形化界面tkinter显示图片 规定大小!
  14. SAP S4HANA1610/Fiori安装过程全记录
  15. 家里电脑怎么控制公司电脑 这三大软件榜上有名
  16. Spring 纯注解定时任务
  17. 后台管理系统-前端2
  18. HDU2567:寻梦
  19. 100内奇数之和流程图_机器视觉基础之工业相机50个常用术语
  20. 分布式环境Raft一致性共识算法解读

热门文章

  1. 模式识别(Pattern Recognition)学习笔记(十九)--多层感知器模型(MLP)
  2. 手机百度输入法暴露隐私
  3. Oracle采用的数据模型,POSTGRES、ORACLE等数据库采用的数据模型面向对象的数据模型()...
  4. 竞争粒子群(CSO)算法
  5. [Common]如何判断是usim卡还是sim卡 - MTK物联网在线解答 - 技术论坛
  6. 一个苦逼次世代游戏建模师的内心独白,请在家长的陪同下阅读
  7. item_get_app - 获得淘宝app商品详情原数据H5数据v2接口优惠券信息
  8. Python如何用print函数输出田字格?如何计算十年后的体重?
  9. 10天学会STM32的学习心得总结
  10. QQ的改版和Skype的固执,取消离线与坚守离线,相反的做法与相反的结果,虽然是产品上的纠结,却让我看到了未来的影子。...