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

写这篇文章的目的,更可能是让本身更熟悉vue-cli脚手架建立项目的依据和项目结构,其次是但愿个人学习过程能够帮到有疑惑的同窗,有什么错误还但愿能够获得指教css

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

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

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

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

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

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

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

1、路由

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

1.引入vue-router

a.下载
cnpm i vue-router --save-dev
复制代码
b.实例化

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

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中,以下操做webpack

import router from "./router/index";

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

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

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

如今你能够在浏览器中访问了es6

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

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

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>
复制代码

这时你看到的是这样的

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,能够访问到下面的页面

2、与服务端交互

官方推荐与服务端交互使用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页面

3、细节的完善

到这里项目框架功能已经算是撸完了,可是它有不少地方不够正规,和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的文件,咱们只须要提供插件,和设置是否开启它

若是你看到这里了,那你的毅力告诉我,你之后技术确定更加厉害

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息