徒手撸个vue项目框架(下)

栏目: 编程语言 · 发布时间: 5年前

内容简介:写这篇文章的目的,更多是让自己更熟悉vue-cli脚手架创建项目的依据和项目结构,其次是希望我的学习过程可以帮到有疑惑的同学,有什么错误还希望可以得到指教为什么要分上、下,因为最近学习react.js,发现项目框架除了使用的js库不同(vue.js、react.js),配置基本上是大同小异的这也是我占坑(脸大)的理由

写这篇文章的目的,更多是让自己更熟悉vue-cli脚手架创建项目的依据和项目结构,其次是希望我的学习过程可以帮到有疑惑的同学,有什么错误还希望可以得到指教

为什么要分上、下,因为最近学习react.js,发现项目框架除了使用的js库不同(vue.js、react.js),配置基本上是大同小异的

这也是我占坑(脸大)的理由

徒手撸个vue项目框架(下)

徒手撸个react项目框架(上)

徒手撸个react项目框架(下)

这次的目的是接着上篇在框架中添加vue-router,在第三方 工具 包的基础上针对业务进行封装

一、路由

路由是vue组件能够灵活切换的关键所在,vue-router是vue的官方路由。

1.引入vue-router

a.下载

cnpm i vue-router --save-dev
复制代码

b.实例化

我们在src目录下新建router文件夹,并在router新建index.js,同时在components下新建login.vue

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
  mode: "hash",
  routes: [
    {
      path: "/",
      name: "index",
      components: require("../components/hello.vue")
    },
    {
      path: "/login",
      name: "login",
      components: require("../components/login.vue")
    }
  ]
})

export default router
复制代码

c.挂载

到这里还没结束,我们还需要将router对象挂载到根实例上,在index.js中,如下操作

import router from "./router/index";

new Vue({
  el: "#app",
  router,
  render: h => h(App)
});
复制代码

在你需要渲染组件的地方添加router-view标签

<template>
  <div>
    <router-view />
  </div>
</template>
复制代码

现在你可以在浏览器中访问了

2.利用导航守卫实现登录控制

但是有时候我们的项目要求没有登陆是不能进入首页的,这个功能可以利用router的前置导航守卫实现

beforeEach就是前置导航守卫的钩子函数,它接收三个参数,to、from、next

  • to:即将进入的路由对象
  • from:离开的路由对象
  • next:一定要调用该方法来resolve这个钩子

我使用了h5的localStorage模拟cookie保存用户信息,这里只是个测试,如果你喜欢cookie,可以使用自己喜欢的cookie包,个人喜欢js-cookie

思路是这样的,登录的时候验证完对的用户名和密码,就设置一个字符串作为token存储在本地,token的作用就是下次可以免登录(这种做法的安全性是个问题)

在router/index.js中

router.beforeEach((to, from, next) => {
  // 如果登录的时候设置过
  if(localStorage.getItem("token") != null){
      if(to.name == 'login'){// 如果还访问登录页就导向首页
          next({path: '/'})
      }else{// 给所有其它页面放行
          next()
      }
  }else{// 如果没有设置这个值,为空,说明没有登录,导向登录页
     if (to.name == "login") {
      next();
    } else {
      if (to.name == "login") {
            next(); // 这里要记得给登录页放行,不然会进入死循环
        } else {
            next({ path: "/login" });
        }
    }
  }
})
复制代码

虽然这里已经做得很不错了,但依然没有发挥出vue-router灵活和强大的一面。换句话说,这里能做的事还有很多很多,比如项目是一个管理系统的话,可能会因角色不同,进入首页的侧边栏目是不一样的,虽然看上去是个很复杂的过程,但是仔细分析下也就几步,感兴趣的同学可以看这里

3.利用命名视图实现特殊布局

a.同级路由

同级路由就是两个router-view标签是并列的,分别给两个标签用name属性命名,所以命名视图就是给视图命名了后的视图router-view

// app.vue
<router-view name="navbar"></router-view>
<router-view name="main"></router-view>

// 对应的路由写法就是
import { NavbarComponent, MainComponent } from "@/components"
const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        navbar: NavbarComponent,
        main: MainComponent
      }
    }
  ]
})
// 在app.vue中
<template>
  <div>
    <router-view name="navbar" />
    <router-view name="main" />
  </div>
</template>
复制代码

这时你看到的是这样的

徒手撸个vue项目框架(下)

b.嵌套路由

嵌套路由在管理系统更为常见,我们经常使用的layout布局就是嵌套路由实现的

