学习资源:点击这里
后端
新建一个文件夹,npm init初始化项目。
安装必要的组件:npm i express cors body-parser mongodb
npm i nodemon -D
将package.json里的scripts更新为如下:
"scripts": {
"start": "node server/index.js",
"dev": "nodemon server/index.js"
},
创建server文件夹,创建index.js文件,输入初始代码:
const express = require('express'); //引入express
const bodyParser = require('body-parser'); //引入bodyParser
const cors = require('cors'); //引入cors
const app = express(); //新建express实例
//Middleware
app.use(bodyParser.json()); //app.use是使用中间件
app.use(cors()); //
const port = process.env.PORT || 5000; //定义port
app.listen(port, () => console.log(`Server started on port ${port}`)); //监听
此时访问localhost:5000会返回Cannot GET /,因为没有设置路由。
在server文件夹下建立routes文件夹,在下面建立api文件夹,再下面建立posts.js文件。
在index.js里的中间件下添加:
const posts = require('./routes/api/posts');
app.use('/api/posts', posts);
目的是当访问/api/posts路径的时候,直接执行posts.js文件(目前是这么理解的)。
index.js的代码如下:
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
//Middleware
app.use(bodyParser.json());
app.use(cors());
const posts = require('./routes/api/posts');
app.use('/api/posts', posts);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server started on port ${port}`));
posts.js的代码如下:
const express = require('express');
const mongodb = require('mongodb');
const router = express.Router();
// Get Posts
router.get('/', async (req, res) => {
const posts = await loadPostsCollection();
res.send(await posts.find({}).toArray());
});
// Add Post
router.post('/', async (req, res) => {
const posts = await loadPostsCollection();
await posts.insertOne({
text: req.body.text,
createAt: new Date()
});
res.status(201).send();
});
// Delete Post
router.delete('/:id', async (req, res) => {
const posts = await loadPostsCollection();
await posts.deleteOne({
_id: new mongodb.ObjectID(req.params.id)
});
res.status(200).send();
})
async function loadPostsCollection(){
const client = await mongodb.MongoClient.connect
('mongodb+srv://alex:abcABC123@cluster0.sapok.mongodb.net/vue_express?retryWrites=true&w=majority', {
useUnifiedTopology: true
});
return client.db('vue_express').collection('posts');
}
module.exports = router;
使用postman就可以验证posts的增、删、查,改还不会。
前端
使用vue创建一个client的项目文件夹 vue create client。
进入client文件夹,使用npm run serve启动服务,浏览器输入localhost:8080即可查看。
要完成一个请求,需要几个文件。
首先是main.js文件,默认的不用改。
然后是App.vue文件,是模板主文件,主要是要import新建的component进来,script里添加:
import PostComponent from './components/PostComponent'
export default {
name: 'app',
components: {
PostComponent
}
}
之后在template里添加一个容器<post-service></post-service>。
然后就是在component里创建一个PostService.vue的模板,用来处理post(此处是帖子的意思,不是POST方法)相关的模板,包括template,script和style三个部分。
在main.js同级目录新建一个PostService.js文件,用来专门处理post向后端的请求,包括get/post/delete等。在PostService.vue里面import PostService.js进来,使用:
import PostService from '../PostService'
此时的请求路径是:
用户发起请求->main.js->App.vue->PostService.vue->PostService.js。
PostService.vue里script部分如下:
<script>
import PostService from '../PostService'
export default {
name: 'PostComponent',
data () {
return {
posts: '',
error: '',
text: ''
}
},
async created () {
const that = this
try {
const posts = await PostService.getPosts()
that.posts = posts
} catch (error) {
that.error = error
}
}
}
</script>
其中created是一个生命周期函数(幸好之前研究小程序的时候了解过),也就是运行的时候自动执行的一个函数。
这里面要注意的是使用async和await,说明函数需要等PostService.getPosts()执行完毕之后再赋值给posts,如果把async和await去掉,就只会得到一个空的值。
按照我目前的理解(写在这篇文章里),有async和await必然会有Promise与之相对应的,这个Promise就在PostService.js里面,如下:
class PostService {
// Get posts
static getPosts () {
return new Promise((resolve, reject) => {
try {
axios.get(url).then((res) => {
resolve(res.data)
})
} catch (err) {
reject(err)
}
})
}
}
这里面使用了return new Promise((resolve, reject) => {})返回了axios请求api返回的数据。
感觉这样子之后,请求就相对比较顺畅了,可以继续往下搞了。
模板
使用从后端请求过来的数据在template里展示的时候,发现v-for需要同时使用v-bind进行绑定post, index和key,要不然就会报错,目前还不知道为什么,当然后面对每个item的操作肯定需要用到这些,不过为什么是require的我就不太明白。
第一次看见有人把<div>这么分开写的。
<div class="post-container">
<div class="post"
v-for = "(post, index) in posts"
v-bind:item = "post"
v-bind:index = "index"
v-bind:key = "post._id"
>
{{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }}
<p class="text">{{ post.text }}</p>{{ post.createAt }}
</div>
</div>
最神奇的事情是,从api结果返回来的时间,如果想使用getDate()、getMonth()、getFullYear()这种方式,会提示数据不存在,这个时候需要在获得数据的时候,将时间处理一下,才能使用这些方法。
处理的方法如下:
//原来
resolve(
res.data
)
//处理
resolve(
res.data.map(post => ({
...post,
createAt: new Date(post.createAt)
}))
)
使用了一个map()方法,将post重新过滤了一遍,我猜测这个方法是把post数组里每一个元素都过了一遍,...post是说别的元素都不改,只有这个createAt元素的时间,使用new Date()方法进行整理。
当然以上纯属猜测,搜索了一下,发现了这个说明文档,原来map()方法就是元素每个都执行一遍括号里的内容,不过...是什么呢?暂时还没有搜索到。
PostComponent.vue全部代码:
<template>
<div class="container">
<h1>Latest Posts</h1>
<div class="error" v-if="error">{{ error }}</div>
<div class="create-post">
<label for="create-post">Say Something...</label>
<input type="text" id="text" v-model="text" placeholder="create a post">
<button @click="createPost">Post!</button>
</div>
<div class="post-container">
<div class="post"
v-for = "(post, index) in posts"
v-bind:item = "post"
v-bind:index = "index"
v-bind:key = "post._id"
@dblclick="deletePost(post._id)"
>
<p class="text">{{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }} {{ post.text }}</p>
</div>
</div>
</div>
</template>
<script>
import PostService from '../PostService'
export default {
name: 'PostComponent',
data () {
return {
posts: '',
error: '',
text: ''
}
},
async created () {
const that = this
try {
that.posts = await PostService.getPosts()
} catch (error) {
that.error = error
}
},
methods: {
async createPost () {
const that = this
await PostService.insertPost(this.text)
that.posts = await PostService.getPosts()
},
async deletePost (id) {
const that = this
await PostService.deletePost(id)
that.posts = await PostService.getPosts()
}
}
}
</script>
<style scoped>
.text {
border: 1px solid black;
padding: 20px auto;
background-color: blanchedalmond;
}
</style>
PostService.js全部代码:
import axios from 'axios'
const url = 'http://localhost:5000/api/posts/'
class PostService {
// Get posts
static getPosts () {
return new Promise((resolve, reject) => {
try {
axios.get(url).then((res) => {
resolve(
res.data.map(post => ({
...post,
createAt: new Date(post.createAt)
}))
)
})
} catch (err) {
reject(err)
}
})
}
// Create posts
static insertPost (text) {
return axios.post(url, {
text
})
}
// Delete posts
static deletePost (id) {
return axios.delete(`${url}${id}`)
}
}
export default PostService
至此前后端就算连接上了,使用的时候发现,这个数据库的请求可真费时间啊。这么看搞一个local的数据库还是非常必要的啊。
发布到Heroku
首先注册heroku的时候需要翻墙,不翻墙就看不到验证码,就无法注册。
然后,在client文件夹下面,新增一个文件叫vue.config.js,内容是:
const path = require('path')
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000'
}
}
}
}
然后把PostService.js里的url改成='api/posts'
重启vue之后,又可以访问了。
坦白讲我不是太知道为什么,这个vue.config.js是预定义的文件名吗?会自动读取到所有文件里吗?为什么没有引入PostService.js就可以使用?里面这堆code是啥意思?
之后,在devServer上面,添加一行:
outputDir: path.resolve(__dirname, '../server/public'),
这个似乎是要定义webpack把文件pack到什么地方去。
之后在vue的console下先停止serve,然后输入npm run build,就会在server文件夹下面打包了一个public文件夹,里面都是前端的内容。
之后要改一下production环境里面对路径的处理,在server/index.js里添加:
好吧,之后一直发布一直说是Application Error,试了无数次还是没有解决,暂时先放一下吧。
发布
把mongodb改成local的之后,这个项目的任督二脉就差发布到服务器上了,等我查查看怎么弄。
最新回复