译使用VueJS 2.0开发一款app

Posted 大前端视野

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译使用VueJS 2.0开发一款app相关的知识,希望对你有一定的参考价值。

前言


    本文来自一篇最新的 Vue.js 2 的外文,我们做了一些翻译和内容的补充。


正文


    VueJS 推出了一个新的版本。对于不知道 VueJS 为何物的同学,可以去他们的官网看看。

    

    VueJS 是一个融合了 AngularJS 和 React 一些特性的前端框架。第一版的 VueJS 可以被称作 “AgularJS lite”,它有类似 Angular 的模板系统,也用到了“脏检查”技术去监测那些需要在 DOM 中变化的数据。

    

    然而,VueJS 的 API 很小巧,因为它不会包含一些额外的工具库如 AJAX,这点和 React 很像。     然而,在下一个版本中,一些事情改变了。它像 React 一样使用了“Virtual DOM”模型,同时允许开发者任意选择模板系统。因此,VueJS 的作者也实现了流式服务端渲染技术,这个技术在今天的Web开发中总是受欢迎的。


    幸运的是,API 接口本身没有多少改变。VueJS 的周边的开发工具需要更新去兼容新的版本,不过我们仍然可以使用.vue单文件去开发组件。想要好好的看一下新版本的实现和一些 API 的改变的话,仔细阅读 VueJS 这个 Github issue。


VueJS 2.0,像 React 一样,使用了 “Virtual DOM” 并且允许你使用任意的模板系统。


    让我们用 Express,PassportJS,VueJS 2.0 开发一个简单的应用,来演示一下如何在应用程序中设置身份验证以及服务端和客户端是如何通信的。这个应用可以让用户去查看、添加和删除留言。你可以查看任意用户的留言,可以添加留言,也可以随时删除自己的留言。你甚至可以删除其它用户的留言,如果你有删除权限的话。


第一件事:


    让我们创建一个目录来保存我们的代码,然后引入初始依赖关系,我们使用npm进行安装。


mkdir vuejs2-authentication

cd vuejs2-authentication

npm init -y

npm install --save-dev nodemon

npm install --save express body-parser express-session connect-mongo flash node-uuid passport passport-local pug


    这些将被用来创建我们的服务器。接下来,让我们创建一些虚拟的数据并存放在data.json文件中。


{

  "users": [

    {

      "username": "rachel@friends.com",

      "password": "green",

      "scopes": ["read", "add", "delete"]

    },

    {

      "username": "ross@friends.com",

      "password": "geller",

      "scopes": ["read"]

    }

  ],

  "exclamations": [

    {

      "id": "10ed2d7b-4a6c-4dad-ac25-d0a56c697753",

      "text": "I'm the holiday armadillo!",

      "user": "ross@friends.com"

    },

    {

      "id": "c03b65c8-477b-4814-aed0-b090d51e4ca0",

      "text": "It's like...all my life, everyone has always told me: \"You're a shoe!\"",

      "user": "rachel@friends.com"

    },

    {

      "id": "911327fa-c6fc-467f-8138-debedaa6d3ce",

      "text": "I...am over...YOU.",

      "user": "rachel@friends.com"

    },

    {

      "id": "ede699aa-9459-4feb-b95e-db1271ab41b7",

      "text": "Imagine the worst things you think about yourself. Now, how would you feel if the one person that you trusted the most in the world not only thinks them too, but actually uses them as reasons not to be with you.",

      "user": "rachel@friends.com"

    },

    {

      "id": "c58741cf-22fd-4036-88de-fe51fd006cfc",

      "text": "You threw my sandwich away?",

      "user": "ross@friends.com"

    },

    {

      "id": "dc8016e0-5d91-45c4-b4fa-48cecee11842",

      "text": "I grew up with Monica. If you didn't eat fast, you didn't eat!",

      "user": "ross@friends.com"

    },

    {

      "id": "87ba7f3a-2ce7-4aa0-9827-28261735f518",

      "text": "I'm gonna go get one of those job things.",

      "user": "rachel@friends.com"

    },

    {

      "id": "9aad4cbc-7fff-45b3-8373-a64d3fdb239b",

      "text": "Ross, I am a human doodle!",

      "user": "rachel@friends.com"

    }

  ]

}


    另外,确保添加了如下脚本到 package.json 文件中。我们稍后会在写 VueJS部分时添加更多的脚本。


"start": "node server.js",

"serve": "nodemon server.js"


    接着创建 server.js 文件,添加如下代码:


// Import needed modules

const express = require('express');

const bodyParser = require('body-parser');

const session = require('express-session');

const MongoStore = require('connect-mongo')(session);

const flash = require('flash');

const passport = require('passport');

const LocalStrategy = require('passport-local');

const uuid = require('node-uuid');