// 这次我这样定义路由
{
  path: "/index",
  name: "index",
  components: require("@/components/hello"),
  children: [
    {
      path: "login",
      name: "login",
      components: require("@/components/login")
    }
  ]
},

// 并且这样写hello组件
<template>
  <div class="test">index page
    <router-view></router-view>
  </div>
</template>
复制代码

我在浏览器中访问http://localhost:8080/#/index/login,可以访问到下面的页面

徒手撸个vue项目框架(下)

二、与服务端交互

官方推荐与服务端交互使用axios,它是基于ajax封装的,用起来十分简洁

1.下载

cnpm i axios --save-dev
复制代码

2.介绍

axios有两种使用方法一种是使用axios对象调用get或者post请求方法, 另一种是使用axios api,使用axios()函数,参数是个配置对象options

axios.get('/getUser')
    .then(data=>{
        // 请求成功处理函数
        console.log(data)
    })
    .catch(err=>{
        // 请求失败处理函数
        console.log(err)
    })
// 或者
axios({
    url: '/getUser',
    mothod: 'get'
})
    .then(data=>{
        // 请求成功处理函数
        console.log(data)
    })
    .catch(err=>{
        // 请求失败处理函数
        console.log(err)
    })
复制代码

3.封装

在实际的项目中,经常需要对发送的请求或者服务端响应的结果进行处理,请求很多时,挨个处理就很繁琐,也很不切合实际,希望在全局就已经处理好了,我们只负责发送请求和接收服务端响应。

好在axios提供了这种方法。

在src下新建http文件夹,下面新建request.js文件

import axios from "axios";

// 重新实例化一个axios实例,这个实例会覆盖所有默认属性
const server = axios.create({
  baseURL: "/api",
  timeout: 5000,
  heads: { 'content-type': 'application/x-www-form-urlencoded' },
});

// 或者通过修改实例的defaults属性,这两种方法是等价的
server.defaults.baseURL = "/api";
server.defaults.timeout = 5000;

export default server
复制代码

不仅如此,我们还希望在每次发送请求的时候带上登录是设置的token值,在收到服务器错误时可以做出相应的反馈,比如返回的状态码为404,就导航到404页面

这里可以使用axios提供的拦截器对象,具体做法如下

// 设置拦截器
// 请求拦截器
server.interceptors.request.use(
  config => {
    config.headers.token = localStorage.get("token");
    return config;
  },
  err => {
    return Promise.reject(err);
  }
);
// 响应拦截器
server.interceptors.response.use(
  response => {
    return response;
  },
  err => {
  switch (err.response.status) {
      case 404:
        router.push({
          path: "/404"
        });
        break;
      case 504:
        router.push({
          path: "/504"
        });
        break;
    }
    return Promise.reject(err);
  }
);
复制代码

++记得添加错误对应的页面和路由++

// router/index.js
    {
      path: "/404",
      name: "404",
      components: require("../components/404.vue")
    },
    {
      path: "/504",
      name: "504",
      components: require("../components/504.vue")
    }
复制代码

简单的封装已经完成了,只需要在使用的地方引入它

4.跨域代理

这里我们就得说说跨域了,这是前后端分离项目无法避开的。对于前后端分离的项目,之所以跨域是因为浏览器有同源策略的安全限制,来防止跨站请求伪造和跨站脚本攻击

跨域就是违背了同源策略,webpack-dev-server提供了解决跨域的方案

// webpack-dev-server方案
// 在webpack.config.js中devServer
devServer: {
    port: 8080,
    proxy: {
        "/api": {
            target: "http://127.0.0.1:10000", // 代理的目标地址
            changeOrigin: true  // 是否开启跨域
        }
    }
}
复制代码

当然axios也提供了跨域方案,只是比较复杂,还需要后端同学的设置配合才行,这里就不说了

5.使用

这里使用一个例子来说明使用方法。==当然,我没有写登录功能,所以记得把之前写的路由前置守卫的钩子函数注释掉,不然无法跳转==

逻辑是这样的,在接入首页时自动发送请求,获取用户列表,请求方式为get

// hello.vue
import axios from '../http/request.js'

export default {
  created() {
    axios({
        url: '/getUsers',
        method: 'get'
    })
    .then(data =>{console.log(data)})
    .catch(err =>{console.log(err)})
  }
};
复制代码

如果你已经有后端服务器的支持,但是没有写对应的路由,那它会在控制台报错超时,即状态码为504,页面会自动跳转到504页面

假如你和我一样没有后端服务程序,那控制台报错为404,即状态码为404,页面会自动跳转到404页面

