当前位置:  首页  >  前端开发  >  JavaScript

直击JavaScript全栈教程

我看了不少资料,很多都是讲解这张图中的一个技术,讲解全栈的肯定是有的,但是往往过于复杂。本文试图通过一组文章,把JavaScript的全栈开发做一个全景的展示,力图使用一个案例,全须全尾的贯穿整个系列,便于初学者对技术的急速理解。

自从一年前发布了Vuejs小书的电子书,也有些日子没有碰过它们了,现在因为项目的缘故,需要使用JavaScript全栈开发。所以,我得把这个全栈环境搭建起来。

整个系列,是会采用我的一贯风格,就是不疾不徐,娓娓道来,学习完毕,你可以掌握我提到的全系列的知识,并且得到一个可以直接拷贝的代码模板,并把它用到你的项目中。

前端的复杂度

很多人是看不起JavaScript开发的。这玩意不就是玩具嘛,一些脚本和标签而已。说这话的时候,他们可能是就翘起二郎腿的,或者抱着膀子的。

然而,前端因为还在快速发展,因此很多东西在变,构造环境的选择比较多,技术种类也不少,很多事情得自己做。因此它其实并不比那么简单的。这篇文章的图,可以窥视到前端复杂的一角了。Modern Frontend Developer in 2018。

我看了不少资料,很多都是讲解这张图中的一个技术,讲解全栈的肯定是有的,但是往往过于复杂。本文试图通过一组文章,把JavaScript的全栈开发做一个全景的展示,力图使用一个案例,全须全尾的贯穿整个系列,便于初学者对技术的急速理解。

大纲

所以,文章会包括这些:

  1. 使用Vuejs脚手架,快速搭建一个CRD用户界面。会使用vuex管理状态,使用vue-router管理路由。
  2. 使用Mongodb存储和提供后端CRD服务。
  3. 使用Nodejs搭建后端CRD服务。
  4. 使用Fecth|Axios访问后端CRD服务
  5. 使用bulfy的美化组件的方法
  6. 整合全栈服务

其中的CRD指的是Create、Read、Delete。针对的数据对象,就是一个Todo对象,看起来是这样的:

{id:1,subject:"Loving"}

如果是多个数据对象,看起来是这样的:

[
  {id:1,subject:"Loving"},
  {id:1,subject:"Writing"},
  {id:1,subject:"Preying"}
]

这个看起来很简单平实的JS对象,会在一组组函数、模块和对象之间流动,甚至跨越网络边界,从内存到硬盘。它会被存储在Mongodb内,也会从Mongodb提取出来,在用户界面、HTTP客户端,HTTP服务器传递。

整个App看起来就是一台机器,可以说代码在运转这个机器,但是也不妨说是数据在驱动这个它。

使用Vuejs脚手架,快速搭建Todo App界面

我们给自己命题,做一个TODO应用,它看起来是这样的:

用户可以看到一个编辑框,和一个清单。

  1. 在编辑框内加入新的todo项目,按回车即可加入到列表
  2. 列表内每个项目有一个按钮,点击按钮,可以删除当前项目

环境要求

说起来搭建JS全栈开发环境,涉及到的东西真的不少。大的选择是这样的:

  1. 前端采用Vuejs
  2. 后端采用Nodejs
  3. 存储使用Mongodb。

大的选择定了,小的配套也就跟着来,前端配套的话需要一系列的技术,特别是前端,对应着Vuejs,配套管理路由、状态、组件的都有相应的技术手段。自己搭配的话,还是非常麻烦的。

幸好Vuejs还有一个前端脚手架工具vue-cli,它可以把以上的组件整合起来到一个工程内。一个最为基础的vue-cli工程脚手架的创建,现在得需要160M左右的空间占用。在我的电脑和网络情况下,需要2分半的时间才会完成。

前端 Vuejs

  1. vue-router,前端路由管理
  2. vuex,前端数据管理,专业一点的说法,就是状态管理,这些数据,可能是属性,数组,对象等等,可以跨组件访问,而不像是data函数那样提供的数据只能被本组件访问,可以想到,稍微大一点的前端工程都必须前端状态管理的。
  3. axios,前端HTTP访问,以promise的形式,封装了类似fetch,AJAX的能力
  4. buefy,前端微型框架,可以使用自定义标签使用自定义组件,并且CSS框架为Bulma
  5. Bulma,尽管使用了微框架,只是让对CSS framework的了解降到最低,但是不是说就不需要了解了。还是得学习的。Bulma相对于老牌的Bootstrap,是不需要依赖于JS框架,也没有任何JS代码,因此可以和任何一框架很好的结合,比如这里的Vuejs。这就是我选择它的原因

