新入职的工程师使用Vue+GraphQL+无服务器开发了一个待办事项应用
首先
关于这篇文章
-
- 新入社員工程师尝试使用完全没有接触过的技术创建了一个Todo应用的故事
-
- 略带教程的格式
-
- 使用了命令行界面等简便的构建方法
-
- 对于各种技术的解释较少
-
- 适合想要了解vue和GraphQL风格的人?
- 欢迎提出错误指正或者更高效的写作方法,以及未来的学习建议。
所有事项应用程序概述
重要文件
-
- ユーザーの認証が出来ること
- TodoのCRUD操作が出来ること
技术
-
- Vue.js
-
- graphcool(serverless GraphQL)
- Apollo(クライアント側でGraphQLを扱いやすくするためのもの)
开发环境
-
- OS: Ubuntu 16.04.3 LTS
-
- node: v8.1.4
-
- npm: 5.5.1
- docker: 17.0.5.0-ce
创建一个任务管理应用
产品已经完成了。
Graphcool的准备。
首先,我們會從建立GraphQL的環境開始準備。
mkdir todo
cd todo
npm install -g graphcool
graphcool init graphcool
现在创建了一个名为graphcool的文件夹,并且准备工作完成了。非常简单!
准备Vue
接下来,我们会准备Vue的环境。
我们会顺便将以后会使用到的工具也安装进去。
npm install -g vue-cli
# 色々質問されますが今回私は基本的にそのままで、テスト関係だけnoにしました
vue init webpack vue
cd vue
npm install
npm install --save vue-apollo apollo-client-preset apollo-link-context graphql graphql-tag
现在,一个名为Vue的文件夹已经被创建,准备工作完成了。非常简单!!
好的,那么现在首先我们从数据库这一方面开始建立吧。
Graphcool的实现
创建模式。
在这个Todo应用中,用户认证是要求之一。Graphcool提供了用于进行认证的模板,因此可以轻松实现认证功能。
cd graphcool
graphcool add-template graphcooltemplates/auth/email-password
这样就添加了认证所需的内容。太厉害了!!!
(顺便提一下,在Graphcool的网站教程中也介绍了使用Facebook登录的方法。)
然而,追加后无法立即使用该功能。
要使用该功能,需要取消graphcool.yml和types.graphql的注释。
在取消注释的同时,还会进行Todo应用的表格设计。
types: ./types.graphql
functions:
signup:
type: resolver
schema: src/email-password/signup.graphql
handler:
code: src/email-password/signup.ts
authenticate:
type: resolver
schema: src/email-password/authenticate.graphql
handler:
code: src/email-password/authenticate.ts
loggedInUser:
type: resolver
schema: src/email-password/loggedInUser.graphql
handler:
code: src/email-password/loggedInUser.ts
permissions:
- operation: "*"
只需要取消掉graphcool.yml文件中的注释。
然后就可以使用认证功能了。
type User @model {
id: ID! @isUnique
createdAt: DateTime!
updatedAt: DateTime!
email: String! @isUnique
password: String!
todos: [Todo!]! @relation(name: "UserTodos")
}
type Todo @model {
id: ID! @isUnique
createdAt: DateTime!
updatedAt: DateTime!
title: String!
done: Boolean!
author: User! @relation(name: "UserTodos")
}
在types.graphql中进行模式定义。
User对象具有系统字段email、password和todos。
Todo对象具有系统字段title、done和author。
title表示todo的名称,done表示该todo是否完成,author表示todo的所有者。
这与基本的Todo应用程序的表设计是相同的。
顺便提一下,!表示该项为必填项(非空)。
建立数据库
好的,既然数据库模式定义已经完成了,那我们就来搭建数据库吧。
这次我们将在Docker上创建。
虽然我对Docker并不太熟悉,但是我成功地使用它了。
当遇到困难时,可以使用的奇门遁甲是“删除所有数据卷”。
graphcool local up
# Please choose the cluster you want to deploy toと
# 聞かれるので、一番下のlocalを選択。その後はそのままでok
graphcool deploy
如果成功了,我认为会显示各种内容。
然后我想在底部会有一个名为”Simple API:”的部分,我会记住它的URL。
这在连接到vue时是必需的。
如果忘记记下来了,可以通过graphcool info之类的方式进行确认。
好了,现在可以使用GraphQL了!太棒了!
创建用户
在Todo应用程序中,我们没有创建用户注册功能。
因此,我们需要从其他地方注册用户。请键入以下命令!
graphcool playground
然后会打开如下的屏幕。
你可以直接从这里开始使用GraphQL。
(顺便提一下,你还可以通过右上角的齿轮标志选择VIM模式。)
我想要创建一个用户。
由于这次添加了认证功能,所以我们将使用名为`sigupUser`的mutations。
mutations是指除了查询以外的操作,如更新、删除和创建(我认为)。
如果您能看一下右侧显示的SCHEMA的MUTATIONS部分,大致可以了解其中的情况。
请按照以下方式操作,点击中间的执行按钮。
mutation {
signupUser(email:"test@mail.com" password:"test")
{
id
token
}
}
用中文本地化解释以下内容,只需要一种选项:
signupUser()是使用的API名称,()内是参数。
{}内返回结果。
在本例中,创建用户后返回id和token。
用户已成功创建。
好的,现在让我们来看一下刚刚创建的用户信息。
复制id,然后写下以下的查询。
query {
User(id:"cjatrk2ti02eb0189w0zgclot")
{
id
createdAt
updatedAt
email
password
todos{
id
title
}
}
}
做得很好!太棒了!
Vue的实现
好了,接下来我们终于要进行Vue端的实现了。
关于Vue,因为在工作中接触过Angular,所以我就想着它们的感觉应该相似吧,结果我没有很仔细地看教程就开始使用,结果踩了不少坑。
我知道可能有一些不好的实现方法,请多多包涵。
登录功能
首先需要登录。
修改文件是 vue/src/main.js 和 vue/App.vue。
添加的文件是 vue/src/component 目录下的 Login.js 和 Logout.js,以及 vue/src/constants 目录下的 graphql.js 和 settings.js。
首先查看App.vue。
这是所谓的索引页面。
由于样式被直接使用,这里省略了它。
<template>
<div id="app">
<logout v-if="isLoggedIn"></logout>
<login v-else></login>
<router-view/>
</div>
</template>
<script>
import Logout from './components/Logout'
import Login from './components/Login'
export default {
name: 'app',
components: {
Logout,
Login
},
computed: {
isLoggedIn () {
return this.$root.$data.token
}
}
}
</script>
只是加载和显示登录和退出组件。
根据isLoggedIn()函数中存储的令牌来切换显示。
<template>
<button @click="logout()">Logout</button>
</template>
<script>
import {AUTH_TOKEN} from '../constants/settings'
export default {
name: 'Logout',
methods: {
logout () {
localStorage.removeItem(AUTH_TOKEN)
this.$root.$data.token = localStorage.getItem(AUTH_TOKEN)
this.$router.push({path: '/'})
}
}
}
</script>
接下来是Logout.vue,也是非常简单的,有一个登出按钮,当按下时,会删除保存在本地存储中的令牌信息,并将页面重定向到顶部。
<template>
<div>
<input v-model="email" type="text" placeholder="Email">
<input v-model="password" type="password" placeholder="Password">
<button @click="confirm()">Login</button>
</div>
</template>
<script>
import {AUTH_TOKEN} from '../constants/settings'
import {LOGIN_MUTATION} from '../constants/graphql'
export default {
name: 'Login',
data () {
return {
email: '',
password: ''
}
},
methods: {
confirm () {
const {email, password} = this.$data
this.$apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
email,
password
}
}).then((result) => {
const token = result.data.authenticateUser.token
localStorage.setItem(AUTH_TOKEN, token)
this.$root.$data.token = localStorage.getItem(AUTH_TOKEN)
this.$router.push({path: '/todos'})
}).catch((error) => {
alert(error)
})
}
}
}
</script>
请先登录。
以email和password作为模板,请您填写相应的表单并点击发送按钮。
表单中输入的值将与data()中的email和password进行绑定。
有一种方法是在用户按下发送按钮时使用confirm()函数进行身份验证。
this.$apollo.mutate用于调用GraphQL的mutation。
该mutation实际上是在vue/src/constants/graphql.js中定义的以下内容。
export const LOGIN_MUTATION = gql`
mutation LoginMutation($email: String!, $password: String!) {
authenticateUser(email: $email, password: $password) {
id
token
}
}
`
如果身份验证成功,将返回ID和令牌。然后将令牌保存到本地存储,然后跳转到待办事项页面。
好的,现在关于保存的token的使用位置是在vue/src/main.js文件中。
import {AUTH_TOKEN, DB_URL} from './constants/settings'
import {ApolloClient} from 'apollo-client'
import {HttpLink} from 'apollo-link-http'
import {setContext} from 'apollo-link-context'
import {InMemoryCache} from 'apollo-cache-inmemory'
import Vue from 'vue'
import App from './App'
import router from './router'
import VueApollo from 'vue-apollo'
Vue.config.productionTip = false
Vue.use(VueApollo)
let token
const authLink = setContext((_, { headers }) => {
token = localStorage.getItem(AUTH_TOKEN)
return {
headers: {
authorization: token ? `Bearer ${token}` : null
}
}
})
const httpLink = new HttpLink({
uri: DB_URL
})
const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$loadingKey: 'loading'
}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
apolloProvider,
router,
data: {
token
},
template: '<App/>',
components: { App }
})
在ApolloClient中,我们使用authLink来在请求头中添加token。同时,在httpLink中指定了我们自己搭建的DB的Simple API的url。我们将这两个链接作为ApolloClient的link进行传递。
现在,我们可以实现以下的登录和登出功能。
设计上一点都不起眼,真是令人感到寂寞。真是可惜。
待办事项功能
终于到了正题,即Todo功能。
以下是中文的同义句:
主要是在”vue/src/components”目录下的”Todos.vue”、”Todo.vue”和”CreateTodo.vue”文件中。
基本上,在”Todos.vue”文件中进行数据获取,在其子组件”Todo.vue”和”CreateTodo.vue”中进行数据库更新操作。
首先,我们来看一下子组件的Todo.vue。
<template>
<div>
<input type="checkbox" @click="done" :checked="todo.done">
<input v-model="title">
<button @click="edit">編集</button>
<button @click="del">削除</button>
</div>
</template>
<script>
import {UPDATE_TODO, DELETE_TODO} from '../constants/graphql'
export default {
name: 'Todo',
props: ['todo'],
data () {
return {
title: this.todo.title
}
},
methods: {
done () {
this.$apollo.mutate({
mutation: UPDATE_TODO,
variables: {
id: this.todo.id,
done: !this.todo.done
}
})
},
edit () {
this.$apollo.mutate({
mutation: UPDATE_TODO,
variables: {
id: this.todo.id,
title: this.title
}
})
},
del () {
this.$apollo.mutate({
mutation: DELETE_TODO,
variables: {
id: this.todo.id
}
}).then(() => {
this.$emit('refresh')
})
}
}
}
</script>
虽然有点长,但内容非常简单。
父组件会传递一行的Todo数据。
模板中会显示该数据,并设置处理用的按钮。
方法中的done、edit和del会更新数据库。
当点击done时,会将任务标记为已完成(或未完成)进行更新,当点击edit时,会修改标题。
此外,这是关于父组件的事情,当触发del的this.$emimt(‘refresh’)时,会重新获取数据。
本来应该是改变列表而不是重新获取数据,但由于实现方法有点模糊,所以只能采取这种形式。
顺便提一下,对于done和edit,即使不触发事件,它们也会自动变化。
但是我不知道为什么会有这样的操作。
请有人告诉我!!
好的,接下来是父组件的Todos.vue。
<template>
<div>
<h4 v-if="loading">Loading Todos...</h4>
<div v-else>
<h3>タスク追加</h3>
<create-todo @refresh="resetStore"></create-todo>
<h3>未完了タスク</h3>
<todo v-for="(todo, i) in tasks" @refresh="resetStore" :key="todo.id" :todo="todo"></todo>
<h3>完了タスク</h3>
<todo v-for="(todo, i) in done" @refresh="resetStore" :key="todo.id" :todo="todo"></todo>
</div>
</div>
</template>
<script>
import {USER_TODOS, LOGGED_IN_USER} from '../constants/graphql'
import Todo from './Todo'
import CreateTodo from './CreateTodo'
export default {
name: 'Todos',
data () {
return {
todos: [],
user: '',
loading: 0
}
},
components: {
Todo,
CreateTodo
},
computed: {
done () {
return this.todos.filter(todo => todo.done === true)
},
tasks () {
return this.todos.filter(todo => todo.done === false)
}
},
apollo: {
user: {
query: LOGGED_IN_USER,
update (data) {
return data.loggedInUser.id
}
},
todos: {
query: USER_TODOS,
variables () {
return {
id: this.user
}
},
update (data) {
return data.User.todos
}
}
},
methods: {
resetStore () {
this.$apollo.provider.defaultClient.resetStore()
}
}
}
</script>
在模板中,进行新注册栏的显示和待办事项列表的显示。
通过计算,将已完成任务和未完成任务分开,然后分别显示它们。
在这里,我使用 Apollo 来执行 GraphQL 的查询。
用户根据填充在标头中的令牌,返回当前用户的查询。
在 vue/src/main.js 中填充的令牌在这里被使用。
(顺便提一下,在 graphcool 的 playground 中使用这个查询(LoggedInUser)时,需要从左下角的 HTTP HEADERS(0) 传递 authorization 和 Bearer 令牌)。
在Todos中,我们获取当前用户的待办事项列表。
我们将变量设置为反应性的,以便在用户更改时执行。
之所以这样做是因为我们不知道如何在获取用户后再获取Todos。
实际上,当执行Todos时会报错说”用户不存在!”。
来吧,到了这一步,只需要按照相同的方法实现vue/src/components/CreateTodo.vue,并将todos添加到router中,就完成了。
以下是完成的Todo应用程序!太棒了!
因为没有指定风格,所以还是感觉寂寞。但是终于完成了!!!
最后
感谢您一直以来的陪伴。
如果我能为您提供任何帮助,我会非常高兴。
作为尝试制作的感想,我发现很难找到apollo的信息,有些困难,同时在vue中对数组操作也无法如我所愿,有些困难。再加上本来就缺乏web开发知识,对于令牌的使用等方面完全不了解。总的来说,我很享受接触到以前没有接触过的技术!
嗯,因为我是新人,所以大部分技术我都没有接触过!虽然我不确定是否要写成一篇文章,但下一步我想将此次制作的东西部署到AWS上试试看。
好的好的。
请给我一个在中国进行本土化的选项。
GraphQL入门-让你爱上GraphQL
Vue + Apollo教程-简介(注意信息有点陈旧)
Vue
Graphcool文档
vue-apollo