三、细节的完善

到这里项目框架功能已经算是撸完了,但是它有很多地方不够正规,和cli创建的项目相比,生产环境和开发环境还没有分离;其次一个合格的大众框架,应该有eslint语法检测;虽然主流的浏览器已经开始支持es6测试语法,但是最好可以加入babel-loader,将es6语法转换es5语法。

1. babel-loader

babel-loader主要是将es6语法转换成浏览器兼容的es5语法,但是项目中node_moudles下也有很多js文件,全都转换会使得速度变慢,并且使项目无法运行,所以需要配置转换文件的路径或者屏蔽掉无需转换的路径

a.下载

babel-loader只是个加载器,转换代码的工作是babel-core babel在转换 ES2015 语法为 ECMAScript 5 的语法时,babel 会需要一些辅助函数, 例如 _extend。babel 默认会将这些辅助函数内联到每一个 js 文件里,这样文件多的时候,项目就会很大。

所以 babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中,这样做能减小项目文件的大小

// babel-preset-env这是babel预设的语法规则,babel-preset-是前缀,env是包名
// @babel/plugin-transform-runtime是插件,babel-plugin-是前缀,transform-runtime是插件名

cnpm i babel-core babel-loader babel-preset-env @babel/plugin-transform-runtime --save-dev
复制代码

==这里要注意下载的版本,起初我下载的是babel-plugin-transform-runtime,结果一直报错==

TypeError: this.setDynamic is not a function
复制代码

后来我下载了@babel/plugin-transform-runtime才解决了这个问题

b.配置

// rules中
// include 表示哪些目录中的 .js 文件需要进行 babel-loader
// exclude 表示哪些目录中的 .js 文件不要进行 babel-loader
 module: {
    rules: [
        {
            test: /\.js$/,
            loader: "babel-loader",
            // include: [path.resolve("src")],
            exclude: /node_modules/,
            options: {
                presets: ['env'],  // env提供语法转化的规则,这里是babel预设的
                plugins: ['transform-runtime'] // 这里放我们自己想要使用的插件
            }
        }
    ]
}
复制代码

babel-loader默认是什么都不会做的,需要在预设presets选项中指定插件为你工作的语法规则,babel已经为我们提供了几套预设方案,babel-preset-env就是最新的语法转换规则,其中babel-preset-只是前缀, 另外babel-loader还可以提供丰富的插件做更多的事,只需要在options下的plugins选项中指明

++注意:要用的插件一定要记得下载++

c. 一个例子说明.babelrc文件的用法

下面除了想自动语法转化外还希望将组件懒加载,使加载变快,这时候需要使用插件babel-plugin-syntax-dynamic-import,所以先下载这个包,然后将它放入plugins中

// 下载
cnpm i babel-plugin-syntax-dynamic-import --save-dev

// 在module选项下
rules: [
        {
            test: /\.js$/,
            loader: "babel-loader",
            include: [path.resolve("src")],
            exclude: [path.resolve("node_modules")],
            options: {
                presets: ['env'],  // env提供语法转化的规则,这里是babel预设的
                plugins: ['@babel/transform-runtime',"syntax-dynamic-import"]
            }
        }
    ]
复制代码

假如你需要的插件很多,还需要单独的配置,你可以在项目根目录下创建一个.babelrc的文件替换掉options,配置语法完全同json,下面给个示例,完全等同上面的options

// 在.babelrc文件中,必须是要有两个数组类型的选项:presets、plugins
{
  "presets": ["env"],
  "plugins": ["@babel/transform-runtime", "syntax-dynamic-import"]
}

复制代码

presets更多选项看这里

2.eslint语法检测

eslint对初学者来说是极其不友好的,因为它有严格的要求,使得很多人都想着关闭它,但是不得不说eslint在多人协作团队来说是至关重要的,它可以保证代码风格的一致性

a.下载

cnpm i eslint-plugin-vue eslint-friendly-formatter eslint-loader eslint --save-dev

// 此外记得全局安装eslint支持vue的插件,否则会报错Cannot find module 'eslint-plugin-vue',但是局部仍然要安装
cnpm i eslint-plugin-vue -g
复制代码

b.使用

在webpack配置文件中,添加新的rules

{
        test: /\.(js|vue)$/,
        loader: "eslint-loader",
        include: [path.resolve(__dirname, 'src')], // 指定检查的目录
        options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine 
            formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
        }
    }
复制代码

c.配置规则