后端 cli-service

为了给前端开发提供工具链和开发便利性,我们常常需要webpack&babel。有了它们,就可以使用ES6的语法,以及代码更新后自动刷新等。这些都是非常便利的特性,用了就离不开的。有了vue-cli,对webpack&babel的了解可以降到最低,但是也不能不学,稍微需要一些定制的配置,也是必须要掌握的,起码得知道如何启动一个开发服务器,已经发布build,还有把前端服务经过proxyChain跳转到后端服务去等等。所幸是在这个教程内,你不需要学习太多就可以把案例跑起来。

App Server + Mongodb

接下来看后端,一般习惯就是使用Nodejs+Express.js的搭配。这个没有多少说的,都是老东西了。为了访问Mongodb,也需要一套框架,基于Callback的,或者基于Promise+Await+Async的,也是需要选择的。

为了便于理解,我会用一个最小的案例完成整个开发过程,就是案例在现实中并不存在,但是也是有用的,就是你可以当它们是模板,直接拷贝代码,然后填充你的内容。天下代码一大抄嘛,没有什么不对的,毕竟这些写代码是最快的。这个案例的数据模型就是对一个{id,name}的对象进行CRD(创建删除列表)。

安装运行环境

安装环境相对简单,特别是如果使用Mac OS X的话。有一些工具链可以帮助快速搭建环境。当然Windows也并不多麻烦就是了,它常常提供的是一个安装程序,大部分时间,你需要的就是点击下一步。

这里以MAC为例,讲解安装。

mongodb

安装和运行Mongodb Daemon:

brew install mongodb
mongodb

访问验证,首先执行Mongodb Shell:

mongo

输入命令,查询数据库清单:

> show dbs
local           0.000GB

能够看到这些信息,说明mongodb安装成功。

Node.js

安装并验证:

$brew install nodejs
$node -v
10.7.0

能够看到这些信息,说明Node.js安装成功。

开始前端编码

安装编码环境

首先安装vue-cli,方法和一般的NPM模块一样的,我们安装的版本是3.0:

npm i @vue/cli

查看版本:

vue -V
3.0.0

看到如下信息,说明成功。然后创建App的脚手架代码:

vue create todoapp

注意,在此创建过程中,命令行会指示你做出选择,我们会选择Manual select feature,然后选择router和vuex,其他不选。然后并执行此代码:

cd todoapp
npm run serve

可以在浏览器中访问localhost:8080看到Vue的启动画面。说明创建脚手架成功。

此时,vue-cli已经帮助安装了vuex和router的模块依赖。本节一次性的安装全部剩余的全部NPM依赖,免得以后用一个安装一个,麻烦而且啰嗦。

  npm install buefy --save
npm install axios --save

buefy是一个基于Bulma的Vuejs用户界面组件库。这次的UI的CSS方案,使用Bulma,Vuejs如果想要以定制组件的形式去使用它的话,那么需要安装Buefy模块。实际上,我研究过Bulma和 Bootstrap,还写了一本免费的电子书,我觉得Bulma相对于Bootstrap的优势在于1.不依赖任何JS框架2.用户接口设计更加简明。这就是我现在选择使用Bulma的原因。

Axios是一个封装了HTTPClient的库,提供了promise接口。我们使用它访问后端的HTTP Server的数据。之前提到的数据对象,就是由Axios提取到客户端,也会是通过Axios把数据对象提交到服务器的。

前端编码

首先,我们从状态开始。我们之前提到的Vuex,是Vuejs管理状态的官方插件。所谓的状态,就是应用程序的数据对象们。也就是我们提到的Todo对象和Todo对象集合。我们在App用户界面上看到的很多数据都是来自于状态对象。状态对象在src/store.js。不仅仅是的应用状态信息,还有和对这些的操作函数。既然需要一个todo项目清单,因此应该加入如下代码:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const defaultTodo = [
      {id:1,subject:'Eating'},
      {id:2,subject:'Loving'},
      {id:3,subject:'Preying'},
    ]