const appData = require('./data.json');


// Create app data (mimics a DB)

const userData = appData.users;

const exclamationData = appData.exclamations;


function getUser(username) {

  const user = userData.find(u => u.username === username);

  return Object.assign({}, user);

}


// Create default port

const PORT = process.env.PORT || 3000;


// Create a new server

const server = express();


// Configure server

server.use(bodyParser.json());

server.use(bodyParser.urlencoded({ extended: false }));

server.use(session({

  secret: process.env.SESSION_SECRET || 'awesomecookiesecret',

  resave: false,

  saveUninitialized: false,

  store: new MongoStore({

    url: process.env.MONGO_URL || 'mongodb://localhost/vue2-auth',

  }),

}));

server.use(flash());

server.use(express.static('public'));

server.use(passport.initialize());

server.use(passport.session());

server.set('views', './views');

server.set('view engine', 'pug');


    让我们过一下这部分代码。首先,我们引入了依赖库。接下来,我们引入了依赖了应用所需的数据文件,通常你会使用一些数据库,不过对于我们这个应用,这样就足够了。最后,我们创建了一个 Express 的服务器并且用 sessionbody parser 模块对它配置。我们还开启了 flash 消息模块以及静态资源模块,这样我们可以通过 Node 服务器提供对 javascript 文件的访问的服务。然后我们把 Pug 作为我们的模板引擎,我们将会在 home 页和 dashboard 页使用。


注释:


Pug 就是之前大名鼎鼎的 Jade


    接下来,我们配置 Passport 来提供一些本地的身份验证。稍后我们会创建与它交互的页面。


// Configure Passport

passport.use(new LocalStrategy(

  (username, password, done) => {

    const user = getUser(username);

    if (!user || user.password !== password) {

      return done(null, false, { message: 'Username and password combination is wrong' });

    }

    delete user.password;

    return done(null, user);

  }

));


// Serialize user in session

passport.serializeUser((user, done) => {

  done(null, user.username);

});


passport.deserializeUser((username, done) => {

  const user = getUser(username);

  delete user.password;

  done(null, user);

});


    这是一段相当标准的 Passport 代码。我们会告诉 Passport 我们本地的策略,当它尝试验证,我们会从用户数据中找到该用户,如果该用户存在且密码正确,那么我们继续前进,否则我们会返回一条消息给用户。


    同时我们也会把用户的名称放到 session 中,当我们需要获取用户消息的时候,我们可以直接通过 session 中的用户名查找到用户。


    接下来部分的代码,我们将编写一些自定义的中间件功能应用到我们的路由上,去确保用户可以做某些事情。


// Create custom middleware functions

function hasScope(scope) {

  return (req, res, next) => {

    const { scopes } = req.user;

    if (!scopes.includes(scope)) {

      req.flash('error', 'The username and password are not valid.');

      return res.redirect('/');

    }

    return next();

  };

}


function canDelete(req, res, next) {

  const { scopes, username } = req.user;

  const { id } = req.params;

  const exclamation = exclamationData.find(exc => exc.id === id);


  if (!exclamation) {

    return res.sendStatus(404);

  }


  if (exclamation.user !== username && !scopes.includes('delete')) {

    return res.status(403).json({ message: "You can't delete that exclamation." });

  }


  return next();

}


function isAuthenticated(req, res, next) {

  if (!req.user) {

    req.flash('error', 'You must be logged in.');

    return res.redirect('/');

  }


  return next();

}


    让我们过一下这段代码。hasScope 方法检查请求中的用户是否有所需的特定权限,我们通过传入一个字符串去调用该方法,它会返回一个服务端使用的中间件。canDelete方法是类似的,不过它同时检查用户是否拥有这个留言以及是否拥有删除权限,如果都没有的话用户就不能删除这条留言。这些方法会稍后会被用到一个简单的路由上。最后,我们实现了isAuthenticated,它仅仅是检查这个请求中是否包含用户字段来判断用户是否登录。


    接下来,让我们创建 2 个主要的路由:home 路由和 dashboard路由。


// Create home route

server.get('/', (req, res) => {

  if (req.user) {

    return res.redirect('/dashboard');

  }

  return res.render('index');

});


server.get('/dashboard',

  isAuthenticated,

  (req, res) => {

    res.render('dashboard');

  }

);


    这里:

    1、我们创建了 home 路由:

    我们检查用户是否登录,如果登录,则把请求重定向到dashborad页面。

    2、同时我们创建了 dashborad 路由:

    我们先使用isAuthenticated中间件去确保用户已经登录,然后渲染 dashborad 页面模板。


    现在我们需要去创建身份验证的路由。


// Create auth routes

const authRoutes = express.Router();


authRoutes.post('/login',

  passport.authenticate('local', {

    failureRedirect: '/',

    successRedirect: '/dashboard',

    failureFlash: true,

  })

);