eslint官网提到了三种使用方法(这也是查找规则所在位置的顺序)

  • 使用注释的形式嵌入到代码
  • 在package.json中添加eslintConfig字段,这里指定配置
  • 使用.eslintrc.*文件(任何后缀名)

我选择效仿别人使用.eslintrc.js形式,既然是js文件我们就需要跟写js模块文件一样暴露出一个对象

// 像这样
module.exports = {}
复制代码

这里我只说几个常用的或者重要的配置选项,官网支持中文版,比babel中文版友好很多

// 像这样
module.exports = {
    // 默认情况下,ESLint 会在所有父级目录里寻找配置文件,一直到根目录。如果你想要你所有项目都遵循一个特定的约定时,这将会很有用,但有时候会导致意想不到的结果。为了将 ESLint 限制到一个特定的项目,在你项目根目录下的 package.json 文件或者 .eslintrc.* 文件里的 eslintConfig 字段下设置 "root": true。ESLint 一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
    "root": true,
    // extends: 继承属性,值可以是 "eslint:recommended" "eslint:all" 或者是个插件 或者是个文件
    extends:[
         "eslint:recommended",
         "eslint:all"
        // "plugin:react/recommended"
    ],
    // env: 指定运行的环境,可选值有browser、node、es6等等,值为布尔类型,true为开启,默认为false
    "env": {
        "browser": true
    },
    // plugins: 指定插件,使用之前需下载,vue项目中使用的话vue就是属于插件
    "plugins": [
        "vue"
        // "react"
    ],
    // rules: 指定语法规则,分为0,1,2三个等级对应off(关闭检测),warn(只警告),error(直接报错)
    "rules": {
        "eqeqeq": "off",
        "curly": "error", // if结构中必须使用{}
        "quotes": ["error", "double"]
        // 更多rules看这里 https://www.jianshu.com/p/80267604c775
        // 这里要说下rules的extends特性
        // 假如你的设置是这样的
        // "eqeqeq": ["error", "allow-null"]
        // "eqeqeq": "warn"
        // 那么它最后生成的配置是 "eqeqeq": ["warn", "allow-null"]
    },
    
}
复制代码

4.设置是否开启eslint

优秀的框架肯定会根据配置文件查看是否开启eslint,我在配置文件中定义了一个可配置的变量esLint,当它的值为true时开启eslint语法检测,但默认值为false

const esLint = false

// 替换掉配置好的eslint-loader
....
{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader"
},
esLint?{
    test: /\.(js|vue)$/,
        loader: "eslint-loader",
        include: [path.resolve(__dirname, "src")], // 指定检查的目录
        options: {
          // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
          formatter: require("eslint-friendly-formatter") // 指定错误报告的格式规范
        }
}:{},
...
复制代码

3.使用绝对路径

项目里我一直使用的是相对路径,虽然有友好的代码提示,但是一旦你改变了文件位置,就会报错不止,直到你把所有路径修改正确,最好的做法就是使用绝对路径,并且给路径添加别名,可以方便我们的书写

resolve: {
    alias: {
      ···
      "@": resolve("src")
    }
  }
复制代码

这样我们在import中书写路径时可以用@表示根目录到src,后面继续跟剩下的路径

import hello from "@/components/hello.vue";
复制代码

4.省略后缀名

resolve: {
    extensions:  [".js", ".vue", ".json"], // 当然,这里还可以添加.css、.less、.sass,这都是允许的
    ···
 }
复制代码

现在你在import时有.js或者.vue文件时不用再写后缀名了

import hello from "@/components/hello";
复制代码

结语

花了两天时间终于把框架撸完了,说实话,以前没有在意的细节现在都很通透,当然这对我来说只是一小步,我还打算选择一套UI框架来封装常见的业务,使框架更加的模块化和完善

另外值得一提的是,一段时间前已经发布了@vue/cli3,也就是vue脚手架第三个版本,看了文档发现它只是将插件配置用vue.config.js代替了,它会自动根据vue.config.js的配置生成一份webpack.config.js的文件,我们只需要提供插件,和设置是否开启它

如果你看到这里了,那你的毅力告诉我,你以后技术肯定更加厉害


以上所述就是小编给大家介绍的《徒手撸个vue项目框架(下)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Realm of Racket

Realm of Racket

Matthias Felleisen、Conrad Barski M.D.、David Van Horn、Eight Students Northeastern University of / No Starch Press / 2013-6-25 / USD 39.95

Racket is the noble descendant of Lisp, a programming language renowned for its elegance and power. But while Racket retains the functional goodness of Lisp that makes programming purists drool, it wa......一起来看看 《Realm of Racket》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具