function indexById(todos,id){
  for (var i = 0; i < todos.length; i++) {
    if (id == todos[i].id)
      return i
  }
  return -1
}
import axios from 'axios'
export default new Vuex.Store({
  state: {
    msg:'Todo App',
    todos:defaultTodo
  },
  mutations: {
    add(state,subject){
      var todo = {id:subject,subject:subject}
      state.todos.push(todo)
    },
    remove(state,id){
      state.todos.splice(indexById(state.todos,id),1)
    },
    reload(state){
      state.todos = defaultTodo
    }
  },
  actions: {
  add: (context, link) => {
      context.commit("add", link)
    },
    remove: (context, link) => {
      context.commit("remove", link)
    },
    reload: (context) => {
      context.commit("reload")
    }
  }
})

其中的state.todos属性,就是我们的主要的数据对象了。state.msg这是提供了一个App的标题字符串。mutations属性内是对数据修改提供的方法,比如

  1. 我们需要添加一个todo,使用add()方法,相应的
  2. 删除一个todo,使用remove()方法
  3. 刷新一个todo列表,就会使用load()方法

有时候,对数据的修改可能是比较消耗时间的,因此为了避免阻塞客户端的主线程,这个对象也提供了异步的方法,actions对象内就是对应修改操作的异步方法,这里的方法功能上和mutations一致,但是是异步的。Vuex提供了类似:

context.commit()

的语法,提供和actions和mutations方法的对接。第一个参数是mutations的方法名称,之后的参数最为mutations方法的参数传递给mutations方法。

特别说下,mutations内的add()方法,其中用户界面会提供一个Todo.subject属性,而ID是需要自动生成的,我们这里临时使用subject的值作为id,就是一个偷懒,只要subject不要输入重复,也暂时可以蒙混过关。因为知道本项目内的后台存储会采用Mongodb,在Mongodb内插入一个新的对象后,会自动生成一个ID,我们的Todo对象的id会采用这个ID。这里就没有必要自己生成了。

在src/views/home.vue内,粘贴为如下代码:


《script》
import NewTodo from '@/components/NewTodo.vue'
import TodoList from '@/components/TodoList.vue'
import {mapState,mapActions} from 'vuex'
export default {
  name: 'home',
  computed:mapState(['todos','msg']),
  components: {
    TodoList,NewTodo
  },
  data(){
      return{newtodo:''}
  },
  methods:{
      ...mapActions([
      'remove',
      'add'
    ]),
      add1:function(){
          this.add(this.newtodo)
          this.newtodo = ''
      }
  }
}
《script》

...mapState,mapActions的解说。

就是说,我们这个Todo App划分为为两个组件,其中一个组件负责显示编辑框,并接受回车事件,把新的Todo项目加入到应用状态内。另外一个组件负责显示全部Todo项目,并接受删除事件,删除指定的Todo项目。它们分别是NewTodo组件和TodoList组件:


这两个组件的代码实现,分别在文件src/components/NewTodo.vuesrc/components/TodoList.vue内。NewTodo代码:


《script》
import {mapState,mapActions} from 'vuex'
export default {
  name: 'newtodo',
  computed:mapState(['todos','msg']),
  data(){
      return{newtodo:''}
  },
  methods:{
      ...mapActions([
      'add'
    ]),
      add1:function(){
          this.add(this.newtodo)
          this.newtodo = ''
      }
  }
}
《script》

TodoList代码:


《script》
import {mapState,mapActions} from 'vuex'
export default {
  name: 'todolist',
  computed:mapState(['todos','msg']),
  components: {
  },
  methods:{
    ...mapActions([
      'remove','reload'
    ])
  },
  mounted(){
    this.reload()
  }
}
《script》

在src/main.js文件内,添加如下代码,引入Buefy:

import Buefy from 'buefy'
import 'buefy/lib/buefy.css'
Vue.use(Buefy)

现在可以使用Buefy组件了。我们可以把NewTodo组件内的标准的input变成组件化的input,把标签换成b-input即可。代码如下:

现在看浏览器,input变成了比较有吸引力的Bulma风格的控件了。访问网络使用axios。需要首先找到src/home.vue在代码的开头部分引用此库:

import axios from 'axios'

在Vue单页组件内使用此库了。比如在src/home.vue内代码对象中加入新方法:

mounted(){
  var url  = 'https://api.coindesk.com/v1/bpi/currentprice.json'
  axios ({
        url:url,
        method: 'get',
    })
    .then( res => {console.log(res.data.chartName)} )
    .catch( err => cosole.error(err))
}