server.use('/auth', authRoutes);


    我们创建了路由安装在 /auth 路径上,它提供了一个简单路由 /login,这些我们稍后会在页面的表单提交时用到。


    接下来,我们将会创建一些 API 的路由。这些 API 会允许我们获取所有的留言,添加一条留言,删除一条留言。还有一个路由 /api/me 去获取当前登录用户的信息。


    为了保持结构统一,我们创建一个新的路由,把我们的路由添加上去,通过 /api 安装到服务中。


// Create API routes

const apiRoutes = express.Router();

apiRoutes.use(isAuthenticated);

apiRoutes.get('/me', (req, res) => {

  res.json({ user: req.user });

});


// Get all of a user's exclamations

apiRoutes.get('/exclamations',

  hasScope('read'),

  (req, res) => {

    const exclamations = exclamationData;

    res.json({ exclamations });

  }

);


// Add an exclamation

apiRoutes.post('/exclamations',

  hasScope('add'),

  (req, res) => {

    const { username } = req.user;

    const { text } = req.body;

    const exclamation = {

      id: uuid.v4(),

      text,

      user: username,

    };

    exclamationData.unshift(exclamation);

    res.status(201).json({ exclamation });

  }

);


// Delete an exclamation

apiRoutes.delete('/exclamations/:id',

  canDelete,

  (req, res) => {

    const { id } = req.params;

    const exclamationIndex = exclamationData.findIndex(exc => exc.id === id);

    exclamationData.splice(exclamationIndex, 1);

    res.sendStatus(204);

  }

);

server.use('/api', apiRoutes);


    现在我们只需要启动服务:


// Start the server

server.listen(PORT, () => {

  console.log(`The API is listening on port ${PORT}`);

});


    以上就是服务端我们需要的代码!我们仍然要创建模板。

    创建 views/index.pug 文件,添加以下代码:


doctype html

html(lang='en')

  head

    title Exclamations!

    link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7' crossorigin='anonymous')


    style.

      h1 {

        margin-bottom: 20px;

      }

  body

    .container-fluid

      .row

        .col-md-4.col-md-offset-4

          while message = flash.shift()

            .alert.alert-danger

              p= message.message

          h1.text-center Exclamations!

          form(action='/auth/login' method='POST')

            .form-group

              input.form-control(name='username')

            .form-group

              label(for='password') Password

              input.form-control(name='password' type='password')

            button.btn.btn-primary(type='submit') Login


    这是一个基本的 HTML 页面,我们使用 bootstrap 去添加一些基本的样式。我们创建一个简单的表单,它用来提交数据到我们的服务器。我们还把从服务端输出的错误消息打印在页面中。

    

    现在,通过执行 npm run serve 命令启动服务,然后在浏览器中输入 localhost:3000,可以看到这个 login 页面,如下图:



doctype html

html(lang='en')

  head

    title Dashboard

    link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7' crossorigin='anonymous')

    link(rel='stylesheet' href='/styles.bundle.css')

  body

    #app-container

    script(src='app.bundle.js')


    这代码也太少了,这一切都是在哪里?其实我们要做的就是给 VueJS 一个位置去安装它的初始化组件。这就是为什么我们只需要一个 app-container 和一个包含我们代码 script 文件就足够了。


    这里并不会做任何事情,除非我们创建了这些文件并且建立一个开发管道让我们的代码工作起来。让我们创建这些文件并且下载所需的依赖。


mkdir public

touch public/app.bundle.js public/styles.bundle.css

npm install --save vue@next axios

npm install --save-dev babel-core babel-runtime babel-plugin-transform-runtime babel-preset-es2015 browserify babelify vueify@next browserify-hmr vue-hot-reload-api watchify concurrently



    虽然需要安装很多依赖,但是这些都很简单。它能允许我们使用 Babel 和它的所有能力。我们通过 browserify 去打包代码,还可以使用 vueify 把组件放在一个文件中。

    注意到我们把 next 版本放在了 vue 和 vueify 后,这样可以安装到最新的alpha版本的VueJS和配合这个VueJS版本的vueify。

    让我们给 package.json 文件添加一些脚本,来让我们的应用编译变得更加容易。


"prestart": "npm run build:js",

"build:js": "browserify src/app.js -t vueify -p [ vueify/plugins/extract-css -o public/styles.bundle.css ] -t babelify -o public/app.bundle.js",

"watch:js": "watchify src/app.js -t vueify -t babelify -p browserify-hmr -p [ vueify/plugins/extract-css -o public/styles.bundle.css ] -o public/app.bundle.js",

"dev": "concurrently \"npm run serve\" \"npm run watch:js\""


    我们还需要对 Babel 做配置,创建一个.babelrc 文件,添加如下代码:


{

  "presets": [

    "es2015"

  ],

  "plugins": [

    "transform-runtime"

  ]

}


    我们已经做好了管道配置,在命令行运行 npm run dev。它将启动我们的服务器,编译静态资源,同时监听我们 JavaScript 文件的变化。一切就绪,让我们去编写 Vue 应用吧。创建 src/app.js 文件,添加如下代码:


import Vue from 'vue';

import ExclamationsViewer from './exclamations_viewer.vue';


new Vue({

  el: '#app-container',

  render(createElement) {

    return createElement(ExclamationsViewer);

  },

});


    这里我们依赖了 Vue 和 ExclamationsViewer 组件,稍后我们会创建它。

    然后我们创建了一个 Vue 实例。我们在实例化的时候会传入一个配置对象,它    包含一个 el 属性,它是我们应用容器的一个选择器,在这个例子中它是 id 为 app-container 选择器。


    我们还会传入一个 render 方法,这是 Vue 创建模板的新方式在以前,我们会传入 template 属性,值为模板字符串。现在,我们可以通过编程方式创建模板就像在 React 里用 React.createElment 方法一样。render 方法传入父组件的 createElement 方法,我们使用它去在父组件里创建子组件。


    在这个例子中我们要实例化一个 ExclamationsViewer。


    现在让我们来创建 ExclamationsViewer 组件。创建 src/exclamations_viewer.vue文件,添加如下代码:


<style>

    .exclamations-viewer,

    .add-form-container {

      margin-top: 20px;

    }

</style>


 <template>

    <div class="container">

      <div class="row exclamations-viewer">

        <div class="col-md-4">

          <Exclamation-List :user='user' title='All Exclamations' :exclamations='exclamations'></Exclamation-List>

        </div>

      </div>

    </div>

  </template>


  <script>

    import axios from 'axios';

    import ExclamationList from './exclamation_list.vue';


    export default {

      name: 'ExclamationsViewer',

      data: () => ({

        user: {

          scopes: [],

        },

        exclamations: [],

      }),

      beforeMount() {

        axios.all([

          axios.get('/api/me'),

          axios.get('/api/exclamations'),

        ]).then(([{ data: meData }, { data: exclamationData }]) => {

          this.user = meData.user;

          this.exclamations = exclamationData.exclamations;

        });

      },

      components: {

        ExclamationList,

      },

    };

  </script>


    这里我们创建一个简单的 Vue 组件。因为我们用了 vueify,我们可以在一个 vue 文件中把组件分离成 CSS,template,script 代码3个部分。


    CSS被 <style></style> 标签包围。我们没有写太多 CSS 代码,只设置了一些间距。在模板中,我们设置了一个经典的 bootstrap 栅格布局并且添加了一个自定义组件 Exclamation-List,稍后我们会创建它。


    为了给组件传入 props 我们在属性前面加上了冒号,然后我们传入一个字符串,它表示我们要传递给子组件的一段数据。


    例如,:user='user' 表示我们把当前组件 data 中的 user 作为属性传入 Exclamation-List。


    接下来,在我们的 <script></script> 标签中,我们引入了 axios 库和 ExclamationList 库。我门通过设置 data 属性为一个 function 并调用它去实例化和组件的数据。这里,我们仅仅返回一个对象包一个拥有空 scopes 数组的 user 对象和一个空 exclamations 数组。


注释:


axios

类似 request 可以同时用在浏览器端和 node.js 中请求类的工具包

支持 Promise API


    任何将要被使用的数据需要在 data 对象里做初始化,这点很重要否则,Vue 可能不能有效的监测到这部分数据的变化


    接下来,在 Vue 生命周期的 beforeMount 方法中去调用 API 请求当前登录用户和所有留言信息。然后我们把数据保存在组件中,它会替换掉我们在 data 方法里创建的数据,Vue 会完美实现这一点。


    最后,我们通过把 ExclamationList 组件添加到当前组件的 components 属性来局部注册。如果不添加的话,VueJS 是不知道任何关于 ExclamationList 组件的。


注意:


我们添加组件的时候可以用 PascalCase(ExclamationList)命名法,也可以用camelCase(exclamationList)命名法,但是在模板中引用组件的时候,必须用list-case(Exclamation-List)命名法。



以上是关于译使用VueJS 2.0开发一款app的主要内容,如果未能解决你的问题,请参考以下文章

Keep Learning Vuejs 2.0 组件的使用

Keep Learning Vuejs 2.0 - 表单输入

[译] 5个 Vuex 插件,给你的下个VueJS项目

Keep Learning Vuejs 2.0 - 监听对象(watcher)

译JavaKotlinRNFlutter 开发出来的 App 大小,你了解过吗?

《vueJs + elementUI+node+es6开发点菜app》¥9.9