我们来看看适应效果。启动cli-service:

npm run serve

然后打开浏览器,输入地址localhost:8080,如果可以在浏览器内看到我们期望的用户界面,并且都可以看到console打印了Bitcoin,那么就说明用户界面代码和初步的访问HTTP网络的axios代码以及状态管理功能都是成功了的。

后端编码

现在,我们已经可以看到UI了,但是用户界面内的数据来自于客户端,而不是来自于服务器。我们的数据当然应该来源于服务器的了。因此我们需要启动给一个自己的服务器,这个服务器可以接受客户在界面上录入的新的Todo对象,也可以提供后端数据库内的Todo清单。

为了测试的目的,常常需要准备一个todo应用的后台JSON服务,可以通过HTTP方式,提供todo项目的增加删除修改和查询。

这样的服务器,使用了nodejs作为服务器端,并且使用了两个node模块,可以使用npm安装它们:

npm install express body-parser

body-parser是一个中间件,可以解析请求内容并把解析结果放到req.body属性内。最常见的做法就是解析json内容。

代码如下(文件名为:jsonserver.js):

  var express = require('express');
  var app = express();
  var path = require('path')
  var bodyParser = require('body-parser')
  app.use(bodyParser.json())
  var todos = []
  var public = path.join(__dirname, '/')
  app.use('/',express.static(public))
  const defaultTodo = [
    {id:1,subject:'Eating'},
    {id:2,subject:'Loving'},
    {id:3,subject:'Preying'},
  ]
  function rs(){
    todos = defaultTodo
  }
  function indexById(id){
    for (var i = 0; i < todos.length; i++) {
      if (id ==todos[i].id)return i
    }
    return -1
  }
  rs()
  app.delete('/api/todo/:id', function (req, res) {
    var userkey = +req.params.id
    todos.splice(indexById(userkey),1)
    res.end( JSON.stringify(todos));
    rs()
  })
  app.get('/api/todos', function (req, res) {
    res.end( JSON.stringify(todos));
  })
  app.post('/api/todo', function (req, res) {
    todos.push(req.body)
    res.end(JSON.stringify(todos))
    rs()
  })
  var server = app.listen(8081, function () {
    var host = server.address().address
    var port = server.address().port
    console.log("listening at http://%s:%s", host, port)
  })

可以使用命令执行:

node jsonserver.js

Curl命令验证

可以通过curl命令验证服务的有效性:

  1. GET操作

     $curl http://localhost:8081/todo/1
     $curl http://localhost:8081/todos
  2. DELETE操作

      $ curl -X "DELETE" http://localhost:8081/api/todo/1
  3. POST操作

    $curl -X POST  -H "Content-Type: application/json" -d '{"subject":"s4"}' http://localhost:8081/api/todo

前端HTML验证

创建一个index.html文件,并放置到和jsonserver.js代码同一目录,代码如下:

todos
todo/1


《script》
  function remove(){
    fetch (
      '/api/todo/1',
      {
        method: 'DELETE',
      }
    )
    .then( res => console.log(res.json()))
    .catch( err => cosole.error(err))
  }
  function create(){
    fetch (
      '/api/todo',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({id: "4", subject: "s4"})
      }
    )
    .then( res => console.log(res.json()))
    .catch( err => cosole.error(err))
  }
《script》

可以提供创建,删除,列表的测试,其中部分结果在console内显示。

说起来,JS访问HTTP的库真的是不少,这里 提到的库都有9种。其中的fetch api使用起来非常的简洁趁手,可是它不支持IE。如果你需要支持IE的话,使用Axios更好。这就是为什么Vuejs官方推荐Axios的原因吧:

The Fetch API is a powerful native API for these types of requests. You may have heard that one of the benefits of the Fetch API is that you don’t need to load an external resource in order to use it, which is true! Except… that it’s not fully supported yet, so you will still need to use a polyfill. There are also some gotchas when working with this API, which is why many prefer to use axios for now. This may very well change in the future though.

axios访问方法

相比fetch,使用axios必须依赖于外部文件。为了方便,我们直接使用unpkg网站提供的库文件。

axios的语法和fetch的大同小异,看着也是比较简洁美观的。以下代码,把create和remove函数的内部实现换掉,其他不变。






















    
扫码关注 PHP1 官方微信号
PHP1.CN | 中国最专业的PHP中文社区 | json解析格式化 | PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved PHP1.CN 第一PHP社区 版权所有